diff options
Diffstat (limited to 'src/leap/bitmask/gui')
-rw-r--r-- | src/leap/bitmask/gui/advanced_key_management.py | 247 | ||||
-rw-r--r-- | src/leap/bitmask/gui/eip_status.py | 313 | ||||
-rw-r--r-- | src/leap/bitmask/gui/loggerwindow.py | 2 | ||||
-rw-r--r-- | src/leap/bitmask/gui/login.py | 5 | ||||
-rw-r--r-- | src/leap/bitmask/gui/mail_status.py | 2 | ||||
-rw-r--r-- | src/leap/bitmask/gui/mainwindow.py | 1001 | ||||
-rw-r--r-- | src/leap/bitmask/gui/preferenceswindow.py | 209 | ||||
-rw-r--r-- | src/leap/bitmask/gui/statemachines.py | 17 | ||||
-rw-r--r-- | src/leap/bitmask/gui/ui/eip_status.ui | 40 | ||||
-rw-r--r-- | src/leap/bitmask/gui/wizard.py | 102 |
10 files changed, 977 insertions, 961 deletions
diff --git a/src/leap/bitmask/gui/advanced_key_management.py b/src/leap/bitmask/gui/advanced_key_management.py index be6b4410..b3a4ed8e 100644 --- a/src/leap/bitmask/gui/advanced_key_management.py +++ b/src/leap/bitmask/gui/advanced_key_management.py @@ -19,11 +19,8 @@ Advanced Key Management """ import logging -from PySide import QtGui -from zope.proxy import sameProxiedObjects +from PySide import QtCore, QtGui -from leap.keymanager import openpgp -from leap.keymanager.errors import KeyAddressMismatch, KeyFingerprintMismatch from leap.bitmask.services import get_service_display_name, MX_SERVICE from ui_advanced_key_management import Ui_AdvancedKeyManagement @@ -34,7 +31,7 @@ class AdvancedKeyManagement(QtGui.QDialog): """ Advanced Key Management """ - def __init__(self, parent, has_mx, user, keymanager, soledad): + def __init__(self, parent, has_mx, user, backend, soledad_started): """ :param parent: parent object of AdvancedKeyManagement. :parent type: QWidget @@ -43,10 +40,10 @@ class AdvancedKeyManagement(QtGui.QDialog): :type has_mx: bool :param user: the current logged in user. :type user: unicode - :param keymanager: the existing keymanager instance - :type keymanager: KeyManager - :param soledad: a loaded instance of Soledad - :type soledad: Soledad + :param backend: Backend being used + :type backend: Backend + :param soledad_started: whether soledad has started or not + :type soledad_started: bool """ QtGui.QDialog.__init__(self, parent) @@ -56,7 +53,6 @@ class AdvancedKeyManagement(QtGui.QDialog): # XXX: Temporarily disable the key import. self.ui.pbImportKeys.setVisible(False) - # if Soledad is not started yet if not has_mx: msg = self.tr("The provider that you are using " "does not support {0}.") @@ -64,8 +60,7 @@ class AdvancedKeyManagement(QtGui.QDialog): self._disable_ui(msg) return - # if Soledad is not started yet - if sameProxiedObjects(soledad, None): + if not soledad_started: msg = self.tr("To use this, you need to enable/start {0}.") msg = msg.format(get_service_display_name(MX_SERVICE)) self._disable_ui(msg) @@ -78,17 +73,12 @@ class AdvancedKeyManagement(QtGui.QDialog): # "existing e-mails.") # self.ui.lblStatus.setText(msg) - self._keymanager = keymanager - self._soledad = soledad - - self._key = keymanager.get_key(user, openpgp.OpenPGPKey) - self._key_priv = keymanager.get_key( - user, openpgp.OpenPGPKey, private=True) + self._user = user + self._backend = backend + self._backend_connect() # show current key information self.ui.leUser.setText(user) - self.ui.leKeyID.setText(self._key.key_id) - self.ui.leFingerprint.setText(self._key.fingerprint) # set up connections self.ui.pbImportKeys.clicked.connect(self._import_keys) @@ -98,7 +88,15 @@ class AdvancedKeyManagement(QtGui.QDialog): self.ui.twPublicKeys.horizontalHeader().setResizeMode( 0, QtGui.QHeaderView.Stretch) - self._list_keys() + self._backend.keymanager_get_key_details(user) + self._backend.keymanager_list_keys() + + def _keymanager_key_details(self, details): + """ + Set the current user's key details into the gui. + """ + self.ui.leKeyID.setText(details[0]) + self.ui.leFingerprint.setText(details[1]) def _disable_ui(self, msg): """ @@ -117,53 +115,11 @@ class AdvancedKeyManagement(QtGui.QDialog): Imports the user's key pair. Those keys need to be ascii armored. """ - fileName, filtr = QtGui.QFileDialog.getOpenFileName( + file_name, filtr = QtGui.QFileDialog.getOpenFileName( self, self.tr("Open keys file"), options=QtGui.QFileDialog.DontUseNativeDialog) - if fileName: - new_key = '' - try: - with open(fileName, 'r') as keys_file: - new_key = keys_file.read() - except IOError as e: - logger.error("IOError importing key. {0!r}".format(e)) - QtGui.QMessageBox.critical( - self, self.tr("Input/Output error"), - self.tr("There was an error accessing the file.\n" - "Import canceled.")) - return - - keymanager = self._keymanager - try: - public_key, private_key = keymanager.parse_openpgp_ascii_key( - new_key) - except (KeyAddressMismatch, KeyFingerprintMismatch) as e: - logger.error(repr(e)) - QtGui.QMessageBox.warning( - self, self.tr("Data mismatch"), - self.tr("The public and private key should have the " - "same address and fingerprint.\n" - "Import canceled.")) - return - - if public_key is None or private_key is None: - QtGui.QMessageBox.warning( - self, self.tr("Missing key"), - self.tr("You need to provide the public AND private " - "key in the same file.\n" - "Import canceled.")) - return - - if public_key.address != self._key.address: - logger.error("The key does not match the ID") - QtGui.QMessageBox.warning( - self, self.tr("Address mismatch"), - self.tr("The identity for the key needs to be the same " - "as your user address.\n" - "Import canceled.")) - return - + if file_name: question = self.tr("Are you sure that you want to replace " "the current key pair whith the imported?") res = QtGui.QMessageBox.question( @@ -171,61 +127,152 @@ class AdvancedKeyManagement(QtGui.QDialog): QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.No) # default No - if res == QtGui.QMessageBox.No: - return + if res == QtGui.QMessageBox.Yes: + self._backend.keymanager_import_keys(self._user, file_name) + else: + logger.debug('Import canceled by the user.') - keymanager.delete_key(self._key) - keymanager.delete_key(self._key_priv) - keymanager.put_key(public_key) - keymanager.put_key(private_key) - keymanager.send_key(openpgp.OpenPGPKey) + @QtCore.Slot() + def _keymanager_import_ok(self): + """ + TRIGGERS: + Signaler.keymanager_import_ok - logger.debug('Import ok') + Notify the user that the key import went OK. + """ + QtGui.QMessageBox.information( + self, self.tr("Import Successful"), + self.tr("The key pair was imported successfully.")) - QtGui.QMessageBox.information( - self, self.tr("Import Successful"), - self.tr("The key pair was imported successfully.")) - else: - logger.debug('Import canceled by the user.') + @QtCore.Slot() + def _import_ioerror(self): + """ + TRIGGERS: + Signaler.keymanager_import_ioerror + + Notify the user that the key import had an IOError problem. + """ + QtGui.QMessageBox.critical( + self, self.tr("Input/Output error"), + self.tr("There was an error accessing the file.\n" + "Import canceled.")) + + @QtCore.Slot() + def _import_datamismatch(self): + """ + TRIGGERS: + Signaler.keymanager_import_datamismatch + + Notify the user that the key import had an data mismatch problem. + """ + QtGui.QMessageBox.warning( + self, self.tr("Data mismatch"), + self.tr("The public and private key should have the " + "same address and fingerprint.\n" + "Import canceled.")) + + @QtCore.Slot() + def _import_missingkey(self): + """ + TRIGGERS: + Signaler.keymanager_import_missingkey + + Notify the user that the key import failed due a missing key. + """ + QtGui.QMessageBox.warning( + self, self.tr("Missing key"), + self.tr("You need to provide the public AND private " + "key in the same file.\n" + "Import canceled.")) + + @QtCore.Slot() + def _import_addressmismatch(self): + """ + TRIGGERS: + Signaler.keymanager_import_addressmismatch + + Notify the user that the key import failed due an address mismatch. + """ + QtGui.QMessageBox.warning( + self, self.tr("Address mismatch"), + self.tr("The identity for the key needs to be the same " + "as your user address.\n" + "Import canceled.")) def _export_keys(self): """ Exports the user's key pair. """ - fileName, filtr = QtGui.QFileDialog.getSaveFileName( + file_name, filtr = QtGui.QFileDialog.getSaveFileName( self, self.tr("Save keys file"), options=QtGui.QFileDialog.DontUseNativeDialog) - if fileName: - try: - with open(fileName, 'w') as keys_file: - keys_file.write(self._key.key_data) - keys_file.write(self._key_priv.key_data) - - logger.debug('Export ok') - QtGui.QMessageBox.information( - self, self.tr("Export Successful"), - self.tr("The key pair was exported successfully.\n" - "Please, store your private key in a safe place.")) - except IOError as e: - logger.error("IOError exporting key. {0!r}".format(e)) - QtGui.QMessageBox.critical( - self, self.tr("Input/Output error"), - self.tr("There was an error accessing the file.\n" - "Export canceled.")) - return + if file_name: + self._backend.keymanager_export_keys(self._user, file_name) else: logger.debug('Export canceled by the user.') - def _list_keys(self): + @QtCore.Slot() + def _keymanager_export_ok(self): + """ + TRIGGERS: + Signaler.keymanager_export_ok + + Notify the user that the key export went OK. """ - Loads all the public keys stored in the local db to the keys table. + QtGui.QMessageBox.information( + self, self.tr("Export Successful"), + self.tr("The key pair was exported successfully.\n" + "Please, store your private key in a safe place.")) + + @QtCore.Slot() + def _keymanager_export_error(self): + """ + TRIGGERS: + Signaler.keymanager_export_error + + Notify the user that the key export didn't go well. + """ + QtGui.QMessageBox.critical( + self, self.tr("Input/Output error"), + self.tr("There was an error accessing the file.\n" + "Export canceled.")) + + @QtCore.Slot() + def _keymanager_keys_list(self, keys): """ - keys = self._keymanager.get_all_keys_in_local_db() + TRIGGERS: + Signaler.keymanager_keys_list + Load the keys given as parameter in the table. + + :param keys: the list of keys to load. + :type keys: list + """ keys_table = self.ui.twPublicKeys + for key in keys: row = keys_table.rowCount() keys_table.insertRow(row) keys_table.setItem(row, 0, QtGui.QTableWidgetItem(key.address)) keys_table.setItem(row, 1, QtGui.QTableWidgetItem(key.key_id)) + + def _backend_connect(self): + """ + Connect to backend signals. + """ + sig = self._backend.signaler + + sig.keymanager_export_ok.connect(self._keymanager_export_ok) + sig.keymanager_export_error.connect(self._keymanager_export_error) + sig.keymanager_keys_list.connect(self._keymanager_keys_list) + + sig.keymanager_key_details.connect(self._keymanager_key_details) + + sig.keymanager_import_ok.connect(self._keymanager_import_ok) + + sig.keymanager_import_ioerror.connect(self._import_ioerror) + sig.keymanager_import_datamismatch.connect(self._import_datamismatch) + sig.keymanager_import_missingkey.connect(self._import_missingkey) + sig.keymanager_import_addressmismatch.connect( + self._import_addressmismatch) diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py index ca28b8bf..8b9f2d44 100644 --- a/src/leap/bitmask/gui/eip_status.py +++ b/src/leap/bitmask/gui/eip_status.py @@ -24,7 +24,7 @@ from functools import partial from PySide import QtCore, QtGui -from leap.bitmask.services.eip.connection import EIPConnection +from leap.bitmask.config import flags from leap.bitmask.services import get_service_display_name, EIP_SERVICE from leap.bitmask.platform_init import IS_LINUX from leap.bitmask.util.averages import RateMovingAverage @@ -32,6 +32,7 @@ from leap.common.check import leap_assert_type from ui_eip_status import Ui_EIPStatus +QtDelayedCall = QtCore.QTimer.singleShot logger = logging.getLogger(__name__) @@ -43,9 +44,14 @@ class EIPStatusWidget(QtGui.QWidget): RATE_STR = "%1.2f KB/s" TOTAL_STR = "%1.2f Kb" - eip_connection_connected = QtCore.Signal() + def __init__(self, parent=None, eip_conductor=None): + """ + :param parent: the parent of the widget. + :type parent: QObject - def __init__(self, parent=None): + :param eip_conductor: an EIPConductor object. + :type eip_conductor: EIPConductor + """ QtGui.QWidget.__init__(self, parent) self._systray = None @@ -54,13 +60,17 @@ class EIPStatusWidget(QtGui.QWidget): self.ui = Ui_EIPStatus() self.ui.setupUi(self) - self.eipconnection = EIPConnection() + self.eip_conductor = eip_conductor + self.eipconnection = eip_conductor.eip_connection # set systray tooltip status self._eip_status = "" self._service_name = get_service_display_name(EIP_SERVICE) self.ui.eip_bandwidth.hide() + self.hide_fw_down_button() + self.ui.btnFwDown.clicked.connect( + self._on_fw_down_button_clicked) # Set the EIP status icons self.CONNECTING_ICON = None @@ -75,11 +85,43 @@ class EIPStatusWidget(QtGui.QWidget): self._make_status_clickable() self._provider = "" + self.is_restart = False + self.is_cold_start = True # Action for the systray self._eip_disabled_action = QtGui.QAction( "{0} is {1}".format(self._service_name, self.tr("disabled")), self) + def connect_backend_signals(self): + """ + Connect backend signals. + """ + signaler = self.eip_conductor._backend.signaler + + signaler.eip_openvpn_already_running.connect( + self._on_eip_openvpn_already_running) + signaler.eip_alien_openvpn_already_running.connect( + self._on_eip_alien_openvpn_already_running) + signaler.eip_openvpn_not_found_error.connect( + self._on_eip_openvpn_not_found_error) + signaler.eip_vpn_launcher_exception.connect( + self._on_eip_vpn_launcher_exception) + signaler.eip_no_polkit_agent_error.connect( + self._on_eip_no_polkit_agent_error) + signaler.eip_connection_aborted.connect( + self._on_eip_connection_aborted) + signaler.eip_no_pkexec_error.connect(self._on_eip_no_pkexec_error) + signaler.eip_no_tun_kext_error.connect(self._on_eip_no_tun_kext_error) + + signaler.eip_state_changed.connect(self.update_vpn_state) + signaler.eip_status_changed.connect(self.update_vpn_status) + + # XXX we cannot connect this signal now because + # it interferes with the proper notifications during restarts + # without available network. + #signaler.eip_network_unreachable.connect( + #self._on_eip_network_unreachable) + def _make_status_clickable(self): """ Makes upload and download figures clickable. @@ -208,7 +250,7 @@ class EIPStatusWidget(QtGui.QWidget): def set_action_eip_startstop(self, action_eip_startstop): """ - Sets the action_eip_startstop to use. + Set the action_eip_startstop to use. :param action_eip_startstop: action_eip_status to be used :type action_eip_startstop: QtGui.QAction @@ -238,9 +280,11 @@ class EIPStatusWidget(QtGui.QWidget): def eip_pre_up(self): """ Triggered when the app activates eip. - Hides the status box and disables the start/stop button. + Disables the start/stop button. """ self.set_startstop_enabled(False) + msg = self.tr("Encrypted Internet is starting") + self.set_eip_message(msg) @QtCore.Slot() def disable_eip_start(self): @@ -248,7 +292,7 @@ class EIPStatusWidget(QtGui.QWidget): Triggered when a default provider_config has not been found. Disables the start button and adds instructions to the user. """ - #logger.debug('Hiding EIP start button') + logger.debug('Hiding EIP start button') # you might be tempted to change this for a .setEnabled(False). # it won't work. it's under the claws of the state machine. # probably the best thing would be to make a conditional @@ -282,10 +326,19 @@ class EIPStatusWidget(QtGui.QWidget): if self.isVisible(): self._eip_status_menu.menuAction().setVisible(True) - # XXX disable (later) -------------------------- + def set_eip_message(self, message): + """ + Set the EIP Widget main message. + + :param message: the message to set in the widget + :type message: str or unicode + """ + self.ui.lblEIPMessage.setText(message) + self.ui.lblEIPMessage.show() + def set_eip_status(self, status, error=False): """ - Sets the status label at the VPN stage to status + Set the status label at the VPN stage to status. :param status: status message :type status: str or unicode @@ -326,29 +379,80 @@ class EIPStatusWidget(QtGui.QWidget): Sets the state of the widget to how it should look after EIP has started """ - self.ui.btnEipStartStop.setText(self.tr("Turn OFF")) self.ui.btnEipStartStop.disconnect(self) self.ui.btnEipStartStop.clicked.connect( self.eipconnection.qtsigs.do_connect_signal) - # XXX disable ----------------------------- - def eip_stopped(self): + def hide_fw_down_button(self): + """ + Hide firewall-down button. + """ + self.ui.btnFwDown.hide() + + def show_fw_down_button(self): + """ + Enable firewall-down button. """ + retry_msg = self.tr("Retry") + self.ui.btnEipStartStop.setText(retry_msg) + self._action_eip_startstop.setText(retry_msg) + self.ui.btnFwDown.show() + + def _on_fw_down_button_clicked(self): + """ + Raise a signal for tearing down the firewall, and hide the button + afterwards. + """ + self.eip_conductor._backend.tear_fw_down() + QtDelayedCall(50, self.hide_fw_down_button) + + # XXX do actual check + msg = "Traffic is being routed in the clear." + self.ui.btnEipStartStop.setText(self.tr("Turn ON")) + self.set_eip_message(msg) + self.set_eip_status("") + + @QtCore.Slot(dict) + def eip_stopped(self, restart=False, failed=False): + """ + TRIGGERS: + EIPConductor.qtsigs.disconnected_signal + Sets the state of the widget to how it should look after EIP has stopped """ - # XXX should connect this to EIPConnection.disconnected_signal + self.set_country_code("") self._reset_traffic_rates() - # XXX disable ----------------------------- - self.ui.btnEipStartStop.setText(self.tr("Turn ON")) - self.ui.btnEipStartStop.disconnect(self) - self.ui.btnEipStartStop.clicked.connect( - self.eipconnection.qtsigs.do_disconnect_signal) - self.ui.eip_bandwidth.hide() - self.ui.lblEIPMessage.setText( - self.tr("Traffic is being routed in the clear")) + + # This is assuming the firewall works correctly, but we should test fw + # status positively. + # Or better call it from the conductor... + + clear_traffic = self.tr("Traffic is being routed in the clear.") + unreachable_net = self.tr("Network is unreachable.") + failed_msg = self.tr("Error connecting") + + if restart: + msg = unreachable_net + elif failed: + msg = failed_msg + else: + msg = clear_traffic + self.set_eip_message(msg) + self.ui.lblEIPStatus.show() + self.show() + + def eip_failed_to_connect(self): + """ + Update EIP messages with error during (re)connection. + """ + msg = self.tr("Error connecting.") + self.ui.lblEIPMessage.setText(msg) self.ui.lblEIPStatus.show() + self.set_eip_status(self.tr("Bitmask is blocking " + "unencrypted traffic.")) + self.show_fw_down_button() @QtCore.Slot(dict) def update_vpn_status(self, data=None): @@ -407,11 +511,20 @@ class EIPStatusWidget(QtGui.QWidget): self.ui.lblEIPStatus.hide() # XXX should be handled by the state machine too. - self.eip_connection_connected.emit() + # --- is this currently being sent? + self.eipconnection.qtsigs.connected_signal.emit() + self._on_eip_connected() + self.is_cold_start = False # XXX should lookup vpn_state map in EIPConnection elif vpn_state == "AUTH": self.set_eip_status(self.tr("Authenticating...")) + # we wipe up any previous error info in the EIP message + # when we detect vpn authentication is happening + msg = self.tr("Encrypted Internet is starting") + self.set_eip_message(msg) + # on the first-run path, we hadn't showed the button yet. + self.eip_button.show() elif vpn_state == "GET_CONFIG": self.set_eip_status(self.tr("Retrieving configuration...")) elif vpn_state == "WAIT": @@ -423,10 +536,11 @@ class EIPStatusWidget(QtGui.QWidget): elif vpn_state == "ALREADYRUNNING": # Put the following calls in Qt's event queue, otherwise # the UI won't update properly - QtCore.QTimer.singleShot( - 0, self.eipconnection.qtsigs.do_disconnect_signal) + #self.send_disconnect_signal() + QtDelayedCall( + 0, self.eipconnection.qtsigns.do_disconnect_signal.emit) msg = self.tr("Unable to start VPN, it's already running.") - QtCore.QTimer.singleShot(0, partial(self.set_eip_status, msg)) + QtDelayedCall(0, partial(self.set_eip_status, msg)) else: self.set_eip_status(vpn_state) @@ -468,5 +582,152 @@ class EIPStatusWidget(QtGui.QWidget): def set_provider(self, provider): self._provider = provider + self.ui.lblEIPMessage.setText( - self.tr("Route traffic through: {0}").format(self._provider)) + self.tr("Routing traffic through: <b>{0}</b>").format( + provider)) + + ccode = flags.CURRENT_VPN_COUNTRY + if ccode is not None: + self.set_country_code(ccode) + + def set_country_code(self, code): + """ + Set the pixmap of the given country code + + :param code: the country code + :type code: str + """ + if code is not None and len(code) == 2: + img = ":/images/countries/%s.png" % (code.lower(),) + else: + img = None + cc = self.ui.lblGatewayCountryCode + cc.setPixmap(QtGui.QPixmap(img)) + cc.setToolTip(code) + + def aborted(self): + """ + Notify the state machine that EIP was aborted for some reason. + """ + # signal connection_aborted to state machine: + qtsigs = self.eipconnection.qtsigs + qtsigs.connection_aborted_signal.emit() + + # + # Slots for signals + # + + @QtCore.Slot() + def _on_eip_connection_aborted(self): + """ + TRIGGERS: + Signaler.eip_connection_aborted + """ + # TODO this name is very misleading, since there's a generic signal + # that's called connection_aborted / connection_died... + # should rename to something more specific about missing config. + logger.error("Tried to start EIP but cannot find any " + "available provider!") + + eip_status_label = self.tr("Could not load {0} configuration.") + eip_status_label = eip_status_label.format( + self.eip_conductor.eip_name) + self.set_eip_status(eip_status_label, error=True) + + self.aborted() + + def _on_eip_openvpn_already_running(self): + self.set_eip_status( + self.tr("Another openvpn instance is already running, and " + "could not be stopped."), + error=True) + self.set_eipstatus_off() + + self.aborted() + + def _on_eip_alien_openvpn_already_running(self): + self.set_eip_status( + self.tr("Another openvpn instance is already running, and " + "could not be stopped because it was not launched by " + "Bitmask. Please stop it and try again."), + error=True) + self.set_eipstatus_off() + + self.aborted() + + def _on_eip_openvpn_not_found_error(self): + self.set_eip_status( + self.tr("We could not find openvpn binary."), + error=True) + self.set_eipstatus_off() + + self.aborted() + + def _on_eip_vpn_launcher_exception(self): + # XXX We should implement again translatable exceptions so + # we can pass a translatable string to the panel (usermessage attr) + self.set_eip_status("VPN Launcher error.", error=True) + self.set_eipstatus_off() + + self.aborted() + + def _on_eip_no_polkit_agent_error(self): + self.set_eip_status( + # XXX this should change to polkit-kde where + # applicable. + self.tr("We could not find any authentication agent in your " + "system.<br/>Make sure you have" + "<b>polkit-gnome-authentication-agent-1</b> running and" + "try again."), + error=True) + self.set_eipstatus_off() + + self.aborted() + + def _on_eip_no_pkexec_error(self): + self.set_eip_status( + self.tr("We could not find <b>pkexec</b> in your system."), + error=True) + self.set_eipstatus_off() + + self.aborted() + + def _on_eip_no_tun_kext_error(self): + self.set_eip_status( + self.tr("{0} cannot be started because the tuntap extension is " + "not installed properly in your " + "system.").format(self.eip_conductor.eip_name)) + self.set_eipstatus_off() + + self.aborted() + + def _on_eip_connected(self): + """ + Reconnect the disconnecting signal when we are just connected, + so that we restore the disconnecting -> stop behaviour. + """ + self.eip_conductor.reconnect_stop_signal() + + @QtCore.Slot() + def _on_eip_network_unreachable(self): + """ + TRIGGERS: + self._eip_connection.qtsigs.network_unreachable + + Displays a "network unreachable" error in the EIP status panel. + """ + self.set_eip_status(self.tr("Network is unreachable"), + error=True) + self.set_eip_status_icon("error") + + def set_eipstatus_off(self, error=True): + # XXX this should be handled by the state machine. + """ + Sets eip status to off + """ + self.set_eip_status("", error=error) + self.set_eip_status_icon("error") + +import eipstatus_rc +assert(eipstatus_rc) diff --git a/src/leap/bitmask/gui/loggerwindow.py b/src/leap/bitmask/gui/loggerwindow.py index f19b172f..3a8354b1 100644 --- a/src/leap/bitmask/gui/loggerwindow.py +++ b/src/leap/bitmask/gui/loggerwindow.py @@ -27,7 +27,7 @@ from twisted.internet import threads from ui_loggerwindow import Ui_LoggerWindow from leap.bitmask.util.constants import PASTEBIN_API_DEV_KEY -from leap.bitmask.util.leap_log_handler import LeapLogHandler +from leap.bitmask.logs.leap_log_handler import LeapLogHandler from leap.bitmask.util import pastebin from leap.common.check import leap_assert, leap_assert_type diff --git a/src/leap/bitmask/gui/login.py b/src/leap/bitmask/gui/login.py index ac7ad878..f66e71d9 100644 --- a/src/leap/bitmask/gui/login.py +++ b/src/leap/bitmask/gui/login.py @@ -24,6 +24,7 @@ from ui_login import Ui_LoginWidget from leap.bitmask.config import flags from leap.bitmask.util import make_address +from leap.bitmask.util.credentials import USERNAME_REGEX from leap.bitmask.util.keyring_helpers import has_keyring from leap.bitmask.util.keyring_helpers import get_keyring from leap.common.check import leap_assert_type @@ -48,8 +49,6 @@ class LoginWidget(QtGui.QWidget): MAX_STATUS_WIDTH = 40 - BARE_USERNAME_REGEX = r"^[A-Za-z\d_]+$" - # Keyring KEYRING_KEY = "bitmask" @@ -87,7 +86,7 @@ class LoginWidget(QtGui.QWidget): self.ui.btnLogout.clicked.connect( self.logout) - username_re = QtCore.QRegExp(self.BARE_USERNAME_REGEX) + username_re = QtCore.QRegExp(USERNAME_REGEX) self.ui.lnUser.setValidator( QtGui.QRegExpValidator(username_re, self)) diff --git a/src/leap/bitmask/gui/mail_status.py b/src/leap/bitmask/gui/mail_status.py index d3346780..5caef745 100644 --- a/src/leap/bitmask/gui/mail_status.py +++ b/src/leap/bitmask/gui/mail_status.py @@ -188,7 +188,7 @@ class MailStatusWidget(QtGui.QWidget): def set_soledad_failed(self): """ TRIGGERS: - SoledadBootstrapper.soledad_failed + Signaler.soledad_bootstrap_failed This method is called whenever soledad has a failure. """ diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index e3848c46..3ef994b1 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -19,22 +19,17 @@ Main window for Bitmask. """ import logging import socket -import time -from threading import Condition from datetime import datetime from PySide import QtCore, QtGui -from zope.proxy import ProxyBase, setProxiedObject from twisted.internet import reactor, threads from leap.bitmask import __version__ as VERSION from leap.bitmask import __version_hash__ as VERSION_HASH from leap.bitmask.config import flags from leap.bitmask.config.leapsettings import LeapSettings -from leap.bitmask.config.providerconfig import ProviderConfig -from leap.bitmask.gui import statemachines from leap.bitmask.gui.advanced_key_management import AdvancedKeyManagement from leap.bitmask.gui.eip_preferenceswindow import EIPPreferencesWindow from leap.bitmask.gui.eip_status import EIPStatusWidget @@ -45,30 +40,24 @@ from leap.bitmask.gui.preferenceswindow import PreferencesWindow from leap.bitmask.gui.systray import SysTray from leap.bitmask.gui.wizard import Wizard -from leap.bitmask import provider from leap.bitmask.platform_init import IS_WIN, IS_MAC, IS_LINUX from leap.bitmask.platform_init.initializers import init_platform from leap.bitmask import backend -from leap.bitmask.services import get_service_display_name - +from leap.bitmask.services.eip import conductor as eip_conductor from leap.bitmask.services.mail import conductor as mail_conductor from leap.bitmask.services import EIP_SERVICE, MX_SERVICE -from leap.bitmask.services.eip.connection import EIPConnection -from leap.bitmask.services.soledad.soledadbootstrapper import \ - SoledadBootstrapper from leap.bitmask.util import make_address from leap.bitmask.util.keyring_helpers import has_keyring -from leap.bitmask.util.leap_log_handler import LeapLogHandler +from leap.bitmask.logs.leap_log_handler import LeapLogHandler if IS_WIN: from leap.bitmask.platform_init.locks import WindowsLock from leap.bitmask.platform_init.locks import raise_window_ack -from leap.common.check import leap_assert from leap.common.events import register from leap.common.events import events_pb2 as proto @@ -76,6 +65,7 @@ from leap.mail.imap.service.imap import IMAP_PORT from ui_mainwindow import Ui_MainWindow +QtDelayedCall = QtCore.QTimer.singleShot logger = logging.getLogger(__name__) @@ -89,17 +79,17 @@ class MainWindow(QtGui.QMainWindow): new_updates = QtCore.Signal(object) raise_window = QtCore.Signal([]) soledad_ready = QtCore.Signal([]) - mail_client_logged_in = QtCore.Signal([]) logout = QtCore.Signal([]) + all_services_stopped = QtCore.Signal() # We use this flag to detect abnormal terminations user_stopped_eip = False # We give EIP some time to come up before starting soledad anyway - EIP_TIMEOUT = 60000 # in milliseconds + EIP_START_TIMEOUT = 60000 # in milliseconds - # We give each service some time to come to a halt before forcing quit - SERVICE_STOP_TIMEOUT = 20 + # We give the services some time to a halt before forcing quit. + SERVICES_STOP_TIMEOUT = 20 def __init__(self, quit_callback, bypass_checks=False, start_hidden=False): """ @@ -125,9 +115,6 @@ class MainWindow(QtGui.QMainWindow): register(signal=proto.RAISE_WINDOW, callback=self._on_raise_window_event, reqcbk=lambda req, resp: None) # make rpc call async - register(signal=proto.IMAP_CLIENT_LOGIN, - callback=self._on_mail_client_logged_in, - reqcbk=lambda req, resp: None) # make rpc call async # end register leap events #################################### self._quit_callback = quit_callback @@ -142,11 +129,16 @@ class MainWindow(QtGui.QMainWindow): self._settings = LeapSettings() + # Login Widget self._login_widget = LoginWidget( self._settings, self) self.ui.loginLayout.addWidget(self._login_widget) + # Mail Widget + self._mail_status = MailStatusWidget(self) + self.ui.mailLayout.addWidget(self._mail_status) + # Qt Signal Connections ##################################### # TODO separate logic from ui signals. @@ -155,67 +147,45 @@ class MainWindow(QtGui.QMainWindow): self._login_widget.show_wizard.connect(self._launch_wizard) self._login_widget.logout.connect(self._logout) - self._eip_status = EIPStatusWidget(self) - self.ui.eipLayout.addWidget(self._eip_status) - self._login_widget.logged_in_signal.connect( - self._eip_status.enable_eip_start) - self._login_widget.logged_in_signal.connect( - self._enable_eip_start_action) + # EIP Control redux ######################################### + self._eip_conductor = eip_conductor.EIPConductor( + self._settings, self._backend) + self._eip_status = EIPStatusWidget(self, self._eip_conductor) - self._mail_status = MailStatusWidget(self) - self.ui.mailLayout.addWidget(self._mail_status) - - self._eip_connection = EIPConnection() - - # XXX this should be handled by EIP Conductor - self._eip_connection.qtsigs.connecting_signal.connect( - self._start_EIP) - self._eip_connection.qtsigs.disconnecting_signal.connect( - self._stop_eip) + self.ui.eipLayout.addWidget(self._eip_status) + self._eip_conductor.add_eip_widget(self._eip_status) - self._eip_status.eip_connection_connected.connect( + self._eip_conductor.connect_signals() + self._eip_conductor.qtsigs.connected_signal.connect( self._on_eip_connection_connected) - self._eip_status.eip_connection_connected.connect( + self._eip_conductor.qtsigs.connected_signal.connect( self._maybe_run_soledad_setup_checks) + self.offline_mode_bypass_login.connect( self._maybe_run_soledad_setup_checks) self.eip_needs_login.connect(self._eip_status.disable_eip_start) self.eip_needs_login.connect(self._disable_eip_start_action) + self._already_started_eip = False self._trying_to_start_eip = False - # This is loaded only once, there's a bug when doing that more - # than once - # XXX HACK!! But we need it as long as we are using - # provider_config in here - self._provider_config = self._backend.get_provider_config() - - # Used for automatic start of EIP - self._provisional_provider_config = ProviderConfig() - self._already_started_eip = False - self._already_started_soledad = False + self._soledad_started = False # This is created once we have a valid provider config self._srp_auth = None self._logged_user = None self._logged_in_offline = False - self._backend_connected_signals = {} - self._backend_connect() + # Set used to track the services being stopped and need wait. + self._services_being_stopped = {} - self._soledad_bootstrapper = SoledadBootstrapper() - self._soledad_bootstrapper.download_config.connect( - self._soledad_intermediate_stage) - self._soledad_bootstrapper.gen_key.connect( - self._soledad_bootstrapped_stage) - self._soledad_bootstrapper.local_only_ready.connect( - self._soledad_bootstrapped_stage) - self._soledad_bootstrapper.soledad_invalid_auth_token.connect( - self._mail_status.set_soledad_invalid_auth_token) - self._soledad_bootstrapper.soledad_failed.connect( - self._mail_status.set_soledad_failed) + # timeout object used to trigger quit + self._quit_timeout_callater = None + + self._backend_connected_signals = [] + self._backend_connect() self.ui.action_preferences.triggered.connect(self._show_preferences) self.ui.action_eip_preferences.triggered.connect( @@ -241,8 +211,7 @@ class MainWindow(QtGui.QMainWindow): self._systray = None - # XXX separate actions into a different - # module. + # XXX separate actions into a different module. self._action_mail_status = QtGui.QAction(self.tr("Mail is OFF"), self) self._mail_status.set_action_mail_status(self._action_mail_status) @@ -260,6 +229,8 @@ class MainWindow(QtGui.QMainWindow): self._ui_mx_visible = True self._ui_eip_visible = True + self._provider_details = None + # last minute UI manipulations self._center_window() @@ -280,10 +251,6 @@ class MainWindow(QtGui.QMainWindow): # XXX should connect to mail_conductor.start_mail_service instead self.soledad_ready.connect(self._start_smtp_bootstrapping) self.soledad_ready.connect(self._start_imap_service) - self.mail_client_logged_in.connect(self._fetch_incoming_mail) - self.logout.connect(self._stop_imap_service) - self.logout.connect(self._stop_smtp_service) - ################################# end Qt Signals connection ######## init_platform() @@ -296,18 +263,11 @@ class MainWindow(QtGui.QMainWindow): self._bypass_checks = bypass_checks self._start_hidden = start_hidden - # We initialize Soledad and Keymanager instances as - # transparent proxies, so we can pass the reference freely - # around. - self._soledad = ProxyBase(None) - self._keymanager = ProxyBase(None) - - self._soledad_defer = None - - self._mail_conductor = mail_conductor.MailConductor( - self._soledad, self._keymanager) + self._mail_conductor = mail_conductor.MailConductor(self._backend) self._mail_conductor.connect_mail_signals(self._mail_status) + self.logout.connect(self._mail_conductor.stop_mail_services) + # Eip machine is a public attribute where the state machine for # the eip connection will be available to the different components. # Remember that this will not live in the +1600LOC mainwindow for @@ -315,20 +275,19 @@ class MainWindow(QtGui.QMainWindow): # the EIPConductor or some other clever component that we will # instantiate from here. - self.eip_machine = None # start event machines - self.start_eip_machine() + # TODO should encapsulate all actions into one object + self._eip_conductor.start_eip_machine( + action=self._action_eip_startstop) self._mail_conductor.start_mail_machine() - self._eip_name = get_service_display_name(EIP_SERVICE) - if self._first_run(): self._wizard_firstrun = True self._disconnect_and_untrack() self._wizard = Wizard(backend=self._backend, bypass_checks=bypass_checks) # Give this window time to finish init and then show the wizard - QtCore.QTimer.singleShot(1, self._launch_wizard) + QtDelayedCall(1, self._launch_wizard) self._wizard.accepted.connect(self._finish_init) self._wizard.rejected.connect(self._rejected_wizard) else: @@ -357,7 +316,7 @@ class MainWindow(QtGui.QMainWindow): :param method: the method to call when the signal is triggered. :type method: callable, Slot or Signal """ - self._backend_connected_signals[signal] = method + self._backend_connected_signals.append((signal, method)) signal.connect(method) def _backend_bad_call(self, data): @@ -370,97 +329,102 @@ class MainWindow(QtGui.QMainWindow): logger.error("Bad call to the backend:") logger.error(data) - def _backend_connect(self): + def _backend_connect(self, only_tracked=False): """ - Helper to connect to backend signals - """ - sig = self._backend.signaler + Connect to backend signals. - sig.backend_bad_call.connect(self._backend_bad_call) + We track some signals in order to disconnect them on demand. + For instance, in the wizard we need to connect to some signals that are + already connected in the mainwindow, so to avoid conflicts we do: + - disconnect signals needed in wizard (`_disconnect_and_untrack`) + - use wizard + - reconnect disconnected signals (we use the `only_tracked` param) - self._connect_and_track(sig.prov_name_resolution, - self._intermediate_stage) - self._connect_and_track(sig.prov_https_connection, - self._intermediate_stage) - self._connect_and_track(sig.prov_download_ca_cert, - self._intermediate_stage) + :param only_tracked: whether or not we should connect only the signals + that we are tracking to disconnect later. + :type only_tracked: bool + """ + sig = self._backend.signaler + conntrack = self._connect_and_track + auth_err = self._authentication_error - self._connect_and_track(sig.prov_download_provider_info, - self._load_provider_config) - self._connect_and_track(sig.prov_check_api_certificate, - self._provider_config_loaded) + conntrack(sig.prov_name_resolution, self._intermediate_stage) + conntrack(sig.prov_https_connection, self._intermediate_stage) + conntrack(sig.prov_download_ca_cert, self._intermediate_stage) + conntrack(sig.prov_download_provider_info, self._load_provider_config) + conntrack(sig.prov_check_api_certificate, self._provider_config_loaded) + conntrack(sig.prov_check_api_certificate, self._get_provider_details) - self._connect_and_track(sig.prov_problem_with_provider, - self._login_problem_provider) + conntrack(sig.prov_problem_with_provider, self._login_problem_provider) + conntrack(sig.prov_cancelled_setup, self._set_login_cancelled) - self._connect_and_track(sig.prov_cancelled_setup, - self._set_login_cancelled) + conntrack(sig.prov_get_details, self._provider_get_details) # Login signals - self._connect_and_track(sig.srp_auth_ok, self._authentication_finished) + conntrack(sig.srp_auth_ok, self._authentication_finished) - auth_error = ( - lambda: self._authentication_error(self.tr("Unknown error."))) - self._connect_and_track(sig.srp_auth_error, auth_error) + auth_error = lambda: auth_err(self.tr("Unknown error.")) + conntrack(sig.srp_auth_error, auth_error) - auth_server_error = ( - lambda: self._authentication_error( - self.tr("There was a server problem with authentication."))) - self._connect_and_track(sig.srp_auth_server_error, auth_server_error) + auth_server_error = lambda: auth_err(self.tr( + "There was a server problem with authentication.")) + conntrack(sig.srp_auth_server_error, auth_server_error) - auth_connection_error = ( - lambda: self._authentication_error( - self.tr("Could not establish a connection."))) - self._connect_and_track(sig.srp_auth_connection_error, - auth_connection_error) + auth_connection_error = lambda: auth_err(self.tr( + "Could not establish a connection.")) + conntrack(sig.srp_auth_connection_error, auth_connection_error) - auth_bad_user_or_password = ( - lambda: self._authentication_error( - self.tr("Invalid username or password."))) - self._connect_and_track(sig.srp_auth_bad_user_or_password, - auth_bad_user_or_password) + auth_bad_user_or_password = lambda: auth_err(self.tr( + "Invalid username or password.")) + conntrack(sig.srp_auth_bad_user_or_password, auth_bad_user_or_password) # Logout signals - self._connect_and_track(sig.srp_logout_ok, self._logout_ok) - self._connect_and_track(sig.srp_logout_error, self._logout_error) - - self._connect_and_track(sig.srp_not_logged_in_error, - self._not_logged_in_error) + conntrack(sig.srp_logout_ok, self._logout_ok) + conntrack(sig.srp_logout_error, self._logout_error) + conntrack(sig.srp_not_logged_in_error, self._not_logged_in_error) # EIP bootstrap signals - self._connect_and_track(sig.eip_config_ready, - self._eip_intermediate_stage) - self._connect_and_track(sig.eip_client_certificate_ready, - self._finish_eip_bootstrap) + conntrack(sig.eip_config_ready, self._eip_intermediate_stage) + conntrack(sig.eip_client_certificate_ready, self._finish_eip_bootstrap) + + ################################################### + # Add tracked signals above this, untracked below! + ################################################### + if only_tracked: + return # We don't want to disconnect some signals so don't track them: + + sig.backend_bad_call.connect(self._backend_bad_call) + sig.prov_unsupported_client.connect(self._needs_update) sig.prov_unsupported_api.connect(self._incompatible_api) + sig.prov_get_all_services.connect(self._provider_get_all_services) - # EIP start signals - sig.eip_openvpn_already_running.connect( - self._on_eip_openvpn_already_running) - sig.eip_alien_openvpn_already_running.connect( - self._on_eip_alien_openvpn_already_running) - sig.eip_openvpn_not_found_error.connect( - self._on_eip_openvpn_not_found_error) - sig.eip_vpn_launcher_exception.connect( - self._on_eip_vpn_launcher_exception) - sig.eip_no_polkit_agent_error.connect( - self._on_eip_no_polkit_agent_error) - sig.eip_no_pkexec_error.connect(self._on_eip_no_pkexec_error) - sig.eip_no_tun_kext_error.connect(self._on_eip_no_tun_kext_error) - - sig.eip_state_changed.connect(self._eip_status.update_vpn_state) - sig.eip_status_changed.connect(self._eip_status.update_vpn_status) - sig.eip_process_finished.connect(self._eip_finished) - sig.eip_network_unreachable.connect(self._on_eip_network_unreachable) - sig.eip_process_restart_tls.connect(self._do_eip_restart) - sig.eip_process_restart_ping.connect(self._do_eip_restart) + # EIP start signals ============================================== + self._eip_conductor.connect_backend_signals() sig.eip_can_start.connect(self._backend_can_start_eip) sig.eip_cannot_start.connect(self._backend_cannot_start_eip) + # ================================================================== + + # Soledad signals + # TODO delegate connection to soledad bootstrapper + sig.soledad_bootstrap_failed.connect( + self._mail_status.set_soledad_failed) + sig.soledad_bootstrap_finished.connect(self._on_soledad_ready) + + sig.soledad_offline_failed.connect( + self._mail_status.set_soledad_failed) + sig.soledad_offline_finished.connect(self._on_soledad_ready) + + sig.soledad_invalid_auth_token.connect( + self._mail_status.set_soledad_invalid_auth_token) + + # TODO: connect this with something + # sig.soledad_cancelled_bootstrap.connect() + def _disconnect_and_untrack(self): """ Helper to disconnect the tracked signals. @@ -468,13 +432,13 @@ class MainWindow(QtGui.QMainWindow): Some signals are emitted from the wizard, and we want to ignore those. """ - for signal, method in self._backend_connected_signals.items(): + for signal, method in self._backend_connected_signals: try: signal.disconnect(method) except RuntimeError: pass # Signal was not connected - self._backend_connected_signals = {} + self._backend_connected_signals = [] @QtCore.Slot() def _rejected_wizard(self): @@ -497,7 +461,7 @@ class MainWindow(QtGui.QMainWindow): # This happens if the user finishes the provider # setup but does not register self._wizard = None - self._backend_connect() + self._backend_connect(only_tracked=True) if self._wizard_firstrun: self._finish_init() @@ -586,13 +550,14 @@ class MainWindow(QtGui.QMainWindow): domain = self._login_widget.get_selected_provider() logged_user = "{0}@{1}".format(self._logged_user, domain) - has_mx = True - if self._logged_user is not None: - provider_config = self._get_best_provider_config() - has_mx = provider_config.provides_mx() + details = self._provider_details + mx_provided = False + if details is not None: + mx_provided = MX_SERVICE in details.services - akm = AdvancedKeyManagement( - self, has_mx, logged_user, self._keymanager, self._soledad) + # XXX: handle differently not logged in user? + akm = AdvancedKeyManagement(self, mx_provided, logged_user, + self._backend, self._soledad_started) akm.show() @QtCore.Slot() @@ -604,11 +569,13 @@ class MainWindow(QtGui.QMainWindow): Displays the preferences window. """ - user = self._login_widget.get_user() - prov = self._login_widget.get_selected_provider() - preferences = PreferencesWindow( - self, self._backend, self._provider_config, self._soledad, - user, prov) + user = self._logged_user + domain = self._login_widget.get_selected_provider() + mx_provided = False + if self._provider_details is not None: + mx_provided = MX_SERVICE in self._provider_details.services + preferences = PreferencesWindow(self, user, domain, self._backend, + self._soledad_started, mx_provided) self.soledad_ready.connect(preferences.set_soledad_ready) preferences.show() @@ -630,7 +597,7 @@ class MainWindow(QtGui.QMainWindow): default_provider = settings.get_defaultprovider() if default_provider is None: - logger.warning("Trying toupdate eip enabled status but there's no" + logger.warning("Trying to update eip enabled status but there's no" " default provider. Disabling EIP for the time" " being...") self._backend_cannot_start_eip() @@ -642,7 +609,7 @@ class MainWindow(QtGui.QMainWindow): # If we don't want to start eip, we leave everything # initialized to quickly start it if not self._trying_to_start_eip: - self._backend.setup_eip(default_provider, skip_network=True) + self._backend.eip_setup(default_provider, skip_network=True) def _backend_can_start_eip(self): """ @@ -670,7 +637,6 @@ class MainWindow(QtGui.QMainWindow): # so the user needs to log in first self._eip_status.disable_eip_start() else: - self._stop_eip() self._eip_status.disable_eip_start() self._eip_status.set_eip_status(self.tr("Disabled")) @@ -697,7 +663,6 @@ class MainWindow(QtGui.QMainWindow): # so the user needs to log in first self._eip_status.disable_eip_start() else: - self._stop_eip() self._eip_status.disable_eip_start() self._eip_status.set_eip_status(self.tr("Disabled")) @@ -817,7 +782,7 @@ class MainWindow(QtGui.QMainWindow): self.eip_needs_login.emit() self._wizard = None - self._backend_connect() + self._backend_connect(only_tracked=True) else: self._update_eip_enabled_status() @@ -846,16 +811,9 @@ class MainWindow(QtGui.QMainWindow): """ providers = self._settings.get_configured_providers() - services = set() - - for prov in providers: - provider_config = ProviderConfig() - loaded = provider_config.load( - provider.get_provider_path(prov)) - if loaded: - for service in provider_config.get_services(): - services.add(service) + self._backend.provider_get_all_services(providers) + def _provider_get_all_services(self, services): self._set_eip_visible(EIP_SERVICE in services) self._set_mx_visible(MX_SERVICE in services) @@ -893,14 +851,11 @@ class MainWindow(QtGui.QMainWindow): """ Set the login label to reflect offline status. """ - if self._logged_in_offline: - provider = "" - else: + provider = "" + if not self._logged_in_offline: provider = self.ui.lblLoginProvider.text() - self.ui.lblLoginProvider.setText( - provider + - self.tr(" (offline mode)")) + self.ui.lblLoginProvider.setText(provider + self.tr(" (offline mode)")) # # systray @@ -923,7 +878,8 @@ class MainWindow(QtGui.QMainWindow): systrayMenu.addAction(self._action_visible) systrayMenu.addSeparator() - eip_status_label = "{0}: {1}".format(self._eip_name, self.tr("OFF")) + eip_status_label = "{0}: {1}".format( + self._eip_conductor.eip_name, self.tr("OFF")) self._eip_menu = eip_menu = systrayMenu.addMenu(eip_status_label) eip_menu.addAction(self._action_eip_startstop) self._eip_status.set_eip_status_menu(eip_menu) @@ -1005,7 +961,7 @@ class MainWindow(QtGui.QMainWindow): # Wait a bit until the window visibility has changed so # the menu is set with the correct value. - QtCore.QTimer.singleShot(500, self._update_hideshow_menu) + QtDelayedCall(500, self._update_hideshow_menu) def _center_window(self): """ @@ -1154,9 +1110,8 @@ class MainWindow(QtGui.QMainWindow): provider configuration if it's not present, otherwise will emit the corresponding signals inmediately """ - # XXX should rename this provider, name clash. - provider = self._login_widget.get_selected_provider() - self._backend.setup_provider(provider) + domain = self._login_widget.get_selected_provider() + self._backend.provider_setup(domain) @QtCore.Slot(dict) def _load_provider_config(self, data): @@ -1164,12 +1119,11 @@ class MainWindow(QtGui.QMainWindow): TRIGGERS: self._backend.signaler.prov_download_provider_info - Once the provider config has been downloaded, this loads the - self._provider_config instance with it and starts the second - part of the bootstrapping sequence + Once the provider config has been downloaded, start the second + part of the bootstrapping sequence. :param data: result from the last stage of the - run_provider_select_checks + backend.provider_setup() :type data: dict """ if data[self._backend.PASSED_KEY]: @@ -1211,7 +1165,6 @@ class MainWindow(QtGui.QMainWindow): self._set_label_offline() self.offline_mode_bypass_login.emit() else: - leap_assert(self._provider_config, "We need a provider config") self.ui.action_create_new_account.setEnabled(False) if self._login_widget.start_login(): self._download_provider_config() @@ -1250,20 +1203,19 @@ class MainWindow(QtGui.QMainWindow): Cancel the running defers to avoid app blocking. """ # XXX: Should we stop all the backend defers? - self._backend.cancel_setup_provider() - self._backend.cancel_login() + self._backend.provider_cancel_setup() + self._backend.user_cancel_login() + self._backend.soledad_cancel_bootstrap() + self._backend.soledad_close() - if self._soledad_defer is not None: - logger.debug("Cancelling soledad defer.") - self._soledad_defer.cancel() - self._soledad_defer = None + self._soledad_started = False @QtCore.Slot() def _set_login_cancelled(self): """ TRIGGERS: Signaler.prov_cancelled_setup fired by - self._backend.cancel_setup_provider() + self._backend.provider_cancel_setup() This method re-enables the login widget and display a message for the cancelled operation. @@ -1280,16 +1232,14 @@ class MainWindow(QtGui.QMainWindow): Once the provider configuration is loaded, this starts the SRP authentication """ - leap_assert(self._provider_config, "We need a provider config!") - if data[self._backend.PASSED_KEY]: username = self._login_widget.get_user() password = self._login_widget.get_password() self._show_hide_unsupported_services() - domain = self._provider_config.get_domain() - self._backend.login(domain, username, password) + domain = self._login_widget.get_selected_provider() + self._backend.user_login(domain, username, password) else: logger.error(data[self._backend.ERROR_KEY]) self._login_problem_provider() @@ -1307,7 +1257,7 @@ class MainWindow(QtGui.QMainWindow): self._logged_user = self._login_widget.get_user() user = self._logged_user - domain = self._provider_config.get_domain() + domain = self._login_widget.get_selected_provider() full_user_id = make_address(user, domain) self._mail_conductor.userid = full_user_id self._start_eip_bootstrap() @@ -1317,11 +1267,11 @@ class MainWindow(QtGui.QMainWindow): if MX_SERVICE in self._enabled_services: btn_enabled = self._login_widget.set_logout_btn_enabled btn_enabled(False) - self.soledad_ready.connect(lambda: btn_enabled(True)) - self._soledad_bootstrapper.soledad_failed.connect( - lambda: btn_enabled(True)) + sig = self._backend.signaler + sig.soledad_bootstrap_failed.connect(lambda: btn_enabled(True)) + sig.soledad_bootstrap_finished.connect(lambda: btn_enabled(True)) - if not self._get_best_provider_config().provides_mx(): + if not MX_SERVICE in self._provider_details.services: self._set_mx_visible(False) def _start_eip_bootstrap(self): @@ -1331,11 +1281,10 @@ class MainWindow(QtGui.QMainWindow): """ self._login_widget.logged_in() - provider = self._provider_config.get_domain() - self.ui.lblLoginProvider.setText(provider) + domain = self._login_widget.get_selected_provider() + self.ui.lblLoginProvider.setText(domain) - self._enabled_services = self._settings.get_enabled_services( - self._provider_config.get_domain()) + self._enabled_services = self._settings.get_enabled_services(domain) # TODO separate UI from logic. if self._provides_mx_and_enabled(): @@ -1345,6 +1294,30 @@ class MainWindow(QtGui.QMainWindow): self._maybe_start_eip() + @QtCore.Slot() + def _get_provider_details(self): + """ + TRIGGERS: + prov_check_api_certificate + + Set the attributes to know if the EIP and MX services are supported + and enabled. + This is triggered right after the provider has been set up. + """ + domain = self._login_widget.get_selected_provider() + lang = QtCore.QLocale.system().name() + self._backend.provider_get_details(domain, lang) + + @QtCore.Slot() + def _provider_get_details(self, details): + """ + Set the details for the just downloaded provider. + + :param details: the details of the provider. + :type details: ProviderConfigLight + """ + self._provider_details = details + def _provides_mx_and_enabled(self): """ Defines if the current provider provides mx and if we have it enabled. @@ -1352,9 +1325,15 @@ class MainWindow(QtGui.QMainWindow): :returns: True if provides and is enabled, False otherwise :rtype: bool """ - provider_config = self._get_best_provider_config() - return (provider_config.provides_mx() and - MX_SERVICE in self._enabled_services) + domain = self._login_widget.get_selected_provider() + enabled_services = self._settings.get_enabled_services(domain) + + mx_enabled = MX_SERVICE in enabled_services + mx_provided = False + if self._provider_details is not None: + mx_provided = MX_SERVICE in self._provider_details.services + + return mx_enabled and mx_provided def _provides_eip_and_enabled(self): """ @@ -1363,33 +1342,30 @@ class MainWindow(QtGui.QMainWindow): :returns: True if provides and is enabled, False otherwise :rtype: bool """ - provider_config = self._get_best_provider_config() - return (provider_config.provides_eip() and - EIP_SERVICE in self._enabled_services) + domain = self._login_widget.get_selected_provider() + enabled_services = self._settings.get_enabled_services(domain) + + eip_enabled = EIP_SERVICE in enabled_services + eip_provided = False + if self._provider_details is not None: + eip_provided = EIP_SERVICE in self._provider_details.services + + return eip_enabled and eip_provided def _maybe_run_soledad_setup_checks(self): """ Conditionally start Soledad. """ # TODO split. - if self._already_started_soledad is True: - return - - if not self._provides_mx_and_enabled(): + if not self._provides_mx_and_enabled() and not flags.OFFLINE: + logger.debug("Provider does not offer MX, but it is enabled.") return username = self._login_widget.get_user() password = unicode(self._login_widget.get_password()) provider_domain = self._login_widget.get_selected_provider() - sb = self._soledad_bootstrapper - if flags.OFFLINE is True: - provider_domain = self._login_widget.get_selected_provider() - sb._password = password - - self._provisional_provider_config.load( - provider.get_provider_path(provider_domain)) - + if flags.OFFLINE: full_user_id = make_address(username, provider_domain) uuid = self._settings.get_uuid(full_user_id) self._mail_conductor.userid = full_user_id @@ -1399,74 +1375,26 @@ class MainWindow(QtGui.QMainWindow): # this is mostly for internal use/debug for now. logger.warning("Sorry! Log-in at least one time.") return - fun = sb.load_offline_soledad - fun(full_user_id, password, uuid) + self._backend.soledad_load_offline(full_user_id, password, uuid) else: - provider_config = self._provider_config - if self._logged_user is not None: - self._soledad_defer = sb.run_soledad_setup_checks( - provider_config, username, password, - download_if_needed=True) + domain = self._login_widget.get_selected_provider() + self._backend.soledad_bootstrap(username, domain, password) ################################################################### # Service control methods: soledad - @QtCore.Slot(dict) - def _soledad_intermediate_stage(self, data): - # TODO missing param docstring - """ - TRIGGERS: - self._soledad_bootstrapper.download_config - - If there was a problem, displays it, otherwise it does nothing. - This is used for intermediate bootstrapping stages, in case - they fail. - """ - passed = data[self._soledad_bootstrapper.PASSED_KEY] - if not passed: - # TODO display in the GUI: - # should pass signal to a slot in status_panel - # that sets the global status - logger.error("Soledad failed to start: %s" % - (data[self._soledad_bootstrapper.ERROR_KEY],)) - - @QtCore.Slot(dict) - def _soledad_bootstrapped_stage(self, data): + @QtCore.Slot() + def _on_soledad_ready(self): """ TRIGGERS: - self._soledad_bootstrapper.gen_key - self._soledad_bootstrapper.local_only_ready - - If there was a problem, displays it, otherwise it does nothing. - This is used for intermediate bootstrapping stages, in case - they fail. + Signaler.soledad_bootstrap_finished - :param data: result from the bootstrapping stage for Soledad - :type data: dict + Actions to take when Soledad is ready. """ - passed = data[self._soledad_bootstrapper.PASSED_KEY] - if not passed: - # TODO should actually *display* on the panel. - logger.debug("ERROR on soledad bootstrapping:") - logger.error("%r" % data[self._soledad_bootstrapper.ERROR_KEY]) - return - logger.debug("Done bootstrapping Soledad") - # Update the proxy objects to point to - # the initialized instances. - setProxiedObject(self._soledad, - self._soledad_bootstrapper.soledad) - setProxiedObject(self._keymanager, - self._soledad_bootstrapper.keymanager) - - # Ok, now soledad is ready, so we can allow other things that - # depend on soledad to start. - self._soledad_defer = None - - # this will trigger start_imap_service - # and start_smtp_boostrapping + self._soledad_started = True self.soledad_ready.emit() ################################################################### @@ -1483,19 +1411,7 @@ class MainWindow(QtGui.QMainWindow): return if self._provides_mx_and_enabled(): - self._mail_conductor.start_smtp_service(self._provider_config, - download_if_needed=True) - - # XXX --- should remove from here, and connecte directly to the state - # machine. - @QtCore.Slot() - def _stop_smtp_service(self): - """ - TRIGGERS: - self.logout - """ - # TODO call stop_mail_service - self._mail_conductor.stop_smtp_service() + self._mail_conductor.start_smtp_service(download_if_needed=True) ################################################################### # Service control methods: imap @@ -1509,69 +1425,14 @@ class MainWindow(QtGui.QMainWindow): # TODO in the OFFLINE mode we should also modify the rules # in the mail state machine so it shows that imap is active # (but not smtp since it's not yet ready for offline use) - start_fun = self._mail_conductor.start_imap_service - if flags.OFFLINE is True: - provider_domain = self._login_widget.get_selected_provider() - self._provider_config.load( - provider.get_provider_path(provider_domain)) - provides_mx = self._provider_config.provides_mx() - - if flags.OFFLINE is True and provides_mx: - start_fun() - return - - if self._provides_mx_and_enabled(): - start_fun() - - def _on_mail_client_logged_in(self, req): - """ - Triggers qt signal when client login event is received. - """ - self.mail_client_logged_in.emit() - - @QtCore.Slot() - def _fetch_incoming_mail(self): - """ - TRIGGERS: - self.mail_client_logged_in - """ - # TODO connect signal directly!!! - self._mail_conductor.fetch_incoming_mail() - - @QtCore.Slot() - def _stop_imap_service(self): - """ - TRIGGERS: - self.logout - """ - cv = Condition() - cv.acquire() - # TODO call stop_mail_service - threads.deferToThread(self._mail_conductor.stop_imap_service, cv) - # and wait for it to be stopped - logger.debug('Waiting for imap service to stop.') - cv.wait(self.SERVICE_STOP_TIMEOUT) + if self._provides_mx_and_enabled() or flags.OFFLINE: + self._mail_conductor.start_imap_service() # end service control methods (imap) ################################################################### # Service control methods: eip - def start_eip_machine(self): - """ - Initializes and starts the EIP state machine - """ - button = self._eip_status.eip_button - action = self._action_eip_startstop - label = self._eip_status.eip_label - builder = statemachines.ConnectionMachineBuilder(self._eip_connection) - eip_machine = builder.make_machine(button=button, - action=action, - label=label) - self.eip_machine = eip_machine - self.eip_machine.start() - logger.debug('eip machine started') - @QtCore.Slot() def _disable_eip_start_action(self): """ @@ -1585,32 +1446,32 @@ class MainWindow(QtGui.QMainWindow): Enables the EIP start action in the systray menu. """ self._action_eip_startstop.setEnabled(True) + self._eip_status.enable_eip_start() @QtCore.Slot() def _on_eip_connection_connected(self): """ TRIGGERS: - self._eip_status.eip_connection_connected - - Emits the EIPConnection.qtsigs.connected_signal + self._eip_conductor.qtsigs.connected_signal This is a little workaround for connecting the vpn-connected signal that currently is beeing processed under status_panel. After the refactor to EIPConductor this should not be necessary. """ - self._eip_connection.qtsigs.connected_signal.emit() - - provider_config = self._get_best_provider_config() - domain = provider_config.get_domain() + domain = self._login_widget.get_selected_provider() self._eip_status.set_provider(domain) self._settings.set_defaultprovider(domain) self._already_started_eip = True # check for connectivity + # we might want to leave a little time here... self._check_name_resolution(domain) def _check_name_resolution(self, domain): + # FIXME this has to be moved to backend !!! + # Should move to netchecks module. + # and separate qt from reactor... """ Check if we can resolve the given domain name. @@ -1641,7 +1502,7 @@ class MainWindow(QtGui.QMainWindow): "missing some helper files that are needed to securely use " "DNS while {1} is active. To install these helper files, quit " "this application and start it again." - ).format(domain, self._eip_name) + ).format(domain, self._eip_conductor.eip_name) show_err = lambda: QtGui.QMessageBox.critical( self, self.tr("Connection Error"), msg) @@ -1663,245 +1524,42 @@ class MainWindow(QtGui.QMainWindow): self._enabled_services = settings.get_enabled_services( default_provider) - loaded = self._provisional_provider_config.load( - provider.get_provider_path(default_provider)) - if loaded and settings.get_autostart_eip(): - # XXX I think we should not try to re-download config every time, - # it adds some delay. - # Maybe if it's the first run in a session, - # or we can try only if it fails. - self._maybe_start_eip() - elif settings.get_autostart_eip(): - # XXX: Display a proper message to the user - self.eip_needs_login.emit() - logger.error("Unable to load %s config, cannot autostart." % - (default_provider,)) - - @QtCore.Slot() - def _start_EIP(self): - """ - Starts EIP - """ - self._eip_status.eip_pre_up() - self.user_stopped_eip = False - - # Until we set an option in the preferences window, we'll assume that - # by default we try to autostart. If we switch it off manually, it - # won't try the next time. - self._settings.set_autostart_eip(True) - - self._backend.start_eip() - - @QtCore.Slot() - def _on_eip_connection_aborted(self): - """ - TRIGGERS: - Signaler.eip_connection_aborted - """ - logger.error("Tried to start EIP but cannot find any " - "available provider!") - - eip_status_label = self.tr("Could not load {0} configuration.") - eip_status_label = eip_status_label.format(self._eip_name) - self._eip_status.set_eip_status(eip_status_label, error=True) - - # signal connection_aborted to state machine: - qtsigs = self._eip_connection.qtsigs - qtsigs.connection_aborted_signal.emit() - - def _on_eip_openvpn_already_running(self): - self._eip_status.set_eip_status( - self.tr("Another openvpn instance is already running, and " - "could not be stopped."), - error=True) - self._set_eipstatus_off() - - def _on_eip_alien_openvpn_already_running(self): - self._eip_status.set_eip_status( - self.tr("Another openvpn instance is already running, and " - "could not be stopped because it was not launched by " - "Bitmask. Please stop it and try again."), - error=True) - self._set_eipstatus_off() - - def _on_eip_openvpn_not_found_error(self): - self._eip_status.set_eip_status( - self.tr("We could not find openvpn binary."), - error=True) - self._set_eipstatus_off() - - def _on_eip_vpn_launcher_exception(self): - # XXX We should implement again translatable exceptions so - # we can pass a translatable string to the panel (usermessage attr) - self._eip_status.set_eip_status("VPN Launcher error.", error=True) - self._set_eipstatus_off() - - def _on_eip_no_polkit_agent_error(self): - self._eip_status.set_eip_status( - # XXX this should change to polkit-kde where - # applicable. - self.tr("We could not find any authentication agent in your " - "system.<br/>Make sure you have" - "<b>polkit-gnome-authentication-agent-1</b> running and" - "try again."), - error=True) - self._set_eipstatus_off() - - def _on_eip_no_pkexec_error(self): - self._eip_status.set_eip_status( - self.tr("We could not find <b>pkexec</b> in your system."), - error=True) - self._set_eipstatus_off() - - def _on_eip_no_tun_kext_error(self): - self._eip_status.set_eip_status( - self.tr("{0} cannot be started because the tuntap extension is " - "not installed properly in your " - "system.").format(self._eip_name)) - self._set_eipstatus_off() - - @QtCore.Slot() - def _stop_eip(self): - """ - TRIGGERS: - self._eip_connection.qtsigs.do_disconnect_signal (via state machine) - - Stops vpn process and makes gui adjustments to reflect - the change of state. - - :param abnormal: whether this was an abnormal termination. - :type abnormal: bool - """ - self.user_stopped_eip = True - self._backend.stop_eip() - - self._set_eipstatus_off(False) - self._already_started_eip = False - - logger.debug('Setting autostart to: False') - self._settings.set_autostart_eip(False) - - if self._logged_user: - self._eip_status.set_provider( - make_address( - self._logged_user, - self._get_best_provider_config().get_domain())) - self._eip_status.eip_stopped() - - @QtCore.Slot() - def _on_eip_network_unreachable(self): - # XXX Should move to EIP Conductor - """ - TRIGGERS: - self._eip_connection.qtsigs.network_unreachable - - Displays a "network unreachable" error in the EIP status panel. - """ - self._eip_status.set_eip_status(self.tr("Network is unreachable"), - error=True) - self._eip_status.set_eip_status_icon("error") - - @QtCore.Slot() - def _do_eip_restart(self): - # XXX Should move to EIP Conductor - """ - TRIGGERS: - self._eip_connection.qtsigs.process_restart - - Restart the connection. - """ - # for some reason, emitting the do_disconnect/do_connect - # signals hangs the UI. - self._stop_eip() - QtCore.QTimer.singleShot(2000, self._start_EIP) - - def _set_eipstatus_off(self, error=True): - """ - Sets eip status to off - """ - # XXX this should be handled by the state machine. - self._eip_status.set_eip_status("", error=error) - self._eip_status.set_eip_status_icon("error") - - @QtCore.Slot(int) - def _eip_finished(self, exitCode): - """ - TRIGGERS: - Signaler.eip_process_finished - - Triggered when the EIP/VPN process finishes to set the UI - accordingly. - - Ideally we would have the right exit code here, - but the use of different wrappers (pkexec, cocoasudo) swallows - the openvpn exit code so we get zero exit in some cases where we - shouldn't. As a workaround we just use a flag to indicate - a purposeful switch off, and mark everything else as unexpected. - - In the near future we should trigger a native notification from here, - since the user really really wants to know she is unprotected asap. - And the right thing to do will be to fail-close. - - :param exitCode: the exit code of the eip process. - :type exitCode: int - """ - # TODO move to EIPConductor. - # TODO Add error catching to the openvpn log observer - # so we can have a more precise idea of which type - # of error did we have (server side, local problem, etc) - - logger.info("VPN process finished with exitCode %s..." - % (exitCode,)) - - qtsigs = self._eip_connection.qtsigs - signal = qtsigs.disconnected_signal - - # XXX check if these exitCodes are pkexec/cocoasudo specific - if exitCode in (126, 127): - eip_status_label = self.tr( - "{0} could not be launched " - "because you did not authenticate properly.") - eip_status_label = eip_status_label.format(self._eip_name) - self._eip_status.set_eip_status(eip_status_label, error=True) - signal = qtsigs.connection_aborted_signal - self._backend.terminate_eip() - - elif exitCode != 0 or not self.user_stopped_eip: - eip_status_label = self.tr("{0} finished in an unexpected manner!") - eip_status_label = eip_status_label.format(self._eip_name) - self._eip_status.eip_stopped() - self._eip_status.set_eip_status_icon("error") - self._eip_status.set_eip_status(eip_status_label, error=True) - signal = qtsigs.connection_died_signal - - if exitCode == 0 and IS_MAC: - # XXX remove this warning after I fix cocoasudo. - logger.warning("The above exit code MIGHT BE WRONG.") - - # We emit signals to trigger transitions in the state machine: - signal.emit() + if settings.get_autostart_eip(): + self._maybe_start_eip(autostart=True) # eip boostrapping, config etc... - def _maybe_start_eip(self): + def _maybe_start_eip(self, autostart=False): """ Start the EIP bootstrapping sequence if the client is configured to do so. + + :param autostart: we are autostarting EIP when this is True + :type autostart: bool """ - if self._provides_eip_and_enabled() and not self._already_started_eip: + # during autostart we assume that the provider provides EIP + if autostart: + should_start = EIP_SERVICE in self._enabled_services + else: + should_start = self._provides_eip_and_enabled() + + if should_start and not self._already_started_eip: + if self._eip_status.is_cold_start: + self._backend.tear_fw_down() # XXX this should be handled by the state machine. + self._enable_eip_start_action() self._eip_status.set_eip_status( self.tr("Starting...")) + self._eip_status.eip_button.setEnabled(False) domain = self._login_widget.get_selected_provider() - self._backend.setup_eip(domain) + self._backend.eip_setup(domain) self._already_started_eip = True # we want to start soledad anyway after a certain timeout if eip # fails to come up - QtCore.QTimer.singleShot( - self.EIP_TIMEOUT, - self._maybe_run_soledad_setup_checks) + QtDelayedCall(self.EIP_START_TIMEOUT, + self._maybe_run_soledad_setup_checks) else: if not self._already_started_eip: if EIP_SERVICE in self._enabled_services: @@ -1920,8 +1578,8 @@ class MainWindow(QtGui.QMainWindow): TRIGGERS: self._backend.signaler.eip_client_certificate_ready - Starts the VPN thread if the eip configuration is properly - loaded + Start the VPN thread if the eip configuration is properly + loaded. """ passed = data[self._backend.PASSED_KEY] @@ -1933,11 +1591,11 @@ class MainWindow(QtGui.QMainWindow): return # DO START EIP Connection! - self._eip_connection.qtsigs.do_connect_signal.emit() + self._eip_conductor.do_connect() @QtCore.Slot(dict) def _eip_intermediate_stage(self, data): - # TODO missing param + # TODO missing param documentation """ TRIGGERS: self._backend.signaler.eip_config_ready @@ -1952,33 +1610,10 @@ class MainWindow(QtGui.QMainWindow): self.tr("Unable to connect: Problem with provider")) logger.error(data[self._backend.ERROR_KEY]) self._already_started_eip = False + self._eip_status.aborted() # end of EIP methods --------------------------------------------- - def _get_best_provider_config(self): - """ - Returns the best ProviderConfig to use at a moment. We may - have to use self._provider_config or - self._provisional_provider_config depending on the start - status. - - :rtype: ProviderConfig - """ - # TODO move this out of gui. - leap_assert(self._provider_config is not None or - self._provisional_provider_config is not None, - "We need a provider config") - - provider_config = None - if self._provider_config.loaded(): - provider_config = self._provider_config - elif self._provisional_provider_config.loaded(): - provider_config = self._provisional_provider_config - else: - leap_assert(False, "We could not find any usable ProviderConfig.") - - return provider_config - @QtCore.Slot() def _logout(self): """ @@ -1987,16 +1622,11 @@ class MainWindow(QtGui.QMainWindow): Starts the logout sequence """ - setProxiedObject(self._soledad, None) - self._cancel_ongoing_defers() - # reset soledad status flag - self._already_started_soledad = False - # XXX: If other defers are doing authenticated stuff, this # might conflict with those. CHECK! - self._backend.logout() + self._backend.user_logout() self.logout.emit() @QtCore.Slot() @@ -2080,59 +1710,37 @@ class MainWindow(QtGui.QMainWindow): # cleanup and quit methods # - def _cleanup_pidfiles(self): - """ - Removes lockfiles on a clean shutdown. - - Triggered after aboutToQuit signal. + def _stop_services(self): """ - if IS_WIN: - WindowsLock.release_all_locks() - - def _cleanup_and_quit(self): - """ - Call all the cleanup actions in a serialized way. - Should be called from the quit function. + Stop services and cancel ongoing actions (if any). """ - logger.debug('About to quit, doing cleanup...') - - self._stop_imap_service() - - if self._logged_user is not None: - self._backend.logout() + logger.debug('About to quit, doing cleanup.') - if self._soledad_bootstrapper.soledad is not None: - logger.debug("Closing soledad...") - self._soledad_bootstrapper.soledad.close() - else: - logger.error("No instance of soledad was found.") + self._cancel_ongoing_defers() - logger.debug('Terminating vpn') - self._backend.stop_eip(shutdown=True) + self._services_being_stopped = {'imap', 'eip'} - # We need to give some time to the ongoing signals for shutdown - # to come into action. This needs to be solved using - # back-communication from backend. - QtCore.QTimer.singleShot(3000, self._shutdown) + imap_stopped = lambda: self._remove_service('imap') + self._backend.signaler.imap_stopped.connect(imap_stopped) - def _shutdown(self): - """ - Actually shutdown. - """ - self._cancel_ongoing_defers() + eip_stopped = lambda: self._remove_service('eip') + self._backend.signaler.eip_stopped.connect(eip_stopped) - # TODO missing any more cancels? + logger.debug('Stopping mail services') + self._backend.imap_stop_service() + self._backend.smtp_stop_service() - logger.debug('Cleaning pidfiles') - self._cleanup_pidfiles() - if self._quit_callback: - self._quit_callback() + if self._logged_user is not None: + logger.debug("Doing logout") + self._backend.user_logout() - logger.debug('Bye.') + logger.debug('Terminating vpn') + self._backend.eip_stop(shutdown=True) def quit(self): """ - Cleanup and tidely close the main window before quitting. + Start the quit sequence and wait for services to finish. + Cleanup and close the main window before quitting. """ # TODO separate the shutting down of services from the # UI stuff. @@ -2142,25 +1750,72 @@ class MainWindow(QtGui.QMainWindow): if self._systray is not None: self._systray.showMessage( self.tr('Quitting...'), - self.tr('The app is quitting, please wait.')) + self.tr('Bitmask is quitting, please wait.')) # explicitly process events to display tooltip immediately - QtCore.QCoreApplication.processEvents() + QtCore.QCoreApplication.processEvents(0, 10) + + # Close other windows if any. + if self._wizard: + self._wizard.close() + + if self._logger_window: + self._logger_window.close() # Set this in case that the app is hidden QtGui.QApplication.setQuitOnLastWindowClosed(True) - self._cleanup_and_quit() + self._stop_services() - # We queue the call to stop since we need to wait until EIP is stopped. - # Otherwise we may exit leaving an unmanaged openvpn process. - reactor.callLater(0, self._backend.stop) self._really_quit = True - if self._wizard: - self._wizard.close() + # call final quit when all the services are stopped + self.all_services_stopped.connect(self.final_quit) + # or if we reach the timeout + self._quit_timeout_callater = reactor.callLater( + self.SERVICES_STOP_TIMEOUT, self.final_quit) - if self._logger_window: - self._logger_window.close() + @QtCore.Slot() + def _remove_service(self, service): + """ + Remove the given service from the waiting list and check if we have + running services that we need to wait until we quit. + Emit self.all_services_stopped signal if we don't need to keep waiting. + :param service: the service that we want to remove + :type service: str + """ + self._services_being_stopped.discard(service) + + if not self._services_being_stopped: + logger.debug("All services stopped.") + self.all_services_stopped.emit() + + @QtCore.Slot() + def final_quit(self): + """ + Final steps to quit the app, starting from here we don't care about + running services or user interaction, just quitting. + """ + logger.debug('Final quit...') + + try: + # disconnect signal if we get here due a timeout. + self.all_services_stopped.disconnect(self.final_quit) + except RuntimeError: + pass # Signal was not connected + + # Cancel timeout to avoid being called if we reached here through the + # signal + if self._quit_timeout_callater.active(): + self._quit_timeout_callater.cancel() + + # Remove lockfiles on a clean shutdown. + logger.debug('Cleaning pidfiles') + if IS_WIN: + WindowsLock.release_all_locks() + + self._backend.stop() self.close() + + reactor.callLater(1, self._quit_callback) diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py index 2947c5db..a3b81d38 100644 --- a/src/leap/bitmask/gui/preferenceswindow.py +++ b/src/leap/bitmask/gui/preferenceswindow.py @@ -23,15 +23,10 @@ import logging from functools import partial from PySide import QtCore, QtGui -from zope.proxy import sameProxiedObjects -from leap.bitmask.provider import get_provider_path from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.gui.ui_preferences import Ui_Preferences -from leap.soledad.client import NoStorageSecret -from leap.bitmask.util.password import basic_password_checks -from leap.bitmask.services import get_supported -from leap.bitmask.config.providerconfig import ProviderConfig +from leap.bitmask.util.credentials import password_checks from leap.bitmask.services import get_service_display_name, MX_SERVICE logger = logging.getLogger(__name__) @@ -43,32 +38,31 @@ class PreferencesWindow(QtGui.QDialog): """ preferences_saved = QtCore.Signal() - def __init__(self, parent, backend, provider_config, - soledad, username, domain): + def __init__(self, parent, username, domain, backend, soledad_started, mx): """ :param parent: parent object of the PreferencesWindow. :parent type: QWidget - :param backend: Backend being used - :type backend: Backend - :param provider_config: ProviderConfig object. - :type provider_config: ProviderConfig - :param soledad: Soledad instance - :type soledad: Soledad :param username: the user set in the login widget :type username: unicode :param domain: the selected domain in the login widget :type domain: unicode + :param backend: Backend being used + :type backend: Backend + :param soledad_started: whether soledad has started or not + :type soledad_started: bool + :param mx: whether the current provider provides mx or not. + :type mx: bool """ QtGui.QDialog.__init__(self, parent) self.AUTOMATIC_GATEWAY_LABEL = self.tr("Automatic") - self._backend = backend - self._settings = LeapSettings() - self._soledad = soledad - self._provider_config = provider_config self._username = username self._domain = domain + self._backend = backend + self._soledad_started = soledad_started + self._mx_provided = mx + self._settings = LeapSettings() self._backend_connect() # Load UI @@ -89,50 +83,17 @@ class PreferencesWindow(QtGui.QDialog): else: self._add_configured_providers() - self._backend.get_logged_in_status() + if self._username is None: + self._not_logged_in() + else: + self.ui.gbPasswordChange.setEnabled(True) + if self._mx_provided: + self._provides_mx() self._select_provider_by_name(domain) - @QtCore.Slot() - def _is_logged_in(self): - """ - TRIGGERS: - Signaler.srp_status_logged_in - - Actions to perform is the user is logged in. - """ - settings = self._settings - pw_enabled = True - - # check if provider has 'mx' ... - # TODO: we should move this to the backend. - if self._provider_config.provides_mx(): - enabled_services = settings.get_enabled_services(self._domain) - mx_name = get_service_display_name(MX_SERVICE) - - # ... and if the user have it enabled - if MX_SERVICE not in enabled_services: - msg = self.tr("You need to enable {0} in order to change " - "the password.".format(mx_name)) - self._set_password_change_status(msg, error=True) - pw_enabled = False - else: - # check if Soledad is bootstrapped - if sameProxiedObjects(self._soledad, None): - msg = self.tr( - "You need to wait until {0} is ready in " - "order to change the password.".format(mx_name)) - self._set_password_change_status(msg) - pw_enabled = False - - self.ui.gbPasswordChange.setEnabled(pw_enabled) - - @QtCore.Slot() def _not_logged_in(self): """ - TRIGGERS: - Signaler.srp_status_not_logged_in - Actions to perform if the user is not logged in. """ msg = self.tr( @@ -140,6 +101,30 @@ class PreferencesWindow(QtGui.QDialog): self._set_password_change_status(msg) self.ui.gbPasswordChange.setEnabled(False) + def _provides_mx(self): + """ + Actions to perform if the provider provides MX. + """ + pw_enabled = True + enabled_services = self._settings.get_enabled_services(self._domain) + mx_name = get_service_display_name(MX_SERVICE) + + if MX_SERVICE not in enabled_services: + msg = self.tr("You need to enable {0} in order to change " + "the password.".format(mx_name)) + self._set_password_change_status(msg, error=True) + pw_enabled = False + else: + # check if Soledad is bootstrapped + if not self._soledad_started: + msg = self.tr( + "You need to wait until {0} is ready in " + "order to change the password.".format(mx_name)) + self._set_password_change_status(msg) + pw_enabled = False + + self.ui.gbPasswordChange.setEnabled(pw_enabled) + @QtCore.Slot() def set_soledad_ready(self): """ @@ -200,7 +185,7 @@ class PreferencesWindow(QtGui.QDialog): new_password = self.ui.leNewPassword.text() new_password2 = self.ui.leNewPassword2.text() - ok, msg = basic_password_checks(username, new_password, new_password2) + ok, msg = password_checks(username, new_password, new_password2) if not ok: self._set_changing_password(False) @@ -209,10 +194,10 @@ class PreferencesWindow(QtGui.QDialog): return self._set_changing_password(True) - self._backend.change_password(current_password, new_password) + self._backend.user_change_password(current_password, new_password) @QtCore.Slot() - def _change_password_ok(self): + def _srp_change_password_ok(self): """ TRIGGERS: self._backend.signaler.srp_password_change_ok @@ -221,12 +206,44 @@ class PreferencesWindow(QtGui.QDialog): """ new_password = self.ui.leNewPassword.text() logger.debug("SRP password changed successfully.") - try: - self._soledad.change_passphrase(new_password) - logger.debug("Soledad password changed successfully.") - except NoStorageSecret: - logger.debug( - "No storage secret for password change in Soledad.") + + if self._mx_provided: + self._backend.soledad_change_password(new_password) + else: + self._change_password_success() + + @QtCore.Slot(unicode) + def _srp_change_password_problem(self, msg): + """ + TRIGGERS: + self._backend.signaler.srp_password_change_error + self._backend.signaler.srp_password_change_badpw + + Callback used to display an error on changing password. + + :param msg: the message to show to the user. + :type msg: unicode + """ + logger.error("Error changing password") + self._set_password_change_status(msg, error=True) + self._set_changing_password(False) + + @QtCore.Slot() + def _soledad_change_password_ok(self): + """ + TRIGGERS: + Signaler.soledad_password_change_ok + + Soledad password change went OK. + """ + logger.debug("Soledad password changed successfully.") + self._change_password_success() + + def _change_password_success(self): + """ + Callback used to display a successfully changed password. + """ + logger.debug("Soledad password changed successfully.") self._set_password_change_status( self.tr("Password changed successfully."), success=True) @@ -234,18 +251,17 @@ class PreferencesWindow(QtGui.QDialog): self._set_changing_password(False) @QtCore.Slot(unicode) - def _change_password_problem(self, msg): + def _soledad_change_password_problem(self, msg): """ TRIGGERS: - self._backend.signaler.srp_password_change_error - self._backend.signaler.srp_password_change_badpw + Signaler.soledad_password_change_error Callback used to display an error on changing password. :param msg: the message to show to the user. :type msg: unicode """ - logger.error("Error changing password") + logger.error("Error changing soledad password") self._set_password_change_status(msg, error=True) self._set_changing_password(False) @@ -321,8 +337,7 @@ class PreferencesWindow(QtGui.QDialog): TRIGGERS: self.ui.cbProvidersServices.currentIndexChanged[unicode] - Loads the services that the provider provides into the UI for - the user to enable or disable. + Fill the services list with the selected provider's services. :param domain: the domain of the provider to load services from. :type domain: str @@ -333,10 +348,6 @@ class PreferencesWindow(QtGui.QDialog): if not domain: return - provider_config = self._get_provider_config(domain) - if provider_config is None: - return - # set the proper connection for the 'save' button try: self.ui.pbSaveServices.clicked.disconnect() @@ -346,7 +357,21 @@ class PreferencesWindow(QtGui.QDialog): save_services = partial(self._save_enabled_services, domain) self.ui.pbSaveServices.clicked.connect(save_services) - services = get_supported(provider_config.get_services()) + self._backend.provider_get_supported_services(domain) + + @QtCore.Slot(str) + def _load_services(self, services): + """ + TRIGGERS: + self.ui.cbProvidersServices.currentIndexChanged[unicode] + + Loads the services that the provider provides into the UI for + the user to enable or disable. + + :param domain: the domain of the provider to load services from. + :type domain: str + """ + domain = self.ui.cbProvidersServices.currentText() services_conf = self._settings.get_enabled_services(domain) # discard changes if other provider is selected @@ -394,36 +419,26 @@ class PreferencesWindow(QtGui.QDialog): self._set_providers_services_status(msg, success=True) self.preferences_saved.emit() - def _get_provider_config(self, domain): - """ - Helper to return a valid Provider Config from the domain name. - - :param domain: the domain name of the provider. - :type domain: str - - :rtype: ProviderConfig or None if there is a problem loading the config - """ - provider_config = ProviderConfig() - if not provider_config.load(get_provider_path(domain)): - provider_config = None - - return provider_config - def _backend_connect(self): """ Helper to connect to backend signals """ sig = self._backend.signaler - sig.srp_status_logged_in.connect(self._is_logged_in) - sig.srp_status_not_logged_in.connect(self._not_logged_in) + sig.prov_get_supported_services.connect(self._load_services) - sig.srp_password_change_ok.connect(self._change_password_ok) + sig.srp_password_change_ok.connect(self._srp_change_password_ok) - pwd_change_error = lambda: self._change_password_problem( + pwd_change_error = lambda: self._srp_change_password_problem( self.tr("There was a problem changing the password.")) sig.srp_password_change_error.connect(pwd_change_error) - pwd_change_badpw = lambda: self._change_password_problem( + pwd_change_badpw = lambda: self._srp_change_password_problem( self.tr("You did not enter a correct current password.")) sig.srp_password_change_badpw.connect(pwd_change_badpw) + + sig.soledad_password_change_ok.connect( + self._soledad_change_password_ok) + + sig.soledad_password_change_error.connect( + self._soledad_change_password_problem) diff --git a/src/leap/bitmask/gui/statemachines.py b/src/leap/bitmask/gui/statemachines.py index 31938a70..00a1387e 100644 --- a/src/leap/bitmask/gui/statemachines.py +++ b/src/leap/bitmask/gui/statemachines.py @@ -504,6 +504,11 @@ class ConnectionMachineBuilder(object): conn.qtsigs.connection_died_signal, states[_OFF]) + # XXX adding this--------------------- + states[_ON].addTransition( + conn.qtsigs.do_disconnect_signal, + states[_DIS]) + # * If we receive the connection_aborted, we transition # from connecting to the off state states[_CON].addTransition( @@ -551,7 +556,8 @@ class ConnectionMachineBuilder(object): # TODO add tooltip # OFF State ---------------------- - off = QState() + off = SignallingState( + None, name=conn.name) off_label = _tr("Turn {0}").format( conn.Connected.short_label) if button: @@ -559,11 +565,15 @@ class ConnectionMachineBuilder(object): button, 'text', off_label) off.assignProperty( button, 'enabled', True) + off.assignProperty( + button, 'visible', True) if action: off.assignProperty( action, 'text', off_label) off.assignProperty( action, 'enabled', True) + off.assignProperty( + action, 'visible', True) off.setObjectName(_OFF) states[_OFF] = off @@ -587,7 +597,10 @@ class ConnectionMachineBuilder(object): states[_CON] = connecting # ON State ------------------------ - on = QState() + on = SignallingState( + None, name=conn.name) + on_label = _tr("Turn {0}").format( + conn.Disconnected.short_label) if button: on.assignProperty( button, 'text', on_label) diff --git a/src/leap/bitmask/gui/ui/eip_status.ui b/src/leap/bitmask/gui/ui/eip_status.ui index 64821ad6..7216bb0a 100644 --- a/src/leap/bitmask/gui/ui/eip_status.ui +++ b/src/leap/bitmask/gui/ui/eip_status.ui @@ -28,7 +28,7 @@ <property name="verticalSpacing"> <number>0</number> </property> - <item row="0" column="2"> + <item row="0" column="4"> <widget class="QPushButton" name="btnEipStartStop"> <property name="text"> <string>Turn On</string> @@ -51,7 +51,7 @@ </property> </widget> </item> - <item row="3" column="1"> + <item row="3" column="2"> <widget class="QLabel" name="lblEIPStatus"> <property name="maximumSize"> <size> @@ -70,7 +70,7 @@ </property> </widget> </item> - <item row="0" column="1"> + <item row="0" column="2"> <widget class="QLabel" name="lblEIPMessage"> <property name="sizePolicy"> <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> @@ -86,7 +86,7 @@ </property> </widget> </item> - <item row="0" column="3"> + <item row="0" column="5"> <widget class="QLabel" name="lblVPNStatusIcon"> <property name="maximumSize"> <size> @@ -105,7 +105,7 @@ </property> </widget> </item> - <item row="1" column="1"> + <item row="1" column="2"> <spacer name="horizontalSpacer"> <property name="orientation"> <enum>Qt::Horizontal</enum> @@ -118,7 +118,7 @@ </property> </spacer> </item> - <item row="2" column="1" colspan="3"> + <item row="2" column="2" colspan="4"> <widget class="QWidget" name="eip_bandwidth" native="true"> <layout class="QHBoxLayout" name="horizontalLayout"> <property name="spacing"> @@ -161,12 +161,13 @@ <property name="text"> <string>0.0 KB/s</string> </property> + <property name="icon"> + <iconset resource="../../../../../data/resources/mainwindow.qrc"> + <normaloff>:/images/black/32/arrow-down.png</normaloff>:/images/black/32/arrow-down.png</iconset> + </property> <property name="flat"> <bool>true</bool> </property> - <property name="icon"> - <pixmap resource="../../../../../data/resources/icons.qrc">:/images/light/16/down-arrow.png</pixmap> - </property> </widget> </item> <item> @@ -211,12 +212,13 @@ <property name="text"> <string>0.0 KB/s</string> </property> + <property name="icon"> + <iconset resource="../../../../../data/resources/mainwindow.qrc"> + <normaloff>:/images/black/32/arrow-up.png</normaloff>:/images/black/32/arrow-up.png</iconset> + </property> <property name="flat"> <bool>true</bool> </property> - <property name="icon"> - <pixmap resource="../../../../../data/resources/icons.qrc">:/images/light/16/up-arrow.png</pixmap> - </property> </widget> </item> <item> @@ -237,6 +239,20 @@ </layout> </widget> </item> + <item row="0" column="3"> + <widget class="QPushButton" name="btnFwDown"> + <property name="text"> + <string>Turn Off</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="lblGatewayCountryCode"> + <property name="text"> + <string/> + </property> + </widget> + </item> </layout> </item> </layout> diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index 020a58e2..4d774907 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -26,11 +26,10 @@ from PySide import QtCore, QtGui from leap.bitmask.config import flags from leap.bitmask.config.leapsettings import LeapSettings -from leap.bitmask.config.providerconfig import ProviderConfig -from leap.bitmask.provider import get_provider_path from leap.bitmask.services import get_service_display_name, get_supported +from leap.bitmask.util.credentials import password_checks, username_checks +from leap.bitmask.util.credentials import USERNAME_REGEX from leap.bitmask.util.keyring_helpers import has_keyring -from leap.bitmask.util.password import basic_password_checks from ui_wizard import Ui_Wizard @@ -49,8 +48,6 @@ class Wizard(QtGui.QWizard): REGISTER_USER_PAGE = 4 SERVICES_PAGE = 5 - BARE_USERNAME_REGEX = r"^[A-Za-z\d_]+$" - def __init__(self, backend, bypass_checks=False): """ Constructor for the main Wizard. @@ -89,10 +86,9 @@ class Wizard(QtGui.QWizard): self._backend_connect() self._domain = None - # HACK!! We need provider_config for the time being, it'll be - # removed - self._provider_config = ( - self._backend._components["provider"]._provider_config) + + # this details are set when the provider download is complete. + self._provider_details = None # We will store a reference to the defers for eventual use # (eg, to cancel them) but not doing anything with them right now. @@ -118,7 +114,7 @@ class Wizard(QtGui.QWizard): self.ui.rbExistingProvider.toggled.connect(self._skip_provider_checks) - usernameRe = QtCore.QRegExp(self.BARE_USERNAME_REGEX) + usernameRe = QtCore.QRegExp(USERNAME_REGEX) self.ui.lblUser.setValidator( QtGui.QRegExpValidator(usernameRe, self)) @@ -231,6 +227,12 @@ class Wizard(QtGui.QWizard): if reset: self._reset_provider_check() + def _focus_username(self): + """ + Focus at the username lineedit for the registration page + """ + self.ui.lblUser.setFocus() + def _focus_password(self): """ Focuses at the password lineedit for the registration page @@ -253,16 +255,22 @@ class Wizard(QtGui.QWizard): password = self.ui.lblPassword.text() password2 = self.ui.lblPassword2.text() - ok, msg = basic_password_checks(username, password, password2) - if ok: + user_ok, msg = username_checks(username) + if user_ok: + pass_ok, msg = password_checks(username, password, password2) + + if user_ok and pass_ok: self._set_register_status(self.tr("Starting registration...")) - self._backend.register_user(self._domain, username, password) + self._backend.user_register(self._domain, username, password) self._username = username self._password = password else: + if user_ok: + self._focus_password() + else: + self._focus_username() self._set_register_status(msg, error=True) - self._focus_password() self.ui.btnRegister.setEnabled(True) def _set_registration_fields_visibility(self, visible): @@ -406,7 +414,7 @@ class Wizard(QtGui.QWizard): self.ui.lblNameResolution.setPixmap(self.QUESTION_ICON) self._provider_select_defer = self._backend.\ - setup_provider(self._domain) + provider_setup(self._domain) @QtCore.Slot(bool) def _skip_provider_checks(self, skip): @@ -502,10 +510,12 @@ class Wizard(QtGui.QWizard): check. Since this check is the last of this set, it also completes the page if passed """ - if self._provider_config.load(get_provider_path(self._domain)): + if data[self._backend.PASSED_KEY]: self._complete_task(data, self.ui.lblProviderInfo, True, self.SELECT_PROVIDER_PAGE) self._provider_checks_ok = True + lang = QtCore.QLocale.system().name() + self._backend.provider_get_details(self._domain, lang) else: new_data = { self._backend.PASSED_KEY: False, @@ -527,6 +537,16 @@ class Wizard(QtGui.QWizard): else: self.ui.cbProviders.setEnabled(True) + @QtCore.Slot() + def _provider_get_details(self, details): + """ + Set the details for the just downloaded provider. + + :param details: the details of the provider. + :type details: ProviderConfigLight + """ + self._provider_details = details + @QtCore.Slot(dict) def _download_ca_cert(self, data): """ @@ -594,11 +614,9 @@ class Wizard(QtGui.QWizard): the user to enable or disable. """ self.ui.grpServices.setTitle( - self.tr("Services by %s") % - (self._provider_config.get_name(),)) + self.tr("Services by {0}").format(self._provider_details.name)) - services = get_supported( - self._provider_config.get_services()) + services = get_supported(self._provider_details.services) for service in services: try: @@ -641,38 +659,31 @@ class Wizard(QtGui.QWizard): if not self._provider_setup_ok: self._reset_provider_setup() sub_title = self.tr("Gathering configuration options for {0}") - sub_title = sub_title.format(self._provider_config.get_name()) + sub_title = sub_title.format(self._provider_details.name) self.page(pageId).setSubTitle(sub_title) self.ui.lblDownloadCaCert.setPixmap(self.QUESTION_ICON) self._provider_setup_defer = self._backend.\ provider_bootstrap(self._domain) if pageId == self.PRESENT_PROVIDER_PAGE: - self.page(pageId).setSubTitle(self.tr("Description of services " - "offered by %s") % - (self._provider_config - .get_name(),)) - - lang = QtCore.QLocale.system().name() - self.ui.lblProviderName.setText( - "<b>%s</b>" % - (self._provider_config.get_name(lang=lang),)) - self.ui.lblProviderURL.setText( - "https://%s" % (self._provider_config.get_domain(),)) - self.ui.lblProviderDesc.setText( - "<i>%s</i>" % - (self._provider_config.get_description(lang=lang),)) - - self.ui.lblServicesOffered.setText(self._provider_config - .get_services_string()) - self.ui.lblProviderPolicy.setText(self._provider_config - .get_enrollment_policy()) + sub_title = self.tr("Description of services offered by {0}") + sub_title = sub_title.format(self._provider_details.name) + self.page(pageId).setSubTitle(sub_title) + + details = self._provider_details + name = "<b>{0}</b>".format(details.name) + domain = "https://{0}".format(details.domain) + description = "<i>{0}</i>".format(details.description) + self.ui.lblProviderName.setText(name) + self.ui.lblProviderURL.setText(domain) + self.ui.lblProviderDesc.setText(description) + self.ui.lblServicesOffered.setText(details.services_string) + self.ui.lblProviderPolicy.setText(details.enrollment_policy) if pageId == self.REGISTER_USER_PAGE: - self.page(pageId).setSubTitle(self.tr("Register a new user with " - "%s") % - (self._provider_config - .get_name(),)) + sub_title = self.tr("Register a new user with {0}") + sub_title = sub_title.format(self._provider_details.name) + self.page(pageId).setSubTitle(sub_title) self.ui.chkRemember.setVisible(False) if pageId == self.SERVICES_PAGE: @@ -695,8 +706,6 @@ class Wizard(QtGui.QWizard): if self.currentPage() == self.page(self.SELECT_PROVIDER_PAGE): if self._use_existing_provider: self._domain = self.ui.cbProviders.currentText() - self._provider_config = ProviderConfig.get_provider_config( - self._domain) if self._show_register: return self.REGISTER_USER_PAGE else: @@ -721,6 +730,7 @@ class Wizard(QtGui.QWizard): sig.prov_name_resolution.connect(self._name_resolution) sig.prov_https_connection.connect(self._https_connection) sig.prov_download_provider_info.connect(self._download_provider_info) + sig.prov_get_details.connect(self._provider_get_details) sig.prov_download_ca_cert.connect(self._download_ca_cert) sig.prov_check_ca_fingerprint.connect(self._check_ca_fingerprint) |