Exemple #1
0
    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_()
Exemple #3
0
    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'))
Exemple #5
0
    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
Exemple #6
0
 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'))
Exemple #7
0
    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()
Exemple #10
0
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()
Exemple #11
0
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, 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()
Exemple #13
0
    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
Exemple #15
0
    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
Exemple #16
0
    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 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'))
Exemple #20
0
    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()
Exemple #21
0
    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()
Exemple #23
0
    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
Exemple #24
0
    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()
Exemple #25
0
        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
Exemple #26
0
    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)
Exemple #27
0
        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)
Exemple #31
0
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)
Exemple #33
0
    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
Exemple #34
0
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()
Exemple #40
0
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)
Exemple #41
0
    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()
Exemple #42
0
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
    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()