def _load_configured_providers(self): """ Loads the configured providers into the wizard providers combo box. """ ls = LeapSettings() providers = ls.get_configured_providers() if not providers: self.ui.rbExistingProvider.setEnabled(False) self.ui.label_8.setEnabled(False) # 'https://' label self.ui.cbProviders.setEnabled(False) return pinned = [] user_added = [] # separate pinned providers from user added ones for p in providers: if ls.is_pinned_provider(p): pinned.append(p) else: user_added.append(p) if user_added: self.ui.cbProviders.addItems(user_added) if user_added and pinned: self.ui.cbProviders.addItem('---') if pinned: random.shuffle(pinned) # don't prioritize alphabetically self.ui.cbProviders.addItems(pinned) # We have configured providers, so by default we select the # 'Use existing provider' option. self.ui.rbExistingProvider.setChecked(True)
def check_missing(): """ Check for the need of installing missing scripts, and raises a dialog to ask user for permission to do it. """ config = LeapSettings() complain_missing = False alert_missing = config.get_alert_missing_scripts() if alert_missing and not flags.STANDALONE: # We refuse to install missing stuff if not running with standalone # flag. Right now we rely on the flag alone, but we can disable this # by overwriting some constant from within the debian package. alert_missing = False complain_missing = True launcher = get_vpn_launcher() missing_scripts = launcher.missing_updown_scripts missing_other = launcher.missing_other_files logger.debug("MISSING OTHER: %s" % (str(missing_other()))) missing_some = missing_scripts() or missing_other() if alert_missing and missing_some: msg = get_missing_helpers_dialog() ret = msg.exec_() if ret == QtGui.QMessageBox.Yes: install_missing_fun = globals().get( "_%s_install_missing_scripts" % (_system.lower(),), None) if not install_missing_fun: logger.warning( "Installer not found for platform %s." % (_system,)) return # XXX maybe move constants to fun ok = install_missing_fun(HELPERS_BADEXEC_MSG, HELPERS_NOTFOUND_MSG) if not ok: msg = QtGui.QMessageBox() msg.setWindowTitle(msg.tr("Problem installing files")) msg.setText(msg.tr('Some of the files could not be copied.')) msg.setIcon(QtGui.QMessageBox.Warning) msg.exec_() elif ret == QtGui.QMessageBox.No: logger.debug("Not installing missing scripts, " "user decided to ignore our warning.") init_signals.eip_missing_helpers.emit() elif ret == QtGui.QMessageBox.Rejected: logger.debug( "Setting alert_missing_scripts to False, we will not " "ask again") config.set_alert_missing_scripts(False) if complain_missing and missing_some: missing = missing_scripts() + missing_other() msg = _get_missing_complain_dialog(missing) ret = msg.exec_()
def __init__(self, parent, domain, backend, leap_signaler): """ :param parent: parent object of the EIPPreferencesWindow. :type parent: QWidget :param domain: the selected by default domain. :type domain: unicode :param backend: Backend being used :type backend: Backend """ QtGui.QDialog.__init__(self, parent) self.AUTOMATIC_GATEWAY_LABEL = self.tr("Automatic") self._settings = LeapSettings() self._leap_signaler = leap_signaler self._backend = backend # Load UI self.ui = Ui_EIPPreferences() self.ui.setupUi(self) self.ui.lblProvidersGatewayStatus.setVisible(False) # Connections self.ui.cbProvidersGateway.currentIndexChanged[int].connect( self._populate_gateways) self.ui.cbGateways.currentIndexChanged[unicode].connect( lambda x: self.ui.lblProvidersGatewayStatus.setVisible(False)) self._selected_domain = domain self._configured_providers = [] self._backend_connect() self._add_configured_providers()
class LeapSettingsTest(BaseLeapTest): """Tests for LeapSettings""" def setUp(self): pass def tearDown(self): pass def test_get_configured_providers(self): """ Test that the config file IS NOT stored under the CWD. """ self._leapsettings = LeapSettings() with mock.patch('os.listdir') as os_listdir: # use this method only to spy where LeapSettings is looking for self._leapsettings.get_configured_providers() args, kwargs = os_listdir.call_args config_dir = args[0] self.assertFalse(config_dir.startswith(os.getcwd())) self.assertFalse(config_dir.endswith('config')) def test_get_configured_providers_in_bundle(self): """ Test that the config file IS stored under the CWD. """ self._leapsettings = LeapSettings(standalone=True) with mock.patch('os.listdir') as os_listdir: # use this method only to spy where LeapSettings is looking for self._leapsettings.get_configured_providers() args, kwargs = os_listdir.call_args config_dir = args[0] self.assertTrue(config_dir.startswith(os.getcwd())) self.assertFalse(config_dir.endswith('config'))
def __init__(self, username, domain): self._settings = LeapSettings() self.username = username self.domain = domain if self.username is not None: self.address = make_address(self.username, self.domain) else: self.address = self.domain
def test_get_configured_providers_in_bundle(self): """ Test that the config file IS stored under the CWD. """ flags.STANDALONE = True self._leapsettings = LeapSettings() with mock.patch('os.listdir') as os_listdir: # use this method only to spy where LeapSettings is looking for self._leapsettings.get_configured_providers() args, kwargs = os_listdir.call_args config_dir = args[0] self.assertTrue(config_dir.startswith(os.getcwd())) self.assertFalse(config_dir.endswith('config'))
def __init__(self, parent, account, app): """ :param parent: parent object of the PreferencesWindow. :parent type: QWidget :param account: user account (user + provider or just provider) :type account: Account :param app: the current App object :type app: App """ PreferencesPage.__init__(self, parent, account, app) self.settings = LeapSettings() self.ui = Ui_PreferencesEmailPage() self.ui.setupUi(self) # the only way to set the tab titles is to re-add them: self.ui.email_tabs.addTab(self.ui.config_tab, self.tr("Mail Client")) self.ui.email_tabs.addTab(self.ui.my_key_tab, self.tr("My Key")) self.ui.email_tabs.addTab(self.ui.other_keys_tab, self.tr("Other Keys")) # set mail client configuration help text lang = QtCore.QLocale.system().name().replace('_', '-') thunderbird_extension_url = \ "https://addons.mozilla.org/{0}/" \ "thunderbird/addon/bitmask/".format(lang) self.ui.thunderbird_label.setText( self.tr("For Thunderbird, you can use the Bitmask extension. " "Search for \"Bitmask\" in the add-on manager or " "download it from <a href='{0}'>addons.mozilla.org</a>.". format(thunderbird_extension_url))) self.ui.mail_client_label.setText( self. tr("Alternatively, you can manually configure your mail client to " "use Bitmask with these options:")) self.ui.webmail_label.setText( self.tr("Bitmask Mail is an integrated mail client based " "on <a href='https://pixelated-project.org/'>Pixelated " "User Agent</a>. If enabled, any user on your device " "can read your mail by opening http://localhost:9090")) self.ui.keys_table.horizontalHeader().setResizeMode( 0, QtGui.QHeaderView.Stretch) self.setup_connections()
def __init__(self, parent, srp_auth): """ :param parent: parent object of the PreferencesWindow. :parent type: QWidget :param srp_auth: SRPAuth object configured in the main app. :type srp_auth: SRPAuth """ QtGui.QDialog.__init__(self, parent) self.AUTOMATIC_GATEWAY_LABEL = self.tr("Automatic") self._srp_auth = srp_auth self._settings = LeapSettings() self._soledad = None # Load UI self.ui = Ui_Preferences() self.ui.setupUi(self) self.ui.lblPasswordChangeStatus.setVisible(False) self.ui.lblProvidersServicesStatus.setVisible(False) self._selected_services = set() # Connections self.ui.pbChangePassword.clicked.connect(self._change_password) self.ui.cbProvidersServices.currentIndexChanged[unicode].connect( self._populate_services) if not self._settings.get_configured_providers(): self.ui.gbEnabledServices.setEnabled(False) else: self._add_configured_providers()
def __init__(self, userid, passwd, mdir=None): """ Initialize the plumber with all that's needed to authenticate against the provider. :param userid: user identifier, foo@bar :type userid: basestring :param passwd: the soledad passphrase :type passwd: basestring :param mdir: a path to a maildir to import :type mdir: str or None """ self.userid = userid self.passwd = passwd user, provider = userid.split('@') self.user = user self.mdir = mdir self.sol = None self._settings = LeapSettings() provider_config_path = os.path.join(get_path_prefix(), get_provider_path(provider)) provider_config = ProviderConfig() loaded = provider_config.load(provider_config_path) if not loaded: print "could not load provider config!" return self.exit()
class Account(): def __init__(self, username, domain): self._settings = LeapSettings() self.username = username self.domain = domain if self.username is not None: self.address = make_address(self.username, self.domain) else: self.address = self.domain def services(self): """ returns a list of service name strings TODO: this should depend not just on the domain """ return self._settings.get_enabled_services(self.domain) def has_email(self): return HAS_MAIL and MX_SERVICE in self.services() def has_eip(self): return HAS_EIP and EIP_SERVICE in self.services()
class Account(): def __init__(self, username, domain): self._settings = LeapSettings() self.username = username self.domain = domain if self.username is not None: self.address = make_address(self.username, self.domain) else: self.address = self.domain def services(self): """ returns a list of service name strings TODO: this should depend not just on the domain """ return self._settings.get_enabled_services(self.domain) def is_email_enabled(self): MX_SERVICE in self.services() def is_eip_enabled(self): EIP_SERVICE in self.services()
def __init__(self, backend, signaler, parent=None): """ Constructs the LoginWidget. :param backend: Backend being used :type backend: Backend :param signaler: Object in charge of handling communication back to the frontend :type signaler: Signaler :param parent: The parent widget for this widget :type parent: QWidget or None """ QtGui.QWidget.__init__(self, parent) SignalTracker.__init__(self) self.ui = Ui_LoginWidget() self.ui.setupUi(self) self.ui.chkRemember.stateChanged.connect(self._remember_state_changed) self.ui.chkRemember.setEnabled(has_keyring()) self.ui.lnUser.textChanged.connect(self._credentials_changed) self.ui.lnPassword.textChanged.connect(self._credentials_changed) self.ui.btnLogin.clicked.connect(self._do_login) self.ui.btnLogout.clicked.connect(self.do_logout) self.ui.lnUser.setValidator( QtGui.QRegExpValidator(QtCore.QRegExp(USERNAME_REGEX), self)) self.ui.clblErrorMsg.hide() self.ui.clblErrorMsg.clicked.connect(self.ui.clblErrorMsg.hide) self.ui.lnUser.textEdited.connect(self.ui.clblErrorMsg.hide) self.ui.lnPassword.textEdited.connect(self.ui.clblErrorMsg.hide) self._settings = LeapSettings() self._backend = backend self._leap_signaler = signaler # the selected provider that we'll use to login self._provider = None self._state = LoginState() self._set_logged_out()
def start_mail_service(self, download_if_needed=False, offline=False): """ Start the IMAP and SMTP servcies. """ if self._firewall is not None: self._firewall.start() if not offline: logger.debug("Starting smtp service...") self.start_smtp_service(download_if_needed=download_if_needed) self.start_imap_service() settings = LeapSettings() pixelmail = settings.get_pixelmail_enabled() if pixelmail: self.start_pixelated_service() self._mail_services_started = True
def check_missing(): """ Checks for the need of installing missing scripts, and raises a dialog to ask user for permission to do it. """ config = LeapSettings() alert_missing = config.get_alert_missing_scripts() launcher = get_vpn_launcher() missing_scripts = launcher.missing_updown_scripts missing_other = launcher.missing_other_files if alert_missing and (missing_scripts() or missing_other()): msg = get_missing_updown_dialog() ret = msg.exec_() if ret == QtGui.QMessageBox.Yes: install_missing_fun = globals().get( "_%s_install_missing_scripts" % (_system.lower(),), None) if not install_missing_fun: logger.warning( "Installer not found for platform %s." % (_system,)) return # XXX maybe move constants to fun ok = install_missing_fun(UPDOWN_BADEXEC_MSG, UPDOWN_NOTFOUND_MSG) if not ok: msg = QtGui.QMessageBox() msg.setWindowTitle(msg.tr("Problem installing files")) msg.setText(msg.tr('Some of the files could not be copied.')) msg.setIcon(QtGui.QMessageBox.Warning) msg.exec_() elif ret == QtGui.QMessageBox.No: logger.debug("Not installing missing scripts, " "user decided to ignore our warning.") elif ret == QtGui.QMessageBox.Rejected: logger.debug( "Setting alert_missing_scripts to False, we will not " "ask again") config.set_alert_missing_scripts(False)
def __init__(self, parent, username, domain, backend, soledad_started, mx, leap_signaler): """ :param parent: parent object of the PreferencesWindow. :parent type: QWidget :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._username = username self._domain = domain self._leap_signaler = leap_signaler self._backend = backend self._soledad_started = soledad_started self._mx_provided = mx self._settings = LeapSettings() self._backend_connect() # Load UI self.ui = Ui_Preferences() self.ui.setupUi(self) self.ui.lblPasswordChangeStatus.setVisible(False) self.ui.lblProvidersServicesStatus.setVisible(False) self._selected_services = set() # Connections self.ui.pbChangePassword.clicked.connect(self._change_password) self.ui.cbProvidersServices.currentIndexChanged[unicode].connect( self._populate_services) if not self._settings.get_configured_providers(): self.ui.gbEnabledServices.setEnabled(False) else: self._add_configured_providers() 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)
def test_get_configured_providers(self): """ Test that the config file IS NOT stored under the CWD. """ self._leapsettings = LeapSettings() with mock.patch('os.listdir') as os_listdir: # use this method only to spy where LeapSettings is looking for self._leapsettings.get_configured_providers() args, kwargs = os_listdir.call_args config_dir = args[0] self.assertFalse(config_dir.startswith(os.getcwd())) self.assertFalse(config_dir.endswith('config'))
def _load_configured_providers_with_pinned(self, pinned): """ Once we have the pinned providers from the backend, we continue setting everything up :param pinned: list of pinned providers :type pinned: list of str """ ls = LeapSettings() providers = ls.get_configured_providers() if not providers and not pinned: self.ui.rbExistingProvider.setEnabled(False) self.ui.label_8.setEnabled(False) # 'https://' label self.ui.cbProviders.setEnabled(False) return user_added = [] # separate pinned providers from user added ones for p in providers: if p not in pinned: user_added.append(p) if user_added: self.ui.cbProviders.addItems(user_added) if user_added and pinned: self.ui.cbProviders.addItem('---') if pinned: random.shuffle(pinned) # don't prioritize alphabetically self.ui.cbProviders.addItems(pinned) # We have configured providers, so by default we select the # 'Use existing provider' option. self.ui.rbExistingProvider.setChecked(True) # We need to set it as complete explicitly self.page(self.INTRO_PAGE).set_completed()
def __init__(self): QtGui.QWidget.__init__(self) self.settings = LeapSettings() self.backend = BackendProxy() self.backend.start() self.signaler = LeapSignaler() self.signaler.start() # periodically check if the backend is alive self._backend_checker = QtCore.QTimer(self) self._backend_checker.timeout.connect(self._check_backend_status) self._backend_checker.start(2000)
def __init__(self, parent, account, app): """ :param parent: parent object of the PreferencesWindow. :parent type: QWidget :param account: user account (user + provider or just provider) :type account: Account :param app: the current App object :type app: App """ PreferencesPage.__init__(self, parent, account, app) self.settings = LeapSettings() self.ui = Ui_PreferencesEmailPage() self.ui.setupUi(self) # the only way to set the tab titles is to re-add them: self.ui.email_tabs.addTab(self.ui.config_tab, self.tr("Mail Client")) self.ui.email_tabs.addTab(self.ui.my_key_tab, self.tr("My Key")) self.ui.email_tabs.addTab(self.ui.other_keys_tab, self.tr("Other Keys")) # set mail client configuration help text lang = QtCore.QLocale.system().name().replace('_', '-') thunderbird_extension_url = \ "https://addons.mozilla.org/{0}/" \ "thunderbird/addon/bitmask/".format(lang) self.ui.thunderbird_label.setText(self.tr( "For Thunderbird, you can use the Bitmask extension. " "Search for \"Bitmask\" in the add-on manager or " "download it from <a href='{0}'>addons.mozilla.org</a>.".format( thunderbird_extension_url))) self.ui.mail_client_label.setText(self.tr( "Alternatively, you can manually configure your mail client to " "use Bitmask with these options:")) self.ui.webmail_label.setText(self.tr( "Bitmask Mail is an integrated mail client based " "on <a href='https://pixelated-project.org/'>Pixelated " "User Agent</a>. If enabled, any user on your device " "can read your mail by opening http://localhost:9090")) self.ui.keys_table.horizontalHeader().setResizeMode( 0, QtGui.QHeaderView.Stretch) self.setup_connections()
def get_gateways(kls, eipconfig, providerconfig): """ Return the selected gateways for a given provider, looking at the EIP config file. :param eipconfig: eip configuration object :type eipconfig: EIPConfig :param providerconfig: provider specific configuration :type providerconfig: ProviderConfig :rtype: list """ gateways = [] leap_settings = LeapSettings() domain = providerconfig.get_domain() gateway_conf = leap_settings.get_selected_gateway(domain) gateway_selector = VPNGatewaySelector(eipconfig) if gateway_conf == leap_settings.GATEWAY_AUTOMATIC: gateways = gateway_selector.get_gateways() else: gateways = [gateway_conf] if not gateways: logger.error('No gateway was found!') raise VPNLauncherException('No gateway was found!') # this only works for selecting the first gateway, as we're # currently doing. ccodes = gateway_selector.get_gateways_country_code() gateway_ccode = ccodes[gateways[0]] flags.CURRENT_VPN_COUNTRY = gateway_ccode logger.debug("Using gateways ips: {0}".format(', '.join(gateways))) return gateways
def __init__(self, provider_config, signaler=None): """ Constructor for SRPAuth implementation :param provider_config: ProviderConfig needed to authenticate. :type provider_config: ProviderConfig :param signaler: Signaler object used to receive notifications from the backend :type signaler: Signaler """ leap_assert(provider_config, "We need a provider config to authenticate") self._provider_config = provider_config self._signaler = signaler self._settings = LeapSettings() # **************************************************** # # Dependency injection helpers, override this for more # granular testing self._fetcher = requests self._srp = srp self._hashfun = self._srp.SHA256 self._ng = self._srp.NG_1024 # **************************************************** # self._reset_session() self._session_id = None self._session_id_lock = threading.Lock() self._uuid = None self._uuid_lock = threading.Lock() self._token = None self._token_lock = threading.Lock() self._srp_user = None self._srp_a = None # User credentials stored for password changing checks self._username = None self._password = None
def __init__(self): QtGui.QWidget.__init__(self) self.settings = LeapSettings() self.backend = BackendProxy() self.backend.start() self.signaler = LeapSignaler() self.signaler.start() self.soledad_started = False self.service_tokens = {} self.login_state = None self.providers_widget = None # periodically check if the backend is alive self._backend_checker = QtCore.QTimer(self) self._backend_checker.timeout.connect(self._check_backend_status) self._backend_checker.start(2000) # store the service tokens for later use, once they are known. self.signaler.soledad_got_service_token.connect( self._set_service_tokens)
def __init__(self, provider_config): """ Constructor for SRPAuth implementation :param server: Server to which we will authenticate :type server: str """ QtCore.QObject.__init__(self) leap_assert(provider_config, "We need a provider config to authenticate") self._provider_config = provider_config self._settings = LeapSettings() # **************************************************** # # Dependency injection helpers, override this for more # granular testing self._fetcher = requests self._srp = srp self._hashfun = self._srp.SHA256 self._ng = self._srp.NG_1024 # **************************************************** # self._reset_session() self._session_id = None self._session_id_lock = QtCore.QMutex() self._uuid = None self._uuid_lock = QtCore.QMutex() self._token = None self._token_lock = QtCore.QMutex() self._srp_user = None self._srp_a = None # User credentials stored for password changing checks self._username = None self._password = None
def __init__(self, parent): """ :param parent: parent object of the EIPPreferencesWindow. :parent type: QWidget """ QtGui.QDialog.__init__(self, parent) self.AUTOMATIC_GATEWAY_LABEL = self.tr("Automatic") self._settings = LeapSettings() # Load UI self.ui = Ui_EIPPreferences() self.ui.setupUi(self) self.ui.lblProvidersGatewayStatus.setVisible(False) self.ui.lblAutoStartEIPStatus.setVisible(False) # Connections self.ui.cbProvidersGateway.currentIndexChanged[unicode].connect( self._populate_gateways) self.ui.cbGateways.currentIndexChanged[unicode].connect( lambda x: self.ui.lblProvidersGatewayStatus.setVisible(False)) self.ui.cbProvidersEIP.currentIndexChanged[unicode].connect( lambda x: self.ui.lblAutoStartEIPStatus.setVisible(False)) self.ui.cbAutoStartEIP.toggled.connect( lambda x: self.ui.lblAutoStartEIPStatus.setVisible(False)) self.ui.pbSaveAutoStartEIP.clicked.connect(self._save_auto_start_eip) self._add_configured_providers() # Load auto start EIP settings self.ui.cbAutoStartEIP.setChecked(self._settings.get_autostart_eip()) default_provider = self._settings.get_defaultprovider() idx = self.ui.cbProvidersEIP.findText(default_provider) self.ui.cbProvidersEIP.setCurrentIndex(idx)
def __init__(self, parent): """ :param parent: parent object of the EIPPreferencesWindow. :parent type: QWidget """ QtGui.QDialog.__init__(self, parent) self.AUTOMATIC_GATEWAY_LABEL = self.tr("Automatic") self._settings = LeapSettings() # Load UI self.ui = Ui_EIPPreferences() self.ui.setupUi(self) self.ui.lblProvidersGatewayStatus.setVisible(False) # Connections self.ui.cbProvidersGateway.currentIndexChanged[int].connect( self._populate_gateways) self.ui.cbGateways.currentIndexChanged[unicode].connect( lambda x: self.ui.lblProvidersGatewayStatus.setVisible(False)) self._add_configured_providers()
class PreferencesWindow(QtGui.QDialog): """ Window that displays the preferences. """ def __init__(self, parent, srp_auth): """ :param parent: parent object of the PreferencesWindow. :parent type: QWidget :param srp_auth: SRPAuth object configured in the main app. :type srp_auth: SRPAuth """ QtGui.QDialog.__init__(self, parent) self.AUTOMATIC_GATEWAY_LABEL = self.tr("Automatic") self._srp_auth = srp_auth self._settings = LeapSettings() self._soledad = None # Load UI self.ui = Ui_Preferences() self.ui.setupUi(self) self.ui.lblPasswordChangeStatus.setVisible(False) self.ui.lblProvidersServicesStatus.setVisible(False) self.ui.lblProvidersGatewayStatus.setVisible(False) self._selected_services = set() # Connections self.ui.pbChangePassword.clicked.connect(self._change_password) self.ui.cbProvidersServices.currentIndexChanged[unicode].connect( self._populate_services) self.ui.cbProvidersGateway.currentIndexChanged[unicode].connect( self._populate_gateways) self.ui.cbGateways.currentIndexChanged[unicode].connect( lambda x: self.ui.lblProvidersGatewayStatus.setVisible(False)) if not self._settings.get_configured_providers(): self.ui.gbEnabledServices.setEnabled(False) else: self._add_configured_providers() def set_soledad_ready(self, soledad): """ SLOT TRIGGERS: parent.soledad_ready It sets the soledad object as ready to use. :param soledad: Soledad object configured in the main app. :type soledad: Soledad """ self._soledad = soledad self.ui.gbPasswordChange.setEnabled(True) def _set_password_change_status(self, status, error=False, success=False): """ Sets the status label for the password change. :param status: status message to display, can be HTML :type status: str """ if error: status = "<font color='red'><b>%s</b></font>" % (status,) elif success: status = "<font color='green'><b>%s</b></font>" % (status,) self.ui.lblPasswordChangeStatus.setVisible(True) self.ui.lblPasswordChangeStatus.setText(status) def _set_changing_password(self, disable): """ Enables or disables the widgets in the password change group box. :param disable: True if the widgets should be disabled and it displays the status label that shows that is changing the password. False if they should be enabled. :type disable: bool """ if disable: self._set_password_change_status(self.tr("Changing password...")) self.ui.leCurrentPassword.setEnabled(not disable) self.ui.leNewPassword.setEnabled(not disable) self.ui.leNewPassword2.setEnabled(not disable) self.ui.pbChangePassword.setEnabled(not disable) def _change_password(self): """ SLOT TRIGGERS: self.ui.pbChangePassword.clicked Changes the user's password if the inputboxes are correctly filled. """ username = self._srp_auth.get_username() current_password = self.ui.leCurrentPassword.text() new_password = self.ui.leNewPassword.text() new_password2 = self.ui.leNewPassword2.text() ok, msg = basic_password_checks(username, new_password, new_password2) if not ok: self._set_changing_password(False) self._set_password_change_status(msg, error=True) self.ui.leNewPassword.setFocus() return self._set_changing_password(True) d = self._srp_auth.change_password(current_password, new_password) d.addCallback(partial(self._change_password_success, new_password)) d.addErrback(self._change_password_problem) def _change_password_success(self, new_password, _): """ Callback used to display a successfully performed action. :param new_password: the new password for the user. :type new_password: str. :param _: the returned data from self._srp_auth.change_password Ignored """ logger.debug("SRP password changed successfully.") try: self._soledad.change_passphrase(str(new_password)) logger.debug("Soledad password changed successfully.") except NoStorageSecret: logger.debug( "No storage secret for password change in Soledad.") self._set_password_change_status( self.tr("Password changed successfully."), success=True) self._clear_password_inputs() self._set_changing_password(False) def _change_password_problem(self, failure): """ Errback called if there was a problem with the deferred. Also is used to display an error message. :param failure: the cause of the method failed. :type failure: twisted.python.Failure """ logger.error("Error changing password: %s", (failure, )) problem = self.tr("There was a problem changing the password.") if failure.check(SRPAuthBadPassword): problem = self.tr("You did not enter a correct current password.") self._set_password_change_status(problem, error=True) self._set_changing_password(False) failure.trap(Exception) def _clear_password_inputs(self): """ Clear the contents of the inputs. """ self.ui.leCurrentPassword.setText("") self.ui.leNewPassword.setText("") self.ui.leNewPassword2.setText("") def _set_providers_services_status(self, status, success=False): """ Sets the status label for the password change. :param status: status message to display, can be HTML :type status: str :param success: is set to True if we should display the message as green :type success: bool """ if success: status = "<font color='green'><b>%s</b></font>" % (status,) self.ui.lblProvidersServicesStatus.setVisible(True) self.ui.lblProvidersServicesStatus.setText(status) def _set_providers_gateway_status(self, status, success=False, error=False): """ Sets the status label for the gateway change. :param status: status message to display, can be HTML :type status: str :param success: is set to True if we should display the message as green :type success: bool :param error: is set to True if we should display the message as red :type error: bool """ if success: status = "<font color='green'><b>%s</b></font>" % (status,) elif error: status = "<font color='red'><b>%s</b></font>" % (status,) self.ui.lblProvidersGatewayStatus.setVisible(True) self.ui.lblProvidersGatewayStatus.setText(status) def _add_configured_providers(self): """ Add the client's configured providers to the providers combo boxes. """ self.ui.cbProvidersServices.clear() self.ui.cbProvidersGateway.clear() for provider in self._settings.get_configured_providers(): self.ui.cbProvidersServices.addItem(provider) self.ui.cbProvidersGateway.addItem(provider) def _service_selection_changed(self, service, state): """ SLOT TRIGGER: service_checkbox.stateChanged Adds the service to the state if the state is checked, removes it otherwise :param service: service to handle :type service: str :param state: state of the checkbox :type state: int """ if state == QtCore.Qt.Checked: self._selected_services = \ self._selected_services.union(set([service])) else: self._selected_services = \ self._selected_services.difference(set([service])) # We hide the maybe-visible status label after a change self.ui.lblProvidersServicesStatus.setVisible(False) def _populate_services(self, domain): """ SLOT 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 """ # We hide the maybe-visible status label after a change self.ui.lblProvidersServicesStatus.setVisible(False) 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() except RuntimeError: pass # Signal was not connected save_services = partial(self._save_enabled_services, domain) self.ui.pbSaveServices.clicked.connect(save_services) services = get_supported(provider_config.get_services()) services_conf = self._settings.get_enabled_services(domain) # discard changes if other provider is selected self._selected_services = set() # from: http://stackoverflow.com/a/13103617/687989 # remove existing checkboxes layout = self.ui.vlServices for i in reversed(range(layout.count())): layout.itemAt(i).widget().setParent(None) # add one checkbox per service and set the current configured value for service in services: try: checkbox = QtGui.QCheckBox(self) service_label = get_service_display_name(service) checkbox.setText(service_label) self.ui.vlServices.addWidget(checkbox) checkbox.stateChanged.connect( partial(self._service_selection_changed, service)) checkbox.setChecked(service in services_conf) except ValueError: logger.error("Something went wrong while trying to " "load service %s" % (service,)) def _save_enabled_services(self, provider): """ SLOT TRIGGERS: self.ui.pbSaveServices.clicked Saves the new enabled services settings to the configuration file. :param provider: the provider config that we need to save. :type provider: str """ services = list(self._selected_services) self._settings.set_enabled_services(provider, services) msg = self.tr( "Services settings for provider '{0}' saved.".format(provider)) logger.debug(msg) self._set_providers_services_status(msg, success=True) 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() provider_config_path = os.path.join( "leap", "providers", domain, "provider.json") if not provider_config.load(provider_config_path): provider_config = None return provider_config def _save_selected_gateway(self, provider): """ SLOT TRIGGERS: self.ui.pbSaveGateway.clicked Saves the new gateway setting to the configuration file. :param provider: the provider config that we need to save. :type provider: str """ gateway = self.ui.cbGateways.currentText() if gateway == self.AUTOMATIC_GATEWAY_LABEL: gateway = self._settings.GATEWAY_AUTOMATIC else: idx = self.ui.cbGateways.currentIndex() gateway = self.ui.cbGateways.itemData(idx) self._settings.set_selected_gateway(provider, gateway) msg = self.tr( "Gateway settings for provider '{0}' saved.".format(provider)) logger.debug(msg) self._set_providers_gateway_status(msg, success=True) def _populate_gateways(self, domain): """ SLOT TRIGGERS: self.ui.cbProvidersGateway.currentIndexChanged[unicode] Loads the gateways that the provider provides into the UI for the user to select. :param domain: the domain of the provider to load gateways from. :type domain: str """ # We hide the maybe-visible status label after a change self.ui.lblProvidersGatewayStatus.setVisible(False) if not domain: return try: # disconnect prevoiusly connected save method self.ui.pbSaveGateway.clicked.disconnect() except RuntimeError: pass # Signal was not connected # set the proper connection for the 'save' button save_gateway = partial(self._save_selected_gateway, domain) self.ui.pbSaveGateway.clicked.connect(save_gateway) eip_config = EIPConfig() provider_config = self._get_provider_config(domain) eip_config_path = os.path.join("leap", "providers", domain, "eip-service.json") api_version = provider_config.get_api_version() eip_config.set_api_version(api_version) eip_loaded = eip_config.load(eip_config_path) if not eip_loaded or provider_config is None: self._set_providers_gateway_status( self.tr("There was a problem with configuration files."), error=True) return gateways = VPNGatewaySelector(eip_config).get_gateways_list() logger.debug(gateways) self.ui.cbGateways.clear() self.ui.cbGateways.addItem(self.AUTOMATIC_GATEWAY_LABEL) # Add the available gateways and # select the one stored in configuration file. selected_gateway = self._settings.get_selected_gateway(domain) index = 0 for idx, (gw_name, gw_ip) in enumerate(gateways): gateway = "{0} ({1})".format(gw_name, gw_ip) self.ui.cbGateways.addItem(gateway, gw_ip) if gw_ip == selected_gateway: index = idx + 1 self.ui.cbGateways.setCurrentIndex(index)
class EIPPreferencesWindow(QtGui.QDialog): """ Window that displays the EIP preferences. """ def __init__(self, parent, domain, backend, leap_signaler): """ :param parent: parent object of the EIPPreferencesWindow. :type parent: QWidget :param domain: the selected by default domain. :type domain: unicode :param backend: Backend being used :type backend: Backend """ QtGui.QDialog.__init__(self, parent) self.AUTOMATIC_GATEWAY_LABEL = self.tr("Automatic") self._settings = LeapSettings() self._leap_signaler = leap_signaler self._backend = backend # Load UI self.ui = Ui_EIPPreferences() self.ui.setupUi(self) self.ui.lblProvidersGatewayStatus.setVisible(False) # Connections self.ui.cbProvidersGateway.currentIndexChanged[int].connect( self._populate_gateways) self.ui.cbGateways.currentIndexChanged[unicode].connect( lambda x: self.ui.lblProvidersGatewayStatus.setVisible(False)) self._selected_domain = domain self._configured_providers = [] self._backend_connect() self._add_configured_providers() def _set_providers_gateway_status(self, status, success=False, error=False): """ Sets the status label for the gateway change. :param status: status message to display, can be HTML :type status: str :param success: is set to True if we should display the message as green :type success: bool :param error: is set to True if we should display the message as red :type error: bool """ if success: status = "<font color='green'><b>%s</b></font>" % (status, ) elif error: status = "<font color='red'><b>%s</b></font>" % (status, ) self.ui.lblProvidersGatewayStatus.setVisible(True) self.ui.lblProvidersGatewayStatus.setText(status) def _add_configured_providers(self): """ Add the client's configured providers to the providers combo boxes. """ providers = self._settings.get_configured_providers() if not providers: return self._backend.eip_get_initialized_providers(domains=providers) def _load_providers_in_combo(self, providers): """ TRIGGERS: Signaler.eip_get_initialized_providers Add the client's configured providers to the providers combo boxes. :param providers: the list of providers to add and whether each one is initialized or not. :type providers: list of tuples (str, bool) """ self.ui.cbProvidersGateway.clear() if not providers: self.ui.gbGatewaySelector.setEnabled(False) return # block signals so the currentIndexChanged slot doesn't get triggered self.ui.cbProvidersGateway.blockSignals(True) for provider, is_initialized in providers: label = provider if not is_initialized: label += self.tr(" (uninitialized)") self.ui.cbProvidersGateway.addItem(label, userData=provider) self.ui.cbProvidersGateway.blockSignals(False) # Select provider by name domain = self._selected_domain if domain is not None: provider_index = self.ui.cbProvidersGateway.findText( domain, QtCore.Qt.MatchStartsWith) self.ui.cbProvidersGateway.setCurrentIndex(provider_index) def _save_selected_gateway(self, provider): """ TRIGGERS: self.ui.pbSaveGateway.clicked Saves the new gateway setting to the configuration file. :param provider: the provider config that we need to save. :type provider: str """ gateway = self.ui.cbGateways.currentText() if gateway == self.AUTOMATIC_GATEWAY_LABEL: gateway = self._settings.GATEWAY_AUTOMATIC else: idx = self.ui.cbGateways.currentIndex() gateway = self.ui.cbGateways.itemData(idx) self._settings.set_selected_gateway(provider, gateway) self._backend.settings_set_selected_gateway(provider=provider, gateway=gateway) msg = self.tr("Gateway settings for provider '{0}' saved.").format( provider) self._set_providers_gateway_status(msg, success=True) def _populate_gateways(self, domain_idx): """ TRIGGERS: self.ui.cbProvidersGateway.currentIndexChanged[unicode] Loads the gateways that the provider provides into the UI for the user to select. :param domain: the domain index of the provider to load gateways from. :type domain: int """ # We hide the maybe-visible status label after a change self.ui.lblProvidersGatewayStatus.setVisible(False) if domain_idx == -1: return domain = self.ui.cbProvidersGateway.itemData(domain_idx) self._selected_domain = domain self._backend.eip_get_gateways_list(domain=domain) def _update_gateways_list(self, gateways): """ TRIGGERS: Signaler.eip_get_gateways_list :param gateways: a list of gateways :type gateways: list of unicode Add the available gateways and select the one stored in configuration file. """ self.ui.pbSaveGateway.setEnabled(True) self.ui.cbGateways.setEnabled(True) self.ui.cbGateways.clear() self.ui.cbGateways.addItem(self.AUTOMATIC_GATEWAY_LABEL) try: # disconnect previously connected save method self.ui.pbSaveGateway.clicked.disconnect() except RuntimeError: pass # Signal was not connected # set the proper connection for the 'save' button domain = self._selected_domain save_gateway = partial(self._save_selected_gateway, domain) self.ui.pbSaveGateway.clicked.connect(save_gateway) selected_gateway = self._settings.get_selected_gateway( self._selected_domain) index = 0 for idx, (gw_name, gw_ip) in enumerate(gateways): gateway = "{0} ({1})".format(gw_name, gw_ip) self.ui.cbGateways.addItem(gateway, gw_ip) if gw_ip == selected_gateway: index = idx + 1 self.ui.cbGateways.setCurrentIndex(index) def _gateways_list_error(self): """ TRIGGERS: Signaler.eip_get_gateways_list_error An error has occurred retrieving the gateway list so we inform the user. """ self._set_providers_gateway_status( self.tr("There was a problem with configuration files."), error=True) self.ui.pbSaveGateway.setEnabled(False) self.ui.cbGateways.setEnabled(False) def _gateways_list_uninitialized(self): """ TRIGGERS: Signaler.eip_uninitialized_provider The requested provider in not initialized yet, so we give the user an error msg. """ self._set_providers_gateway_status( self.tr("This is an uninitialized provider, please log in first."), error=True) self.ui.pbSaveGateway.setEnabled(False) self.ui.cbGateways.setEnabled(False) def _backend_connect(self): sig = self._leap_signaler sig.eip_get_gateways_list.connect(self._update_gateways_list) sig.eip_get_gateways_list_error.connect(self._gateways_list_error) sig.eip_uninitialized_provider.connect( self._gateways_list_uninitialized) sig.eip_get_initialized_providers.connect( self._load_providers_in_combo)
class PreferencesWindow(QtGui.QDialog): """ Window that displays the preferences. """ preferences_saved = QtCore.Signal() def __init__(self, parent, username, domain, backend, soledad_started, mx, leap_signaler): """ :param parent: parent object of the PreferencesWindow. :parent type: QWidget :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._username = username self._domain = domain self._leap_signaler = leap_signaler self._backend = backend self._soledad_started = soledad_started self._mx_provided = mx self._settings = LeapSettings() self._backend_connect() # Load UI self.ui = Ui_Preferences() self.ui.setupUi(self) self.ui.lblPasswordChangeStatus.setVisible(False) self.ui.lblProvidersServicesStatus.setVisible(False) self._selected_services = set() # Connections self.ui.pbChangePassword.clicked.connect(self._change_password) self.ui.cbProvidersServices.currentIndexChanged[unicode].connect( self._populate_services) if not self._settings.get_configured_providers(): self.ui.gbEnabledServices.setEnabled(False) else: self._add_configured_providers() 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) def _not_logged_in(self): """ Actions to perform if the user is not logged in. """ msg = self.tr( "In order to change your password you need to be logged in.") 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): """ TRIGGERS: parent.soledad_ready It notifies when the soledad object as ready to use. """ self.ui.lblPasswordChangeStatus.setVisible(False) self.ui.gbPasswordChange.setEnabled(True) def _set_password_change_status(self, status, error=False, success=False): """ Sets the status label for the password change. :param status: status message to display, can be HTML :type status: str """ if error: status = "<font color='red'><b>%s</b></font>" % (status,) elif success: status = "<font color='green'><b>%s</b></font>" % (status,) if not self.ui.gbPasswordChange.isEnabled(): status = "<font color='black'>%s</font>" % (status,) self.ui.lblPasswordChangeStatus.setVisible(True) self.ui.lblPasswordChangeStatus.setText(status) def _set_changing_password(self, disable): """ Enables or disables the widgets in the password change group box. :param disable: True if the widgets should be disabled and it displays the status label that shows that is changing the password. False if they should be enabled. :type disable: bool """ if disable: self._set_password_change_status(self.tr("Changing password...")) self.ui.leCurrentPassword.setEnabled(not disable) self.ui.leNewPassword.setEnabled(not disable) self.ui.leNewPassword2.setEnabled(not disable) self.ui.pbChangePassword.setEnabled(not disable) @QtCore.Slot() def _change_password(self): """ TRIGGERS: self.ui.pbChangePassword.clicked Changes the user's password if the inputboxes are correctly filled. """ username = self._username current_password = self.ui.leCurrentPassword.text() new_password = self.ui.leNewPassword.text() new_password2 = self.ui.leNewPassword2.text() ok, msg = password_checks(username, new_password, new_password2) if not ok: self._set_changing_password(False) self._set_password_change_status(msg, error=True) self.ui.leNewPassword.setFocus() return self._set_changing_password(True) self._backend.user_change_password(current_password=current_password, new_password=new_password) @QtCore.Slot() def _srp_change_password_ok(self): """ TRIGGERS: self._backend.signaler.srp_password_change_ok Callback used to display a successfully changed password. """ new_password = self.ui.leNewPassword.text() logger.debug("SRP password changed successfully.") if self._mx_provided: self._backend.soledad_change_password(new_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) self._clear_password_inputs() self._set_changing_password(False) @QtCore.Slot(unicode) def _soledad_change_password_problem(self, msg): """ TRIGGERS: 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 soledad password") self._set_password_change_status(msg, error=True) self._set_changing_password(False) def _clear_password_inputs(self): """ Clear the contents of the inputs. """ self.ui.leCurrentPassword.setText("") self.ui.leNewPassword.setText("") self.ui.leNewPassword2.setText("") def _set_providers_services_status(self, status, success=False): """ Sets the status label for the password change. :param status: status message to display, can be HTML :type status: str :param success: is set to True if we should display the message as green :type success: bool """ if success: status = "<font color='green'><b>%s</b></font>" % (status,) self.ui.lblProvidersServicesStatus.setVisible(True) self.ui.lblProvidersServicesStatus.setText(status) def _add_configured_providers(self): """ Add the client's configured providers to the providers combo boxes. """ self.ui.cbProvidersServices.clear() for provider in self._settings.get_configured_providers(): self.ui.cbProvidersServices.addItem(provider) def _select_provider_by_name(self, name): """ Given a provider name/domain, selects it in the combobox. :param name: name or domain for the provider :type name: str """ provider_index = self.ui.cbProvidersServices.findText(name) self.ui.cbProvidersServices.setCurrentIndex(provider_index) @QtCore.Slot(str, int) def _service_selection_changed(self, service, state): """ TRIGGERS: service_checkbox.stateChanged Adds the service to the state if the state is checked, removes it otherwise :param service: service to handle :type service: str :param state: state of the checkbox :type state: int """ if state == QtCore.Qt.Checked: self._selected_services = \ self._selected_services.union(set([service])) else: self._selected_services = \ self._selected_services.difference(set([service])) # We hide the maybe-visible status label after a change self.ui.lblProvidersServicesStatus.setVisible(False) @QtCore.Slot(str) def _populate_services(self, domain): """ TRIGGERS: self.ui.cbProvidersServices.currentIndexChanged[unicode] Fill the services list with the selected provider's services. :param domain: the domain of the provider to load services from. :type domain: str """ # We hide the maybe-visible status label after a change self.ui.lblProvidersServicesStatus.setVisible(False) if not domain: return # set the proper connection for the 'save' button try: self.ui.pbSaveServices.clicked.disconnect() except RuntimeError: pass # Signal was not connected save_services = partial(self._save_enabled_services, domain) self.ui.pbSaveServices.clicked.connect(save_services) self._backend.provider_get_supported_services(domain=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 self._selected_services = set() # from: http://stackoverflow.com/a/13103617/687989 # remove existing checkboxes layout = self.ui.vlServices for i in reversed(range(layout.count())): layout.itemAt(i).widget().setParent(None) # add one checkbox per service and set the current configured value for service in services: try: checkbox = QtGui.QCheckBox(self) service_label = get_service_display_name(service) checkbox.setText(service_label) self.ui.vlServices.addWidget(checkbox) checkbox.stateChanged.connect( partial(self._service_selection_changed, service)) checkbox.setChecked(service in services_conf) except ValueError: logger.error("Something went wrong while trying to " "load service %s" % (service,)) @QtCore.Slot(str) def _save_enabled_services(self, provider): """ TRIGGERS: self.ui.pbSaveServices.clicked Saves the new enabled services settings to the configuration file. :param provider: the provider config that we need to save. :type provider: str """ services = list(self._selected_services) self._settings.set_enabled_services(provider, services) msg = self.tr( "Services settings for provider '{0}' saved.".format(provider)) logger.debug(msg) self._set_providers_services_status(msg, success=True) self.preferences_saved.emit() def _backend_connect(self): """ Helper to connect to backend signals """ sig = self._leap_signaler sig.prov_get_supported_services.connect(self._load_services) sig.srp_password_change_ok.connect(self._srp_change_password_ok) 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._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)
class __impl(QtCore.QObject): """ Implementation of the SRPAuth interface """ LOGIN_KEY = "login" A_KEY = "A" CLIENT_AUTH_KEY = "client_auth" SESSION_ID_KEY = "_session_id" USER_VERIFIER_KEY = 'user[password_verifier]' USER_SALT_KEY = 'user[password_salt]' AUTHORIZATION_KEY = "Authorization" def __init__(self, provider_config): """ Constructor for SRPAuth implementation :param server: Server to which we will authenticate :type server: str """ QtCore.QObject.__init__(self) leap_assert(provider_config, "We need a provider config to authenticate") self._provider_config = provider_config self._settings = LeapSettings() # **************************************************** # # Dependency injection helpers, override this for more # granular testing self._fetcher = requests self._srp = srp self._hashfun = self._srp.SHA256 self._ng = self._srp.NG_1024 # **************************************************** # self._reset_session() self._session_id = None self._session_id_lock = QtCore.QMutex() self._uuid = None self._uuid_lock = QtCore.QMutex() self._token = None self._token_lock = QtCore.QMutex() self._srp_user = None self._srp_a = None # User credentials stored for password changing checks self._username = None self._password = None def _reset_session(self): """ Resets the current session and sets max retries to 30. """ self._session = self._fetcher.session() # We need to bump the default retries, otherwise logout # fails most of the times # NOTE: This is a workaround for the moment, the server # side seems to return correctly every time, but it fails # on the client end. if requests_has_max_retries: adapter = HTTPAdapter(max_retries=30) else: adapter = HTTPAdapter() self._session.mount('https://', adapter) def _safe_unhexlify(self, val): """ Rounds the val to a multiple of 2 and returns the unhexlified value :param val: hexlified value :type val: str :rtype: binary hex data :return: unhexlified val """ return binascii.unhexlify(val) \ if (len(val) % 2 == 0) else binascii.unhexlify('0' + val) def _authentication_preprocessing(self, username, password): """ Generates the SRP.User to get the A SRP parameter :param username: username to login :type username: str :param password: password for the username :type password: str """ logger.debug("Authentication preprocessing...") self._srp_user = self._srp.User(username.encode('utf-8'), password.encode('utf-8'), self._hashfun, self._ng) _, A = self._srp_user.start_authentication() self._srp_a = A def _start_authentication(self, _, username): """ Sends the first request for authentication to retrieve the salt and B parameter Might raise all SRPAuthenticationError based: SRPAuthenticationError SRPAuthConnectionError SRPAuthBadStatusCode SRPAuthNoSalt SRPAuthNoB :param _: IGNORED, output from the previous callback (None) :type _: IGNORED :param username: username to login :type username: str :return: salt and B parameters :rtype: tuple """ logger.debug("Starting authentication process...") try: auth_data = { self.LOGIN_KEY: username, self.A_KEY: binascii.hexlify(self._srp_a) } sessions_url = "%s/%s/%s/" % \ (self._provider_config.get_api_uri(), self._provider_config.get_api_version(), "sessions") ca_cert_path = self._provider_config.get_ca_cert_path() ca_cert_path = ca_cert_path.encode(sys.getfilesystemencoding()) init_session = self._session.post(sessions_url, data=auth_data, verify=ca_cert_path, timeout=REQUEST_TIMEOUT) # Clean up A value, we don't need it anymore self._srp_a = None except requests.exceptions.ConnectionError as e: logger.error("No connection made (salt): {0!r}".format(e)) raise SRPAuthConnectionError() except Exception as e: logger.error("Unknown error: %r" % (e,)) raise SRPAuthenticationError() content, mtime = reqhelper.get_content(init_session) if init_session.status_code not in (200,): logger.error("No valid response (salt): " "Status code = %r. Content: %r" % (init_session.status_code, content)) if init_session.status_code == 422: logger.error("Invalid username or password.") raise SRPAuthBadUserOrPassword() logger.error("There was a problem with authentication.") raise SRPAuthBadStatusCode() json_content = json.loads(content) salt = json_content.get("salt", None) B = json_content.get("B", None) if salt is None: logger.error("The server didn't send the salt parameter.") raise SRPAuthNoSalt() if B is None: logger.error("The server didn't send the B parameter.") raise SRPAuthNoB() return salt, B def _process_challenge(self, salt_B, username): """ Given the salt and B processes the auth challenge and generates the M2 parameter Might raise SRPAuthenticationError based: SRPAuthenticationError SRPAuthBadDataFromServer SRPAuthConnectionError SRPAuthJSONDecodeError SRPAuthBadUserOrPassword :param salt_B: salt and B parameters for the username :type salt_B: tuple :param username: username for this session :type username: str :return: the M2 SRP parameter :rtype: str """ logger.debug("Processing challenge...") try: salt, B = salt_B unhex_salt = self._safe_unhexlify(salt) unhex_B = self._safe_unhexlify(B) except (TypeError, ValueError) as e: logger.error("Bad data from server: %r" % (e,)) raise SRPAuthBadDataFromServer() M = self._srp_user.process_challenge(unhex_salt, unhex_B) auth_url = "%s/%s/%s/%s" % (self._provider_config.get_api_uri(), self._provider_config. get_api_version(), "sessions", username) auth_data = { self.CLIENT_AUTH_KEY: binascii.hexlify(M) } try: auth_result = self._session.put(auth_url, data=auth_data, verify=self._provider_config. get_ca_cert_path(), timeout=REQUEST_TIMEOUT) except requests.exceptions.ConnectionError as e: logger.error("No connection made (HAMK): %r" % (e,)) raise SRPAuthConnectionError() try: content, mtime = reqhelper.get_content(auth_result) except JSONDecodeError: logger.error("Bad JSON content in auth result.") raise SRPAuthJSONDecodeError() if auth_result.status_code == 422: error = "" try: error = json.loads(content).get("errors", "") except ValueError: logger.error("Problem parsing the received response: %s" % (content,)) except AttributeError: logger.error("Expecting a dict but something else was " "received: %s", (content,)) logger.error("[%s] Wrong password (HAMK): [%s]" % (auth_result.status_code, error)) raise SRPAuthBadUserOrPassword() if auth_result.status_code not in (200,): logger.error("No valid response (HAMK): " "Status code = %s. Content = %r" % (auth_result.status_code, content)) raise SRPAuthBadStatusCode() return json.loads(content) def _extract_data(self, json_content): """ Extracts the necessary parameters from json_content (M2, id, token) Might raise SRPAuthenticationError based: SRPBadDataFromServer :param json_content: Data received from the server :type json_content: dict """ try: M2 = json_content.get("M2", None) uuid = json_content.get("id", None) token = json_content.get("token", None) except Exception as e: logger.error(e) raise SRPAuthBadDataFromServer() self.set_uuid(uuid) self.set_token(token) if M2 is None or self.get_uuid() is None: logger.error("Something went wrong. Content = %r" % (json_content,)) raise SRPAuthBadDataFromServer() events_signal( proto.CLIENT_UID, content=uuid, reqcbk=lambda req, res: None) # make the rpc call async return M2 def _verify_session(self, M2): """ Verifies the session based on the M2 parameter. If the verification succeeds, it sets the session_id for this session Might raise SRPAuthenticationError based: SRPAuthBadDataFromServer SRPAuthVerificationFailed :param M2: M2 SRP parameter :type M2: str """ logger.debug("Verifying session...") try: unhex_M2 = self._safe_unhexlify(M2) except TypeError: logger.error("Bad data from server (HAWK)") raise SRPAuthBadDataFromServer() self._srp_user.verify_session(unhex_M2) if not self._srp_user.authenticated(): logger.error("Auth verification failed.") raise SRPAuthVerificationFailed() logger.debug("Session verified.") session_id = self._session.cookies.get(self.SESSION_ID_KEY, None) if not session_id: logger.error("Bad cookie from server (missing _session_id)") raise SRPAuthNoSessionId() events_signal( proto.CLIENT_SESSION_ID, content=session_id, reqcbk=lambda req, res: None) # make the rpc call async self.set_session_id(session_id) def _threader(self, cb, res, *args, **kwargs): return threads.deferToThread(cb, res, *args, **kwargs) def change_password(self, current_password, new_password): """ Changes the password for the currently logged user if the current password match. It requires to be authenticated. Might raise: SRPAuthBadUserOrPassword requests.exceptions.HTTPError :param current_password: the current password for the logged user. :type current_password: str :param new_password: the new password for the user :type new_password: str """ leap_assert(self.get_uuid() is not None) if current_password != self._password: raise SRPAuthBadUserOrPassword url = "%s/%s/users/%s.json" % ( self._provider_config.get_api_uri(), self._provider_config.get_api_version(), self.get_uuid()) salt, verifier = self._srp.create_salted_verification_key( self._username.encode('utf-8'), new_password.encode('utf-8'), self._hashfun, self._ng) cookies = {self.SESSION_ID_KEY: self.get_session_id()} headers = { self.AUTHORIZATION_KEY: "Token token={0}".format(self.get_token()) } user_data = { self.USER_VERIFIER_KEY: binascii.hexlify(verifier), self.USER_SALT_KEY: binascii.hexlify(salt) } change_password = self._session.put( url, data=user_data, verify=self._provider_config.get_ca_cert_path(), cookies=cookies, timeout=REQUEST_TIMEOUT, headers=headers) # In case of non 2xx it raises HTTPError change_password.raise_for_status() self._password = new_password def authenticate(self, username, password): """ Executes the whole authentication process for a user Might raise SRPAuthenticationError :param username: username for this session :type username: unicode :param password: password for this user :type password: unicode :returns: A defer on a different thread :rtype: twisted.internet.defer.Deferred """ leap_assert(self.get_session_id() is None, "Already logged in") # User credentials stored for password changing checks self._username = username self._password = password self._reset_session() d = threads.deferToThread(self._authentication_preprocessing, username=username, password=password) d.addCallback( partial(self._threader, self._start_authentication), username=username) d.addCallback( partial(self._threader, self._process_challenge), username=username) d.addCallback( partial(self._threader, self._extract_data)) d.addCallback(partial(self._threader, self._verify_session)) return d def logout(self): """ Logs out the current session. Expects a session_id to exists, might raise AssertionError """ logger.debug("Starting logout...") if self.get_session_id() is None: logger.debug("Already logged out") return logout_url = "%s/%s/%s/" % (self._provider_config.get_api_uri(), self._provider_config. get_api_version(), "logout") try: self._session.delete(logout_url, data=self.get_session_id(), verify=self._provider_config. get_ca_cert_path(), timeout=REQUEST_TIMEOUT) except Exception as e: logger.warning("Something went wrong with the logout: %r" % (e,)) raise else: self.set_session_id(None) self.set_uuid(None) self.set_token(None) # Also reset the session self._session = self._fetcher.session() logger.debug("Successfully logged out.") def set_session_id(self, session_id): QtCore.QMutexLocker(self._session_id_lock) self._session_id = session_id def get_session_id(self): QtCore.QMutexLocker(self._session_id_lock) return self._session_id def set_uuid(self, uuid): QtCore.QMutexLocker(self._uuid_lock) full_uid = "%s@%s" % ( self._username, self._provider_config.get_domain()) if uuid is not None: # avoid removing the uuid from settings self._settings.set_uuid(full_uid, uuid) self._uuid = uuid def get_uuid(self): QtCore.QMutexLocker(self._uuid_lock) return self._uuid def set_token(self, token): QtCore.QMutexLocker(self._token_lock) self._token = token def get_token(self): QtCore.QMutexLocker(self._token_lock) return self._token
class LoginWidget(QtGui.QWidget, SignalTracker): """ Login widget that emits signals to display the wizard or to perform login. """ login_start = QtCore.Signal() login_finished = QtCore.Signal() login_offline_finished = QtCore.Signal() login_failed = QtCore.Signal() logged_out = QtCore.Signal() MAX_STATUS_WIDTH = 40 # Keyring KEYRING_KEY = "bitmask" def __init__(self, backend, signaler, parent=None): """ Constructs the LoginWidget. :param backend: Backend being used :type backend: Backend :param signaler: Object in charge of handling communication back to the frontend :type signaler: Signaler :param parent: The parent widget for this widget :type parent: QWidget or None """ QtGui.QWidget.__init__(self, parent) SignalTracker.__init__(self) self.ui = Ui_LoginWidget() self.ui.setupUi(self) self.ui.chkRemember.stateChanged.connect(self._remember_state_changed) self.ui.chkRemember.setEnabled(has_keyring()) self.ui.lnUser.textChanged.connect(self._credentials_changed) self.ui.lnPassword.textChanged.connect(self._credentials_changed) self.ui.btnLogin.clicked.connect(self._do_login) self.ui.btnLogout.clicked.connect(self.do_logout) self.ui.lnUser.setValidator( QtGui.QRegExpValidator(QtCore.QRegExp(USERNAME_REGEX), self)) self.ui.clblErrorMsg.hide() self.ui.clblErrorMsg.clicked.connect(self.ui.clblErrorMsg.hide) self.ui.lnUser.textEdited.connect(self.ui.clblErrorMsg.hide) self.ui.lnPassword.textEdited.connect(self.ui.clblErrorMsg.hide) self._settings = LeapSettings() self._backend = backend self._leap_signaler = signaler # the selected provider that we'll use to login self._provider = None self._state = LoginState() self._set_logged_out() def _remember_state_changed(self, state): """ Save the remember state in the LeapSettings. :param state: the current state of the check box. :type state: int """ # The possible state values of the checkbox (from QtCore.Qt.CheckState) # are: Checked, Unchecked and PartiallyChecked self._settings.set_remember(state == QtCore.Qt.Checked) def _credentials_changed(self, text): """ TRIGGER: self.ui.lnUser.textChanged self.ui.lnPassword.textChanged Update the 'enabled' status of the login button depending if we have all the fields needed set. """ enabled = self._provider and self.get_user() and self.get_password() enabled = bool(enabled) # provider can be None self.ui.btnLogin.setEnabled(enabled) def wait_for_login(self, wait): """ Set the wait flag to True/False so the next time that a login action is requested it will wait or not. If we set the wait to True and we have paused a login request before, this will trigger a login action. :param wait: whether we should wait or not on the next login request. :type wait: bool """ self._state.wait_to_login = wait if not wait and self._state.login_waiting: logger.debug("No more waiting, triggering login sequence.") self._do_login() def set_provider(self, provider): """ Set the provider to use in the login sequence. :param provider: the provider to use. :type provider: unicode """ self._provider = provider def set_remember(self, value): """ Check the remember user and password checkbox :param value: True to mark it checked, False otherwise :type value: bool """ self.ui.chkRemember.setChecked(value) def get_remember(self): """ Returns the remember checkbox state :rtype: bool """ return self.ui.chkRemember.isChecked() def set_user(self, user): """ Sets the user and focuses on the next field, password. :param user: user to set the field to :type user: str """ self.ui.lnUser.setText(user) self._focus_password() def get_user(self): """ Return the user that appears in the widget. :rtype: unicode """ return self.ui.lnUser.text() def get_logged_user(self): """ Return the current logged user or None if no user is logged in. The return value has the format: 'user@provider' :rtype: unicode or None """ return self._state.full_logged_username def set_password(self, password): """ Sets the password for the widget :param password: password to set :type password: unicode """ self.ui.lnPassword.setText(password) def get_password(self): """ Returns the password that appears in the widget :rtype: unicode """ return self.ui.lnPassword.text() def set_status(self, status, error=True): """ Sets the status label at the login stage to status :param status: status message :type status: str """ if len(status) > self.MAX_STATUS_WIDTH: status = status[:self.MAX_STATUS_WIDTH] + "..." if error: self.ui.clblErrorMsg.show() self.ui.clblErrorMsg.setText(status) else: self.ui.lblStatus.setText(status) def set_enabled(self, enabled=False): """ Enables or disables all the login widgets :param enabled: wether they should be enabled or not :type enabled: bool """ self.ui.lnUser.setEnabled(enabled) self.ui.lnPassword.setEnabled(enabled) self.ui.chkRemember.setEnabled(enabled and has_keyring()) self._set_cancel(not enabled) def set_logout_btn_enabled(self, enabled): """ Enables or disables the logout button. :param enabled: wether they should be enabled or not :type enabled: bool """ self.ui.btnLogout.setEnabled(enabled) def _set_cancel(self, enabled=False): """ Enables or disables the cancel action in the "log in" process. :param enabled: wether it should be enabled or not :type enabled: bool """ text = self.tr("Cancel") login_or_cancel = self._do_cancel hide_remember = enabled if not enabled: text = self.tr("Log In") login_or_cancel = self._do_login self.ui.btnLogin.setText(text) self.ui.btnLogin.clicked.disconnect() self.ui.btnLogin.clicked.connect(login_or_cancel) self.ui.chkRemember.setVisible(not hide_remember) self.ui.lblStatus.setVisible(hide_remember) def _focus_password(self): """ Focuses in the password lineedit """ self.ui.lnPassword.setFocus() def _check_login(self): """ Check that we have the needed fields to do the actual login: provider, username and password. :return: True if everything's good to go, False otherwise. :rtype: bool """ provider = self._provider username = self.get_user() password = self.get_password() if not provider: self.set_status(self.tr("Please select a valid provider")) return False if not username: self.set_status(self.tr("Please provide a valid username")) return False if not password: self.set_status(self.tr("Please provide a valid password")) return False return True def _set_logging_in(self): """ Set the status of the widget to "Logging in". """ self.set_status(self.tr("Logging in..."), error=False) self.set_enabled(False) self.ui.clblErrorMsg.hide() def _save_credentials(self): """ If the user asked to remember the credentials, we save them into the keyring. """ provider = self._provider username = self.get_user() password = self.get_password() self._settings.set_provider(provider) if self.get_remember() and has_keyring(): # in the keyring and in the settings # we store the value 'usename@provider' full_user_id = make_address(username, provider).encode("utf8") try: keyring = get_keyring() keyring.set_password(self.KEYRING_KEY, full_user_id, password.encode("utf8")) # Only save the username if it was saved correctly in # the keyring self._settings.set_user(full_user_id) except KeyringInitError as e: logger.error("Failed to unlock keyring, maybe the user " "cancelled the operation {0!r}".format(e)) except Exception as e: logger.exception("Problem saving data to keyring. %r" % (e,)) def do_login(self): """ Start the login sequence. We check that we have the needed fields to do the actual login: provider, username and password. If everything is ok we perform the login. Note that the actual login won't be started if you set the `wait_to_login` flag, it will be scheduled to get started when you set that flag to False. :return: True if the login sequence started, False otherwise. :rtype: bool """ ok = self._provider and self.get_user() and self.get_password() if ok: self._do_login() return bool(ok) def _do_login(self): """ Start the login sequence. """ if self._state.wait_to_login: logger.debug("Login delayed, waiting...") self._state.login_waiting = True self.ui.btnLogin.setEnabled(False) self.ui.btnLogin.setText(self.tr("Waiting...")) # explicitly process events to display the button's text change. QtCore.QCoreApplication.processEvents(0, 10) return else: self._state.login_waiting = False self.ui.btnLogin.setEnabled(True) self.login_start.emit() provider = self._provider if flags.OFFLINE: self._do_offline_login() return # connect to the backend signals, remember to disconnect after login. self._backend_connect() if self._check_login(): self._set_logging_in() self._save_credentials() self._backend.provider_setup(provider=provider) def _do_cancel(self): logger.debug("Cancelling log in.") self._backend.provider_cancel_setup() self._backend.user_cancel_login() self._set_logged_out() def _set_login_cancelled(self): """ TRIGGERS: Signaler.prov_cancelled_setup Re-enable the login widget and display a message for the cancelled operation. """ self.set_status(self.tr("Log in cancelled by the user.")) self.set_enabled(True) def _provider_setup_intermediate(self, data): """ TRIGGERS: self._backend.signaler.prov_name_resolution self._backend.signaler.prov_https_connection Handle a possible problem during the provider setup process. If there was a problem, display it, otherwise it does nothing. """ if not data[PASSED_KEY]: logger.error(data[ERROR_KEY]) self._login_problem_provider() def _login_problem_provider(self): """ Warn the user about a problem with the provider during login. """ self.set_status(self.tr("Unable to login: Problem with provider")) self.set_enabled(True) def _load_provider_config(self, data): """ TRIGGERS: self._backend.signaler.prov_download_provider_info Once the provider config has been downloaded, start the second part of the bootstrapping sequence. :param data: result from the last stage of the backend.provider_setup() :type data: dict """ if not data[PASSED_KEY]: logger.error(data[ERROR_KEY]) self._login_problem_provider() return self._backend.provider_bootstrap(provider=self._provider) def _provider_config_loaded(self, data): """ TRIGGERS: self._backend.signaler.prov_check_api_certificate Once the provider configuration is loaded, this starts the SRP authentication """ if not data[PASSED_KEY]: logger.error(data[ERROR_KEY]) self._login_problem_provider() return self._backend.user_login(provider=self._provider, username=self.get_user(), password=self.get_password()) # TODO check this! def _do_offline_login(self): logger.debug("OFFLINE mode! bypassing remote login") # TODO reminder, we're not handling logout for offline mode. self._set_logged_in() self._logged_in_offline = True self._set_label_offline() self.login_offline_finished.emit() def _set_label_offline(self): """ Set the login label to reflect offline status. """ # TODO: figure out what widget to use for this. Maybe the window title? def _set_logged_in(self): """ Set the widgets to the logged in state. """ fullname = make_address(self.get_user(), self._provider) self._state.full_logged_username = fullname self.ui.login_widget.hide() self.ui.logged_widget.show() self.ui.lblUser.setText(fullname) def _authentication_finished(self): """ TRIGGERS: self._srp_auth.authentication_finished The SRP auth was successful, set the login status. """ self.set_status(self.tr("Succeeded"), error=False) self._set_logged_in() self.disconnect_and_untrack() if not flags.OFFLINE: self.login_finished.emit() def _authentication_error(self, msg): """ TRIGGERS: Signaler.srp_auth_error Signaler.srp_auth_server_error Signaler.srp_auth_connection_error Signaler.srp_auth_bad_user_or_password Handle the authentication errors. :param msg: the message to show to the user. :type msg: unicode """ self.set_status(msg) self.set_enabled(True) self.login_failed.emit() def _set_logged_out(self): """ Set the widgets to the logged out state. """ # TODO consider "logging out offline" too... how that would be ??? self._state.full_logged_username = None self.ui.login_widget.show() self.ui.logged_widget.hide() self.set_password("") self.set_enabled(True) self.set_status("", error=False) def do_logout(self): """ TRIGGER: self.ui.btnLogout.clicked Start the logout sequence and set the widgets to the "logging out" state. """ if self._state.full_logged_username is not None: self._set_logging_out() self._backend.user_logout() else: logger.debug("Not logged in.") def _set_logging_out(self, logging_out=True): """ Set the status of the logout button. logging_out == True: button text -> "Logging out..." button enabled -> False logging_out == False: button text -> "Logout button enabled -> True :param logging_out: wether we are logging out or not. :type logging_out: bool """ if logging_out: self.ui.btnLogout.setText(self.tr("Logging out...")) self.ui.btnLogout.setEnabled(False) else: self.ui.btnLogout.setText(self.tr("Logout")) self.ui.btnLogout.setEnabled(True) self.ui.clblErrorMsg.hide() def _logout_error(self): """ TRIGGER: self._srp_auth.logout_error Inform the user about a logout error. """ self._set_logging_out(False) self.set_status(self.tr("Something went wrong with the logout.")) def _logout_ok(self): """ TRIGGER: self._srp_auth.logout_ok Switch the stackedWidget back to the login stage after logging out. """ self._set_logging_out(False) self._set_logged_out() self.logged_out.emit() def load_user_from_keyring(self, saved_user): """ Try to load a user from the keyring. :param saved_user: the saved username as user@domain :type saved_user: unicode :return: True if the user was loaded successfully, False otherwise. :rtype: bool """ leap_assert_type(saved_user, unicode) try: username, domain = saved_user.split('@') except ValueError as e: # if the saved_user does not contain an '@' logger.error('Username@provider malformed. %r' % (e, )) return False self.set_user(username) self.set_remember(True) saved_password = None try: keyring = get_keyring() u_user = saved_user.encode("utf8") saved_password = keyring.get_password(self.KEYRING_KEY, u_user) except ValueError as e: logger.debug("Incorrect Password. %r." % (e,)) except KeyringInitError as e: logger.error("Failed to unlock keyring, maybe the user " "cancelled the operation {0!r}".format(e)) if saved_password is not None: self.set_password(saved_password) return True return False def _backend_connect(self): """ Connect to backend signals. We track the signals in order to disconnect them on demand. """ sig = self._leap_signaler conntrack = self.connect_and_track auth_err = self._authentication_error # provider_setup signals conntrack(sig.prov_name_resolution, self._provider_setup_intermediate) conntrack(sig.prov_https_connection, self._provider_setup_intermediate) conntrack(sig.prov_download_provider_info, self._load_provider_config) # provider_bootstrap signals conntrack(sig.prov_download_ca_cert, self._provider_setup_intermediate) # XXX missing check_ca_fingerprint connection conntrack(sig.prov_check_api_certificate, self._provider_config_loaded) conntrack(sig.prov_problem_with_provider, self._login_problem_provider) conntrack(sig.prov_cancelled_setup, self._set_login_cancelled) # Login signals conntrack(sig.srp_auth_ok, self._authentication_finished) auth_error = lambda: auth_err(self.tr("Unknown error.")) conntrack(sig.srp_auth_error, auth_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: auth_err(self.tr( "Could not establish a connection.")) conntrack(sig.srp_auth_connection_error, auth_connection_error) 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 sig.srp_logout_ok.connect(self._logout_ok) sig.srp_logout_error.connect(self._logout_error)
class EIPPreferencesWindow(QtGui.QDialog): """ Window that displays the EIP preferences. """ def __init__(self, parent): """ :param parent: parent object of the EIPPreferencesWindow. :parent type: QWidget """ QtGui.QDialog.__init__(self, parent) self.AUTOMATIC_GATEWAY_LABEL = self.tr("Automatic") self._settings = LeapSettings() # Load UI self.ui = Ui_EIPPreferences() self.ui.setupUi(self) self.ui.lblProvidersGatewayStatus.setVisible(False) self.ui.lblAutoStartEIPStatus.setVisible(False) # Connections self.ui.cbProvidersGateway.currentIndexChanged[unicode].connect( self._populate_gateways) self.ui.cbGateways.currentIndexChanged[unicode].connect( lambda x: self.ui.lblProvidersGatewayStatus.setVisible(False)) self.ui.cbProvidersEIP.currentIndexChanged[unicode].connect( lambda x: self.ui.lblAutoStartEIPStatus.setVisible(False)) self.ui.cbAutoStartEIP.toggled.connect( lambda x: self.ui.lblAutoStartEIPStatus.setVisible(False)) self.ui.pbSaveAutoStartEIP.clicked.connect(self._save_auto_start_eip) self._add_configured_providers() # Load auto start EIP settings self.ui.cbAutoStartEIP.setChecked(self._settings.get_autostart_eip()) default_provider = self._settings.get_defaultprovider() idx = self.ui.cbProvidersEIP.findText(default_provider) self.ui.cbProvidersEIP.setCurrentIndex(idx) def _save_auto_start_eip(self): """ SLOT TRIGGER: self.ui.cbAutoStartEIP.toggled Saves the automatic start of EIP user preference. """ default_provider = self.ui.cbProvidersEIP.currentText() enabled = self.ui.cbAutoStartEIP.isChecked() self._settings.set_autostart_eip(enabled) self._settings.set_defaultprovider(default_provider) self.ui.lblAutoStartEIPStatus.show() logger.debug('Auto start EIP saved: {0} {1}.'.format( default_provider, enabled)) def _set_providers_gateway_status(self, status, success=False, error=False): """ Sets the status label for the gateway change. :param status: status message to display, can be HTML :type status: str :param success: is set to True if we should display the message as green :type success: bool :param error: is set to True if we should display the message as red :type error: bool """ if success: status = "<font color='green'><b>%s</b></font>" % (status,) elif error: status = "<font color='red'><b>%s</b></font>" % (status,) self.ui.lblProvidersGatewayStatus.setVisible(True) self.ui.lblProvidersGatewayStatus.setText(status) def _add_configured_providers(self): """ Add the client's configured providers to the providers combo boxes. """ self.ui.cbProvidersGateway.clear() self.ui.cbProvidersEIP.clear() providers = self._settings.get_configured_providers() if not providers: self.ui.gbAutomaticEIP.setEnabled(False) self.ui.gbGatewaySelector.setEnabled(False) return for provider in providers: self.ui.cbProvidersGateway.addItem(provider) self.ui.cbProvidersEIP.addItem(provider) def _save_selected_gateway(self, provider): """ SLOT TRIGGERS: self.ui.pbSaveGateway.clicked Saves the new gateway setting to the configuration file. :param provider: the provider config that we need to save. :type provider: str """ gateway = self.ui.cbGateways.currentText() if gateway == self.AUTOMATIC_GATEWAY_LABEL: gateway = self._settings.GATEWAY_AUTOMATIC else: idx = self.ui.cbGateways.currentIndex() gateway = self.ui.cbGateways.itemData(idx) self._settings.set_selected_gateway(provider, gateway) msg = self.tr( "Gateway settings for provider '{0}' saved.").format(provider) self._set_providers_gateway_status(msg, success=True) def _populate_gateways(self, domain): """ SLOT TRIGGERS: self.ui.cbProvidersGateway.currentIndexChanged[unicode] Loads the gateways that the provider provides into the UI for the user to select. :param domain: the domain of the provider to load gateways from. :type domain: str """ # We hide the maybe-visible status label after a change self.ui.lblProvidersGatewayStatus.setVisible(False) if not domain: return try: # disconnect previously connected save method self.ui.pbSaveGateway.clicked.disconnect() except RuntimeError: pass # Signal was not connected # set the proper connection for the 'save' button save_gateway = partial(self._save_selected_gateway, domain) self.ui.pbSaveGateway.clicked.connect(save_gateway) eip_config = EIPConfig() provider_config = ProviderConfig.get_provider_config(domain) eip_config_path = os.path.join("leap", "providers", domain, "eip-service.json") api_version = provider_config.get_api_version() eip_config.set_api_version(api_version) eip_loaded = eip_config.load(eip_config_path) if not eip_loaded or provider_config is None: self._set_providers_gateway_status( self.tr("There was a problem with configuration files."), error=True) return gateways = VPNGatewaySelector(eip_config).get_gateways_list() logger.debug(gateways) self.ui.cbGateways.clear() self.ui.cbGateways.addItem(self.AUTOMATIC_GATEWAY_LABEL) # Add the available gateways and # select the one stored in configuration file. selected_gateway = self._settings.get_selected_gateway(domain) index = 0 for idx, (gw_name, gw_ip) in enumerate(gateways): gateway = "{0} ({1})".format(gw_name, gw_ip) self.ui.cbGateways.addItem(gateway, gw_ip) if gw_ip == selected_gateway: index = idx + 1 self.ui.cbGateways.setCurrentIndex(index)
def get_vpn_command( self, eipconfig=None, providerconfig=None, socket_host=None, socket_port="unix", openvpn_verb=1 ): """ Returns the platform dependant vpn launching command Might raise VPNException. :param eipconfig: eip configuration object :type eipconfig: EIPConfig :param providerconfig: provider specific configuration :type providerconfig: ProviderConfig :param socket_host: either socket path (unix) or socket IP :type socket_host: str :param socket_port: either string "unix" if it's a unix socket, or port otherwise :type socket_port: str :param openvpn_verb: openvpn verbosity wanted :type openvpn_verb: int :return: A VPN command ready to be launched :rtype: list """ leap_assert(eipconfig, "We need an eip config") leap_assert_type(eipconfig, EIPConfig) leap_assert(providerconfig, "We need a provider config") leap_assert_type(providerconfig, ProviderConfig) leap_assert(socket_host, "We need a socket host!") leap_assert(socket_port, "We need a socket port!") if not self.maybe_kextloaded(): raise EIPNoTunKextLoaded kwargs = {} if ProviderConfig.standalone: kwargs["path_extension"] = os.path.join(providerconfig.get_path_prefix(), "..", "apps", "eip") openvpn_possibilities = which(self.OPENVPN_BIN, **kwargs) if len(openvpn_possibilities) == 0: raise OpenVPNNotFoundException() openvpn = first(openvpn_possibilities) args = [openvpn] args += ["--setenv", "LEAPOPENVPN", "1"] if openvpn_verb is not None: args += ["--verb", "%d" % (openvpn_verb,)] gateways = [] leap_settings = LeapSettings(ProviderConfig.standalone) domain = providerconfig.get_domain() gateway_conf = leap_settings.get_selected_gateway(domain) if gateway_conf == leap_settings.GATEWAY_AUTOMATIC: gateway_selector = VPNGatewaySelector(eipconfig) gateways = gateway_selector.get_gateways() else: gateways = [gateway_conf] if not gateways: logger.error("No gateway was found!") raise VPNLauncherException(self.tr("No gateway was found!")) logger.debug("Using gateways ips: {0}".format(", ".join(gateways))) for gw in gateways: args += ["--remote", gw, "1194", "udp"] args += [ "--client", "--dev", "tun", ############################################################## # persist-tun makes ping-restart fail because it leaves a # broken routing table ############################################################## # '--persist-tun', "--persist-key", "--tls-client", "--remote-cert-tls", "server", ] openvpn_configuration = eipconfig.get_openvpn_configuration() for key, value in openvpn_configuration.items(): args += ["--%s" % (key,), value] user = getpass.getuser() ############################################################## # The down-root plugin fails in some situations, so we don't # drop privs for the time being ############################################################## # args += [ # '--user', user, # '--group', grp.getgrgid(os.getgroups()[-1]).gr_name # ] if socket_port == "unix": args += ["--management-client-user", user] args += ["--management-signal", "--management", socket_host, socket_port, "--script-security", "2"] if _has_updown_scripts(self.UP_SCRIPT): args += ["--up", '"%s"' % (self.UP_SCRIPT,)] if _has_updown_scripts(self.DOWN_SCRIPT): args += ["--down", '"%s"' % (self.DOWN_SCRIPT,)] # should have the down script too if _has_updown_scripts(self.OPENVPN_DOWN_PLUGIN): args += [ ########################################################### # For the time being we are disabling the usage of the # down-root plugin, because it doesn't quite work as # expected (i.e. it doesn't run route -del as root # when finishing, so it fails to properly # restart/quit) ########################################################### # '--plugin', self.OPENVPN_DOWN_PLUGIN, # '\'%s\'' % self.DOWN_SCRIPT ] # we set user to be passed to the up/down scripts args += ["--setenv", "LEAPUSER", "%s" % (user,)] args += [ "--cert", eipconfig.get_client_cert_path(providerconfig), "--key", eipconfig.get_client_cert_path(providerconfig), "--ca", providerconfig.get_ca_cert_path(), ] command, cargs = self.get_cocoasudo_ovpn_cmd() cmd_args = cargs + args logger.debug("Running VPN with command:") logger.debug("%s %s" % (command, " ".join(cmd_args))) return [command] + cmd_args
def get_vpn_command( self, eipconfig=None, providerconfig=None, socket_host=None, socket_port="9876", openvpn_verb=1 ): """ Returns the platform dependant vpn launching command. It will look for openvpn in the regular paths and algo in path_prefix/apps/eip/ (in case standalone is set) Might raise VPNException. :param eipconfig: eip configuration object :type eipconfig: EIPConfig :param providerconfig: provider specific configuration :type providerconfig: ProviderConfig :param socket_host: either socket path (unix) or socket IP :type socket_host: str :param socket_port: either string "unix" if it's a unix socket, or port otherwise :type socket_port: str :param openvpn_verb: the openvpn verbosity wanted :type openvpn_verb: int :return: A VPN command ready to be launched :rtype: list """ leap_assert(eipconfig, "We need an eip config") leap_assert_type(eipconfig, EIPConfig) leap_assert(providerconfig, "We need a provider config") leap_assert_type(providerconfig, ProviderConfig) leap_assert(socket_host, "We need a socket host!") leap_assert(socket_port, "We need a socket port!") leap_assert(socket_port != "unix", "We cannot use unix sockets in windows!") openvpn_possibilities = which( self.OPENVPN_BIN, path_extension=os.path.join(providerconfig.get_path_prefix(), "..", "apps", "eip") ) if len(openvpn_possibilities) == 0: raise OpenVPNNotFoundException() openvpn = first(openvpn_possibilities) args = [] args += ["--setenv", "LEAPOPENVPN", "1"] if openvpn_verb is not None: args += ["--verb", "%d" % (openvpn_verb,)] gateways = [] leap_settings = LeapSettings(ProviderConfig.standalone) domain = providerconfig.get_domain() gateway_conf = leap_settings.get_selected_gateway(domain) if gateway_conf == leap_settings.GATEWAY_AUTOMATIC: gateway_selector = VPNGatewaySelector(eipconfig) gateways = gateway_selector.get_gateways() else: gateways = [gateway_conf] if not gateways: logger.error("No gateway was found!") raise VPNLauncherException(self.tr("No gateway was found!")) logger.debug("Using gateways ips: {0}".format(", ".join(gateways))) for gw in gateways: args += ["--remote", gw, "1194", "udp"] args += [ "--client", "--dev", "tun", ############################################################## # persist-tun makes ping-restart fail because it leaves a # broken routing table ############################################################## # '--persist-tun', "--persist-key", "--tls-client", # We make it log to a file because we cannot attach to the # openvpn process' stdout since it's a process with more # privileges than we are "--log-append", "eip.log", "--remote-cert-tls", "server", ] openvpn_configuration = eipconfig.get_openvpn_configuration() for key, value in openvpn_configuration.items(): args += ["--%s" % (key,), value] ############################################################## # The down-root plugin fails in some situations, so we don't # drop privs for the time being ############################################################## # args += [ # '--user', getpass.getuser(), # #'--group', grp.getgrgid(os.getgroups()[-1]).gr_name # ] args += ["--management-signal", "--management", socket_host, socket_port, "--script-security", "2"] args += [ "--cert", eipconfig.get_client_cert_path(providerconfig), "--key", eipconfig.get_client_cert_path(providerconfig), "--ca", providerconfig.get_ca_cert_path(), ] logger.debug("Running VPN with command:") logger.debug("%s %s" % (openvpn, " ".join(args))) return [openvpn] + args
class PreferencesEmailPage(PreferencesPage): def __init__(self, parent, account, app): """ :param parent: parent object of the PreferencesWindow. :parent type: QWidget :param account: user account (user + provider or just provider) :type account: Account :param app: the current App object :type app: App """ PreferencesPage.__init__(self, parent, account, app) self.settings = LeapSettings() self.ui = Ui_PreferencesEmailPage() self.ui.setupUi(self) # the only way to set the tab titles is to re-add them: self.ui.email_tabs.addTab(self.ui.config_tab, self.tr("Mail Client")) self.ui.email_tabs.addTab(self.ui.my_key_tab, self.tr("My Key")) self.ui.email_tabs.addTab(self.ui.other_keys_tab, self.tr("Other Keys")) # set mail client configuration help text lang = QtCore.QLocale.system().name().replace('_', '-') thunderbird_extension_url = \ "https://addons.mozilla.org/{0}/" \ "thunderbird/addon/bitmask/".format(lang) self.ui.thunderbird_label.setText(self.tr( "For Thunderbird, you can use the Bitmask extension. " "Search for \"Bitmask\" in the add-on manager or " "download it from <a href='{0}'>addons.mozilla.org</a>.".format( thunderbird_extension_url))) self.ui.mail_client_label.setText(self.tr( "Alternatively, you can manually configure your mail client to " "use Bitmask with these options:")) self.ui.webmail_label.setText(self.tr( "Bitmask Mail is an integrated mail client based " "on <a href='https://pixelated-project.org/'>Pixelated " "User Agent</a>. If enabled, any user on your device " "can read your mail by opening http://localhost:9090")) self.ui.keys_table.horizontalHeader().setResizeMode( 0, QtGui.QHeaderView.Stretch) self.setup_connections() def setup_connections(self): """ connect signals """ self.app.signaler.keymanager_key_details.connect(self._key_details) self.app.signaler.keymanager_keys_list.connect( self._keymanager_keys_list) self.app.signaler.keymanager_export_ok.connect( self._keymanager_export_ok) self.app.signaler.keymanager_export_error.connect( self._keymanager_export_error) self.ui.import_button.clicked.connect(self._import_keys) self.ui.export_button.clicked.connect(self._export_keys) self.ui.webmail_checkbox.stateChanged.connect(self._toggle_webmail) def teardown_connections(self): """ disconnect signals """ self.app.signaler.keymanager_key_details.disconnect(self._key_details) self.app.signaler.keymanager_export_ok.disconnect( self._keymanager_export_ok) self.app.signaler.keymanager_export_error.disconnect( self._keymanager_export_error) def showEvent(self, event): """ called whenever this widget is shown """ self.ui.keys_table.clearContents() if self.account.username is None: self.ui.email_tabs.setVisible(False) self.ui.message_label.setVisible(True) self.ui.message_label.setText( self.tr('You must be logged in to edit email settings.')) else: webmail_enabled = self.settings.get_pixelmail_enabled() self.ui.webmail_checkbox.setChecked(webmail_enabled) if not HAS_PIXELATED: self.ui.webmail_box.setVisible(False) self.ui.import_button.setVisible(False) # hide this until working self.ui.message_label.setVisible(False) self.ui.email_tabs.setVisible(True) smtp_port = 2013 self.ui.imap_port_edit.setText(str(IMAP_PORT)) self.ui.imap_host_edit.setText("127.0.0.1") self.ui.smtp_port_edit.setText(str(smtp_port)) self.ui.smtp_host_edit.setText("127.0.0.1") self.ui.username_edit.setText(self.account.address) self.ui.password_edit.setText( self.app.service_tokens.get('mail_auth', '')) self.app.backend.keymanager_list_keys() self.app.backend.keymanager_get_key_details( username=self.account.address) def _key_details(self, details): """ Trigger by signal: keymanager_key_details Set the current user's key details into the gui. """ self.ui.fingerprint_edit.setPlainText( self._format_fingerprint(details["fingerprint"])) self.ui.expiration_edit.setText(details["expiry_date"]) self.ui.uid_edit.setText(" ".join(details["uids"])) self.ui.public_key_edit.setPlainText(details["key_data"]) def _format_fingerprint(self, fingerprint): """ formats an openpgp fingerprint in a manner similar to what gpg produces, wrapped to two lines. """ fp = fingerprint.upper() fp_list = [fp[i:i + 4] for i in range(0, len(fp), 4)] fp_wrapped = " ".join(fp_list[0:5]) + "\n" + " ".join(fp_list[5:10]) return fp_wrapped def _export_keys(self): """ Exports the user's key pair. """ file_name, filtr = QtGui.QFileDialog.getSaveFileName( self, self.tr("Save private key file"), filter="*.pem", options=QtGui.QFileDialog.DontUseNativeDialog) if file_name: if not file_name.endswith('.pem'): file_name += '.pem' self.app.backend.keymanager_export_keys( username=self.account.address, filename=file_name) else: logger.debug('Export canceled by the user.') def _keymanager_export_ok(self): """ TRIGGERS: Signaler.keymanager_export_ok Notify the user that the key export went 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.")) 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.")) def _import_keys(self): """ not yet supported """ def _keymanager_keys_list(self, keys): """ 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 """ for key in keys: row = self.ui.keys_table.rowCount() self.ui.keys_table.insertRow(row) self.ui.keys_table.setItem( row, 0, QtGui.QTableWidgetItem(" ".join(key["uids"]))) self.ui.keys_table.setItem( row, 1, QtGui.QTableWidgetItem(key["fingerprint"])) def _toggle_webmail(self, state): value = True if state == QtCore.Qt.Checked else False self.settings.set_pixelmail_enabled(value)
def check_missing(): """ Check for the need of installing missing scripts, and raises a dialog to ask user for permission to do it. """ config = LeapSettings() complain_missing = False alert_missing = config.get_alert_missing_scripts() if alert_missing and not flags.STANDALONE: # We refuse to install missing stuff if not running with standalone # flag. Right now we rely on the flag alone, but we can disable this # by overwriting some constant from within the Debian package. alert_missing = False complain_missing = True launcher = get_vpn_launcher() missing_scripts = launcher.missing_updown_scripts() missing_other = launcher.missing_other_files() if missing_scripts: logger.warning("Missing scripts: %s" % (missing_scripts)) if missing_other: logger.warning("Missing other files: %s" % (missing_other)) missing_some = missing_scripts or missing_other if alert_missing and missing_some: msg = get_missing_helpers_dialog() ret = msg.exec_() if ret == QtGui.QMessageBox.Yes: install_missing_fun = globals().get( "_%s_install_missing_scripts" % (_system.lower(), ), None) if not install_missing_fun: logger.warning("Installer not found for platform %s." % (_system, )) return # XXX maybe move constants to fun ok = install_missing_fun(HELPERS_BADEXEC_MSG, HELPERS_NOTFOUND_MSG) if not ok: msg = QtGui.QMessageBox() msg.setWindowTitle(msg.tr("Problem installing files")) msg.setText(msg.tr('Some of the files could not be copied.')) msg.setIcon(QtGui.QMessageBox.Warning) msg.exec_() elif ret == QtGui.QMessageBox.No: logger.debug("Not installing missing scripts, " "user decided to ignore our warning.") init_signals.eip_missing_helpers.emit() elif ret == QtGui.QMessageBox.Rejected: logger.debug("Setting alert_missing_scripts to False, we will not " "ask again") init_signals.eip_missing_helpers.emit() config.set_alert_missing_scripts(False) if complain_missing and missing_some: missing = missing_scripts + missing_other msg = _get_missing_complain_dialog(missing) ret = msg.exec_() # If there is some missing file and we don't want to complain, we emit the # 'missing helpers' signal so the eip status can show that some files are # missing. if missing_some and not alert_missing and not complain_missing: init_signals.eip_missing_helpers.emit()
class LoginWidget(QtGui.QWidget, SignalTracker): """ Login widget that emits signals to display the wizard or to perform login. """ login_start = QtCore.Signal() login_finished = QtCore.Signal() login_offline_finished = QtCore.Signal() login_failed = QtCore.Signal() logged_out = QtCore.Signal() MAX_STATUS_WIDTH = 40 # Keyring KEYRING_KEY = "bitmask" def __init__(self, backend, signaler, parent=None): """ Constructs the LoginWidget. :param backend: Backend being used :type backend: Backend :param signaler: Object in charge of handling communication back to the frontend :type signaler: Signaler :param parent: The parent widget for this widget :type parent: QWidget or None """ QtGui.QWidget.__init__(self, parent) SignalTracker.__init__(self) self.ui = Ui_LoginWidget() self.ui.setupUi(self) self.ui.chkRemember.stateChanged.connect(self._remember_state_changed) self.ui.chkRemember.setEnabled(has_keyring()) self.ui.lnUser.textChanged.connect(self._credentials_changed) self.ui.lnPassword.textChanged.connect(self._credentials_changed) self.ui.btnLogin.clicked.connect(self._do_login) self.ui.btnLogout.clicked.connect(self.do_logout) self.ui.lnUser.setValidator( QtGui.QRegExpValidator(QtCore.QRegExp(USERNAME_REGEX), self)) self.ui.clblErrorMsg.hide() self.ui.clblErrorMsg.clicked.connect(self.ui.clblErrorMsg.hide) self.ui.lnUser.textEdited.connect(self.ui.clblErrorMsg.hide) self.ui.lnPassword.textEdited.connect(self.ui.clblErrorMsg.hide) self._settings = LeapSettings() self._backend = backend self._leap_signaler = signaler # the selected provider that we'll use to login self._provider = None self._state = LoginState() self._set_logged_out() def _remember_state_changed(self, state): """ Save the remember state in the LeapSettings. :param state: the current state of the check box. :type state: int """ # The possible state values of the checkbox (from QtCore.Qt.CheckState) # are: Checked, Unchecked and PartiallyChecked self._settings.set_remember(state == QtCore.Qt.Checked) def _credentials_changed(self, text): """ TRIGGER: self.ui.lnUser.textChanged self.ui.lnPassword.textChanged Update the 'enabled' status of the login button depending if we have all the fields needed set. """ enabled = self._provider and self.get_user() and self.get_password() enabled = bool(enabled) # provider can be None self.ui.btnLogin.setEnabled(enabled) def wait_for_login(self, wait): """ Set the wait flag to True/False so the next time that a login action is requested it will wait or not. If we set the wait to True and we have paused a login request before, this will trigger a login action. :param wait: whether we should wait or not on the next login request. :type wait: bool """ self._state.wait_to_login = wait if not wait and self._state.login_waiting: logger.debug("No more waiting, triggering login sequence.") self._do_login() def set_provider(self, provider): """ Set the provider to use in the login sequence. :param provider: the provider to use. :type provider: unicode """ self._provider = provider def set_remember(self, value): """ Check the remember user and password checkbox :param value: True to mark it checked, False otherwise :type value: bool """ self.ui.chkRemember.setChecked(value) def get_remember(self): """ Returns the remember checkbox state :rtype: bool """ return self.ui.chkRemember.isChecked() def set_user(self, user): """ Sets the user and focuses on the next field, password. :param user: user to set the field to :type user: str """ self.ui.lnUser.setText(user) self._focus_password() def get_user(self): """ Return the user that appears in the widget. :rtype: unicode """ return self.ui.lnUser.text() def get_logged_user(self): """ Return the current logged user or None if no user is logged in. The return value has the format: 'user@provider' :rtype: unicode or None """ return self._state.full_logged_username def set_password(self, password): """ Sets the password for the widget :param password: password to set :type password: unicode """ self.ui.lnPassword.setText(password) def get_password(self): """ Returns the password that appears in the widget :rtype: unicode """ return self.ui.lnPassword.text() def set_status(self, status, error=True): """ Sets the status label at the login stage to status :param status: status message :type status: str """ if len(status) > self.MAX_STATUS_WIDTH: status = status[:self.MAX_STATUS_WIDTH] + "..." if error: self.ui.clblErrorMsg.show() self.ui.clblErrorMsg.setText(status) else: self.ui.lblStatus.setText(status) def set_enabled(self, enabled=False): """ Enables or disables all the login widgets :param enabled: wether they should be enabled or not :type enabled: bool """ self.ui.lnUser.setEnabled(enabled) self.ui.lnPassword.setEnabled(enabled) self.ui.chkRemember.setEnabled(enabled and has_keyring()) self._set_cancel(not enabled) def set_logout_btn_enabled(self, enabled): """ Enables or disables the logout button. :param enabled: wether they should be enabled or not :type enabled: bool """ self.ui.btnLogout.setEnabled(enabled) def _set_cancel(self, enabled=False): """ Enables or disables the cancel action in the "log in" process. :param enabled: wether it should be enabled or not :type enabled: bool """ text = self.tr("Cancel") login_or_cancel = self._do_cancel hide_remember = enabled if not enabled: text = self.tr("Log In") login_or_cancel = self._do_login self.ui.btnLogin.setText(text) self.ui.btnLogin.clicked.disconnect() self.ui.btnLogin.clicked.connect(login_or_cancel) self.ui.chkRemember.setVisible(not hide_remember) self.ui.lblStatus.setVisible(hide_remember) def _focus_password(self): """ Focuses in the password lineedit """ self.ui.lnPassword.setFocus() def _check_login(self): """ Check that we have the needed fields to do the actual login: provider, username and password. :return: True if everything's good to go, False otherwise. :rtype: bool """ provider = self._provider username = self.get_user() password = self.get_password() if not provider: self.set_status(self.tr("Please select a valid provider")) return False if not username: self.set_status(self.tr("Please provide a valid username")) return False if not password: self.set_status(self.tr("Please provide a valid password")) return False return True def _set_logging_in(self): """ Set the status of the widget to "Logging in". """ self.set_status(self.tr("Logging in..."), error=False) self.set_enabled(False) self.ui.clblErrorMsg.hide() def _save_credentials(self): """ If the user asked to remember the credentials, we save them into the keyring. """ provider = self._provider username = self.get_user() password = self.get_password() self._settings.set_provider(provider) if self.get_remember() and has_keyring(): # in the keyring and in the settings # we store the value 'usename@provider' full_user_id = make_address(username, provider).encode("utf8") try: keyring = get_keyring() keyring.set_password(self.KEYRING_KEY, full_user_id, password.encode("utf8")) # Only save the username if it was saved correctly in # the keyring self._settings.set_user(full_user_id) except KeyringInitError as e: logger.error("Failed to unlock keyring, maybe the user " "cancelled the operation {0!r}".format(e)) except Exception as e: logger.exception("Problem saving data to keyring. %r" % (e, )) def do_login(self): """ Start the login sequence. We check that we have the needed fields to do the actual login: provider, username and password. If everything is ok we perform the login. Note that the actual login won't be started if you set the `wait_to_login` flag, it will be scheduled to get started when you set that flag to False. :return: True if the login sequence started, False otherwise. :rtype: bool """ ok = self._provider and self.get_user() and self.get_password() if ok: self._do_login() return bool(ok) def _do_login(self): """ Start the login sequence. """ if self._state.wait_to_login: logger.debug("Login delayed, waiting...") self._state.login_waiting = True self.ui.btnLogin.setEnabled(False) self.ui.btnLogin.setText(self.tr("Waiting...")) # explicitly process events to display the button's text change. QtCore.QCoreApplication.processEvents(0, 10) return else: self._state.login_waiting = False self.ui.btnLogin.setEnabled(True) self.login_start.emit() provider = self._provider if flags.OFFLINE: self._do_offline_login() return # connect to the backend signals, remember to disconnect after login. self._backend_connect() if self._check_login(): self._set_logging_in() self._save_credentials() self._backend.provider_setup(provider=provider) def _do_cancel(self): logger.debug("Cancelling log in.") self._backend.provider_cancel_setup() self._backend.user_cancel_login() self._set_logged_out() def _set_login_cancelled(self): """ TRIGGERS: Signaler.prov_cancelled_setup Re-enable the login widget and display a message for the cancelled operation. """ self.set_status(self.tr("Log in cancelled by the user.")) self.set_enabled(True) def _provider_setup_intermediate(self, data): """ TRIGGERS: self._backend.signaler.prov_name_resolution self._backend.signaler.prov_https_connection Handle a possible problem during the provider setup process. If there was a problem, display it, otherwise it does nothing. """ if not data[PASSED_KEY]: logger.error(data[ERROR_KEY]) self._login_problem_provider() def _login_problem_provider(self): """ Warn the user about a problem with the provider during login. """ self.set_status(self.tr("Unable to login: Problem with provider")) self.set_enabled(True) def _load_provider_config(self, data): """ TRIGGERS: self._backend.signaler.prov_download_provider_info Once the provider config has been downloaded, start the second part of the bootstrapping sequence. :param data: result from the last stage of the backend.provider_setup() :type data: dict """ if not data[PASSED_KEY]: logger.error(data[ERROR_KEY]) self._login_problem_provider() return self._backend.provider_bootstrap(provider=self._provider) def _provider_config_loaded(self, data): """ TRIGGERS: self._backend.signaler.prov_check_api_certificate Once the provider configuration is loaded, this starts the SRP authentication """ if not data[PASSED_KEY]: logger.error(data[ERROR_KEY]) self._login_problem_provider() return self._backend.user_login(provider=self._provider, username=self.get_user(), password=self.get_password()) # TODO check this! def _do_offline_login(self): logger.debug("OFFLINE mode! bypassing remote login") # TODO reminder, we're not handling logout for offline mode. self._set_logged_in() self._logged_in_offline = True self._set_label_offline() self.login_offline_finished.emit() def _set_label_offline(self): """ Set the login label to reflect offline status. """ # TODO: figure out what widget to use for this. Maybe the window title? def _set_logged_in(self): """ Set the widgets to the logged in state. """ fullname = make_address(self.get_user(), self._provider) self._state.full_logged_username = fullname self.ui.login_widget.hide() self.ui.logged_widget.show() self.ui.lblUser.setText(fullname) def _authentication_finished(self): """ TRIGGERS: self._srp_auth.authentication_finished The SRP auth was successful, set the login status. """ self.set_status(self.tr("Succeeded"), error=False) self._set_logged_in() self.disconnect_and_untrack() if not flags.OFFLINE: self.login_finished.emit() def _authentication_error(self, msg): """ TRIGGERS: Signaler.srp_auth_error Signaler.srp_auth_server_error Signaler.srp_auth_connection_error Signaler.srp_auth_bad_user_or_password Handle the authentication errors. :param msg: the message to show to the user. :type msg: unicode """ self.set_status(msg) self.set_enabled(True) self.login_failed.emit() def _set_logged_out(self): """ Set the widgets to the logged out state. """ # TODO consider "logging out offline" too... how that would be ??? self._state.full_logged_username = None self.ui.login_widget.show() self.ui.logged_widget.hide() self.set_password("") self.set_enabled(True) self.set_status("", error=False) def do_logout(self): """ TRIGGER: self.ui.btnLogout.clicked Start the logout sequence and set the widgets to the "logging out" state. """ if self._state.full_logged_username is not None: self._set_logging_out() self._backend.user_logout() else: logger.debug("Not logged in.") def _set_logging_out(self, logging_out=True): """ Set the status of the logout button. logging_out == True: button text -> "Logging out..." button enabled -> False logging_out == False: button text -> "Logout button enabled -> True :param logging_out: wether we are logging out or not. :type logging_out: bool """ if logging_out: self.ui.btnLogout.setText(self.tr("Logging out...")) self.ui.btnLogout.setEnabled(False) else: self.ui.btnLogout.setText(self.tr("Logout")) self.ui.btnLogout.setEnabled(True) self.ui.clblErrorMsg.hide() def _logout_error(self): """ TRIGGER: self._srp_auth.logout_error Inform the user about a logout error. """ self._set_logging_out(False) self.set_status(self.tr("Something went wrong with the logout.")) def _logout_ok(self): """ TRIGGER: self._srp_auth.logout_ok Switch the stackedWidget back to the login stage after logging out. """ self._set_logging_out(False) self._set_logged_out() self.logged_out.emit() def load_user_from_keyring(self, saved_user): """ Try to load a user from the keyring. :param saved_user: the saved username as user@domain :type saved_user: unicode :return: True if the user was loaded successfully, False otherwise. :rtype: bool """ leap_assert_type(saved_user, unicode) try: username, domain = saved_user.split('@') except ValueError as e: # if the saved_user does not contain an '@' logger.error('Username@provider malformed. %r' % (e, )) return False self.set_user(username) self.set_remember(True) saved_password = None try: keyring = get_keyring() u_user = saved_user.encode("utf8") saved_password = keyring.get_password(self.KEYRING_KEY, u_user) except ValueError as e: logger.debug("Incorrect Password. %r." % (e, )) except KeyringInitError as e: logger.error("Failed to unlock keyring, maybe the user " "cancelled the operation {0!r}".format(e)) if saved_password is not None: self.set_password(saved_password) return True return False def _backend_connect(self): """ Connect to backend signals. We track the signals in order to disconnect them on demand. """ sig = self._leap_signaler conntrack = self.connect_and_track auth_err = self._authentication_error # provider_setup signals conntrack(sig.prov_name_resolution, self._provider_setup_intermediate) conntrack(sig.prov_https_connection, self._provider_setup_intermediate) conntrack(sig.prov_download_provider_info, self._load_provider_config) # provider_bootstrap signals conntrack(sig.prov_download_ca_cert, self._provider_setup_intermediate) # XXX missing check_ca_fingerprint connection conntrack(sig.prov_check_api_certificate, self._provider_config_loaded) conntrack(sig.prov_problem_with_provider, self._login_problem_provider) conntrack(sig.prov_cancelled_setup, self._set_login_cancelled) # Login signals conntrack(sig.srp_auth_ok, self._authentication_finished) auth_error = lambda: auth_err(self.tr("Unknown error.")) conntrack(sig.srp_auth_error, auth_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: auth_err( self.tr("Could not establish a connection.")) conntrack(sig.srp_auth_connection_error, auth_connection_error) 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 sig.srp_logout_ok.connect(self._logout_ok) sig.srp_logout_error.connect(self._logout_error)
def _load_configured_providers_with_pinned(self, pinned): """ Once we have the pinned providers from the backend, we continue setting everything up :param pinned: list of pinned providers :type pinned: list of str How the combobox items are arranged: ----------------------------------- First run: demo.bitmask.net -- pinned2.org pinned1.org pinned3.org After some usage: added-by-user.org pinned-but-then-used.org --- demo.bitmask.net pinned1.org pinned3.org pinned2.org In other words: * There are two sections. * Section one consists of all the providers that the user has used. If this is empty, than use demo.bitmask.net for this section. This list is sorted alphabetically. * Section two consists of all the pinned or 'pre seeded' providers, minus any providers that are now in section one. This last list is in random order. """ ls = LeapSettings() user_added = ls.get_configured_providers() if not user_added and not pinned: self.ui.rbExistingProvider.setEnabled(False) self.ui.label_8.setEnabled(False) # 'https://' label self.ui.cbProviders.setEnabled(False) return user_added.sort() if not user_added: user_added = [pinned.pop(0)] # separate unused pinned providers from user added ones for p in user_added: if p in pinned: pinned.remove(p) if user_added: self.ui.cbProviders.addItems(user_added) if user_added and pinned: self.ui.cbProviders.addItem('---') if pinned: random.shuffle(pinned) # don't prioritize alphabetically self.ui.cbProviders.addItems(pinned) # We have configured providers, so by default we select the # 'Use existing provider' option. self.ui.rbExistingProvider.setChecked(True) # We need to set it as complete explicitly self.page(self.INTRO_PAGE).set_completed()
class PreferencesEmailPage(PreferencesPage): def __init__(self, parent, account, app): """ :param parent: parent object of the PreferencesWindow. :parent type: QWidget :param account: user account (user + provider or just provider) :type account: Account :param app: the current App object :type app: App """ PreferencesPage.__init__(self, parent, account, app) self.settings = LeapSettings() self.ui = Ui_PreferencesEmailPage() self.ui.setupUi(self) # the only way to set the tab titles is to re-add them: self.ui.email_tabs.addTab(self.ui.config_tab, self.tr("Mail Client")) self.ui.email_tabs.addTab(self.ui.my_key_tab, self.tr("My Key")) self.ui.email_tabs.addTab(self.ui.other_keys_tab, self.tr("Other Keys")) # set mail client configuration help text lang = QtCore.QLocale.system().name().replace('_', '-') thunderbird_extension_url = \ "https://addons.mozilla.org/{0}/" \ "thunderbird/addon/bitmask/".format(lang) self.ui.thunderbird_label.setText( self.tr("For Thunderbird, you can use the Bitmask extension. " "Search for \"Bitmask\" in the add-on manager or " "download it from <a href='{0}'>addons.mozilla.org</a>.". format(thunderbird_extension_url))) self.ui.mail_client_label.setText( self. tr("Alternatively, you can manually configure your mail client to " "use Bitmask with these options:")) self.ui.webmail_label.setText( self.tr("Bitmask Mail is an integrated mail client based " "on <a href='https://pixelated-project.org/'>Pixelated " "User Agent</a>. If enabled, any user on your device " "can read your mail by opening http://localhost:9090")) self.ui.keys_table.horizontalHeader().setResizeMode( 0, QtGui.QHeaderView.Stretch) self.setup_connections() def setup_connections(self): """ connect signals """ self.app.signaler.keymanager_key_details.connect(self._key_details) self.app.signaler.keymanager_keys_list.connect( self._keymanager_keys_list) self.app.signaler.keymanager_export_ok.connect( self._keymanager_export_ok) self.app.signaler.keymanager_export_error.connect( self._keymanager_export_error) self.ui.import_button.clicked.connect(self._import_keys) self.ui.export_button.clicked.connect(self._export_keys) self.ui.webmail_checkbox.stateChanged.connect(self._toggle_webmail) def teardown_connections(self): """ disconnect signals """ self.app.signaler.keymanager_key_details.disconnect(self._key_details) self.app.signaler.keymanager_export_ok.disconnect( self._keymanager_export_ok) self.app.signaler.keymanager_export_error.disconnect( self._keymanager_export_error) def showEvent(self, event): """ called whenever this widget is shown """ self.ui.keys_table.clearContents() if self.account.username is None: self.ui.email_tabs.setVisible(False) self.ui.message_label.setVisible(True) self.ui.message_label.setText( self.tr('You must be logged in to edit email settings.')) else: webmail_enabled = self.settings.get_pixelmail_enabled() self.ui.webmail_checkbox.setChecked(webmail_enabled) if not HAS_PIXELATED: self.ui.webmail_box.setVisible(False) self.ui.import_button.setVisible(False) # hide this until working self.ui.message_label.setVisible(False) self.ui.email_tabs.setVisible(True) smtp_port = 2013 self.ui.imap_port_edit.setText(str(IMAP_PORT)) self.ui.imap_host_edit.setText("127.0.0.1") self.ui.smtp_port_edit.setText(str(smtp_port)) self.ui.smtp_host_edit.setText("127.0.0.1") self.ui.username_edit.setText(self.account.address) self.ui.password_edit.setText( self.app.service_tokens.get('mail_auth', '')) self.app.backend.keymanager_list_keys() self.app.backend.keymanager_get_key_details( username=self.account.address) def _key_details(self, details): """ Trigger by signal: keymanager_key_details Set the current user's key details into the gui. """ self.ui.fingerprint_edit.setPlainText( self._format_fingerprint(details["fingerprint"])) self.ui.expiration_edit.setText(details["expiry_date"]) self.ui.uid_edit.setText(" ".join(details["uids"])) self.ui.public_key_edit.setPlainText(details["key_data"]) def _format_fingerprint(self, fingerprint): """ formats an openpgp fingerprint in a manner similar to what gpg produces, wrapped to two lines. """ fp = fingerprint.upper() fp_list = [fp[i:i + 4] for i in range(0, len(fp), 4)] fp_wrapped = " ".join(fp_list[0:5]) + "\n" + " ".join(fp_list[5:10]) return fp_wrapped def _export_keys(self): """ Exports the user's key pair. """ file_name, filtr = QtGui.QFileDialog.getSaveFileName( self, self.tr("Save private key file"), filter="*.pem", options=QtGui.QFileDialog.DontUseNativeDialog) if file_name: if not file_name.endswith('.pem'): file_name += '.pem' self.app.backend.keymanager_export_keys( username=self.account.address, filename=file_name) else: logger.debug('Export canceled by the user.') def _keymanager_export_ok(self): """ TRIGGERS: Signaler.keymanager_export_ok Notify the user that the key export went 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.")) 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.")) def _import_keys(self): """ not yet supported """ def _keymanager_keys_list(self, keys): """ 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 """ for key in keys: row = self.ui.keys_table.rowCount() self.ui.keys_table.insertRow(row) self.ui.keys_table.setItem( row, 0, QtGui.QTableWidgetItem(" ".join(key["uids"]))) self.ui.keys_table.setItem( row, 1, QtGui.QTableWidgetItem(key["fingerprint"])) def _toggle_webmail(self, state): value = True if state == QtCore.Qt.Checked else False self.settings.set_pixelmail_enabled(value)
def get_vpn_command( self, eipconfig=None, providerconfig=None, socket_host=None, socket_port="unix", openvpn_verb=1 ): """ Returns the platform dependant vpn launching command. It will look for openvpn in the regular paths and algo in path_prefix/apps/eip/ (in case standalone is set) Might raise: VPNLauncherException, OpenVPNNotFoundException. :param eipconfig: eip configuration object :type eipconfig: EIPConfig :param providerconfig: provider specific configuration :type providerconfig: ProviderConfig :param socket_host: either socket path (unix) or socket IP :type socket_host: str :param socket_port: either string "unix" if it's a unix socket, or port otherwise :type socket_port: str :param openvpn_verb: openvpn verbosity wanted :type openvpn_verb: int :return: A VPN command ready to be launched :rtype: list """ leap_assert(eipconfig, "We need an eip config") leap_assert_type(eipconfig, EIPConfig) leap_assert(providerconfig, "We need a provider config") leap_assert_type(providerconfig, ProviderConfig) leap_assert(socket_host, "We need a socket host!") leap_assert(socket_port, "We need a socket port!") kwargs = {} if ProviderConfig.standalone: kwargs["path_extension"] = os.path.join(providerconfig.get_path_prefix(), "..", "apps", "eip") openvpn_possibilities = which(self.OPENVPN_BIN, **kwargs) if len(openvpn_possibilities) == 0: raise OpenVPNNotFoundException() openvpn = first(openvpn_possibilities) args = [] pkexec = self.maybe_pkexec() if pkexec: args.append(openvpn) openvpn = first(pkexec) args += ["--setenv", "LEAPOPENVPN", "1"] if openvpn_verb is not None: args += ["--verb", "%d" % (openvpn_verb,)] gateways = [] leap_settings = LeapSettings(ProviderConfig.standalone) domain = providerconfig.get_domain() gateway_conf = leap_settings.get_selected_gateway(domain) if gateway_conf == leap_settings.GATEWAY_AUTOMATIC: gateway_selector = VPNGatewaySelector(eipconfig) gateways = gateway_selector.get_gateways() else: gateways = [gateway_conf] if not gateways: logger.error("No gateway was found!") raise VPNLauncherException(self.tr("No gateway was found!")) logger.debug("Using gateways ips: {0}".format(", ".join(gateways))) for gw in gateways: args += ["--remote", gw, "1194", "udp"] args += [ "--client", "--dev", "tun", ############################################################## # persist-tun makes ping-restart fail because it leaves a # broken routing table ############################################################## # '--persist-tun', "--persist-key", "--tls-client", "--remote-cert-tls", "server", ] openvpn_configuration = eipconfig.get_openvpn_configuration() for key, value in openvpn_configuration.items(): args += ["--%s" % (key,), value] ############################################################## # The down-root plugin fails in some situations, so we don't # drop privs for the time being ############################################################## # args += [ # '--user', getpass.getuser(), # '--group', grp.getgrgid(os.getgroups()[-1]).gr_name # ] if socket_port == "unix": # that's always the case for linux args += ["--management-client-user", getpass.getuser()] args += ["--management-signal", "--management", socket_host, socket_port, "--script-security", "2"] plugin_path = self.maybe_down_plugin() # If we do not have the down plugin neither in the bundle # nor in the system, we do not do updown scripts. The alternative # is leaving the user without the ability to restore dns and routes # to its original state. if plugin_path and _has_updown_scripts(self.UP_DOWN_PATH): args += [ "--up", self.UP_DOWN_PATH, "--down", self.UP_DOWN_PATH, ############################################################## # For the time being we are disabling the usage of the # down-root plugin, because it doesn't quite work as # expected (i.e. it doesn't run route -del as root # when finishing, so it fails to properly # restart/quit) ############################################################## # '--plugin', plugin_path, # '\'script_type=down %s\'' % self.UP_DOWN_PATH ] args += [ "--cert", eipconfig.get_client_cert_path(providerconfig), "--key", eipconfig.get_client_cert_path(providerconfig), "--ca", providerconfig.get_ca_cert_path(), ] logger.debug("Running VPN with command:") logger.debug("%s %s" % (openvpn, " ".join(args))) return [openvpn] + args