Example #1
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()
Example #2
0
    def __init__(self, settings, parent=None):
        """
        Constructs the LoginWidget.

        :param settings: client wide settings
        :type settings: LeapSettings
        :param parent: The parent widget for this widget
        :type parent: QWidget or None
        """
        QtGui.QWidget.__init__(self, parent)

        self._settings = settings
        self._selected_provider_index = -1

        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.lnPassword.setEchoMode(QtGui.QLineEdit.Password)

        self.ui.btnLogin.clicked.connect(self.login)
        self.ui.lnPassword.returnPressed.connect(self.login)

        self.ui.lnUser.returnPressed.connect(self._focus_password)

        self.ui.cmbProviders.currentIndexChanged.connect(
            self._current_provider_changed)

        self.ui.btnLogout.clicked.connect(
            self.logout)

        username_re = QtCore.QRegExp(USERNAME_REGEX)
        self.ui.lnUser.setValidator(
            QtGui.QRegExpValidator(username_re, self))

        self.logged_out()

        self.ui.btnLogout.clicked.connect(self.start_logout)

        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)
Example #3
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()
Example #4
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)
Example #5
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)
Example #6
0
class LoginWidget(QtGui.QWidget):
    """
    Login widget that emits signals to display the wizard or to
    perform login.
    """
    # Emitted when the login button is clicked
    login = QtCore.Signal()
    logged_in_signal = QtCore.Signal()
    cancel_login = QtCore.Signal()
    logout = QtCore.Signal()

    # Emitted when the user selects "Other..." in the provider
    # combobox or click "Create Account"
    show_wizard = QtCore.Signal()

    MAX_STATUS_WIDTH = 40

    # Keyring
    KEYRING_KEY = "bitmask"

    def __init__(self, settings, parent=None):
        """
        Constructs the LoginWidget.

        :param settings: client wide settings
        :type settings: LeapSettings
        :param parent: The parent widget for this widget
        :type parent: QWidget or None
        """
        QtGui.QWidget.__init__(self, parent)

        self._settings = settings
        self._selected_provider_index = -1

        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.lnPassword.setEchoMode(QtGui.QLineEdit.Password)

        self.ui.btnLogin.clicked.connect(self.login)
        self.ui.lnPassword.returnPressed.connect(self.login)

        self.ui.lnUser.returnPressed.connect(self._focus_password)

        self.ui.cmbProviders.currentIndexChanged.connect(
            self._current_provider_changed)

        self.ui.btnLogout.clicked.connect(
            self.logout)

        username_re = QtCore.QRegExp(USERNAME_REGEX)
        self.ui.lnUser.setValidator(
            QtGui.QRegExpValidator(username_re, self))

        self.logged_out()

        self.ui.btnLogout.clicked.connect(self.start_logout)

        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)

    def _remember_state_changed(self, state):
        """
        Saves the remember state in the LeapSettings

        :param state: possible stats can be Checked, Unchecked and
        PartiallyChecked
        :type state: QtCore.Qt.CheckState
        """
        enable = True if state == QtCore.Qt.Checked else False
        self._settings.set_remember(enable)

    def set_providers(self, provider_list):
        """
        Set the provider list to provider_list plus an "Other..." item
        that triggers the wizard

        :param provider_list: list of providers
        :type provider_list: list of str
        """
        self.ui.cmbProviders.blockSignals(True)
        self.ui.cmbProviders.clear()
        self.ui.cmbProviders.addItems(provider_list + [self.tr("Other...")])
        self.ui.cmbProviders.blockSignals(False)

    def select_provider_by_name(self, name):
        """
        Given a provider name/domain, it selects it in the combobox

        :param name: name or domain for the provider
        :type name: str
        """
        provider_index = self.ui.cmbProviders.findText(name)
        self.ui.cmbProviders.setCurrentIndex(provider_index)

    def get_selected_provider(self):
        """
        Returns the selected provider in the combobox
        """
        return self.ui.cmbProviders.currentText()

    def set_remember(self, value):
        """
        Checks 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):
        """
        Returns the user that appears in the widget.

        :rtype: unicode
        """
        return self.ui.lnUser.text()

    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)
        self.ui.cmbProviders.setEnabled(enabled)

        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.cancel_login
        hide_remember = enabled

        if not enabled:
            text = self.tr("Log In")
            login_or_cancel = self.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()

    @QtCore.Slot(int)
    def _current_provider_changed(self, idx):
        """
        TRIGGERS:
            self.ui.cmbProviders.currentIndexChanged

        :param idx: the index of the new selected item
        :type idx: int
        """
        if idx == (self.ui.cmbProviders.count() - 1):
            self.show_wizard.emit()
            # Leave the previously selected provider in the combobox
            prev_provider = 0
            if self._selected_provider_index != -1:
                prev_provider = self._selected_provider_index
            self.ui.cmbProviders.blockSignals(True)
            self.ui.cmbProviders.setCurrentIndex(prev_provider)
            self.ui.cmbProviders.blockSignals(False)
        else:
            self._selected_provider_index = idx

    def start_login(self):
        """
        Setups the login widgets for actually performing the login and
        performs some basic checks.

        :returns: True if everything's good to go, False otherwise
        :rtype: bool
        """
        username = self.get_user()
        password = self.get_password()
        provider = self.get_selected_provider()

        self._enabled_services = self._settings.get_enabled_services(
            self.get_selected_provider())

        if len(provider) == 0:
            self.set_status(
                self.tr("Please select a valid provider"))
            return False

        if len(username) == 0:
            self.set_status(
                self.tr("Please provide a valid username"))
            return False

        if len(password) == 0:
            self.set_status(
                self.tr("Please provide a valid password"))
            return False

        self.set_status(self.tr("Logging in..."), error=False)
        self.set_enabled(False)
        self.ui.clblErrorMsg.hide()

        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 Exception as e:
                logger.exception("Problem saving data to keyring. %r"
                                 % (e,))
        return True

    def logged_in(self):
        """
        Sets the widgets to the logged in state
        """
        self.ui.login_widget.hide()
        self.ui.logged_widget.show()
        self.ui.lblUser.setText(make_address(
            self.get_user(), self.get_selected_provider()))

        if flags.OFFLINE is False:
            self.logged_in_signal.emit()

    def logged_out(self):
        """
        Sets the widgets to the logged out state
        """
        # TODO consider "logging out offline" too...
        # how that would be ???

        self.ui.login_widget.show()
        self.ui.logged_widget.hide()

        self.set_password("")
        self.set_enabled(True)
        self.set_status("", error=False)

    def start_logout(self):
        """
        Sets the widgets to the logging out state
        """
        self.ui.btnLogout.setText(self.tr("Logging out..."))
        self.ui.btnLogout.setEnabled(False)

    def done_logout(self):
        """
        Sets the widgets to the logged out state
        """
        self.ui.btnLogout.setText(self.tr("Logout"))
        self.ui.btnLogout.setEnabled(True)
        self.ui.clblErrorMsg.hide()

    def load_user_from_keyring(self, saved_user):
        """
        Tries to load a user from the keyring, returns True if it was
        loaded successfully, False otherwise.

        :param saved_user: String containing the saved username as
                           user@domain
        :type saved_user: unicode

        :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()
            saved_password = keyring.get_password(self.KEYRING_KEY,
                                                  saved_user
                                                  .encode("utf8"))
        except ValueError as e:
            logger.debug("Incorrect Password. %r." % (e,))

        if saved_password is not None:
            self.set_password(saved_password)
            return True

        return False
Example #7
0
class LoginWidget(QtGui.QWidget):
    """
    Login widget that emits signals to display the wizard or to
    perform login.
    """

    # Emitted when the login button is clicked
    login = QtCore.Signal()
    cancel_login = QtCore.Signal()

    # Emitted when the user selects "Other..." in the provider
    # combobox or click "Create Account"
    show_wizard = QtCore.Signal()

    MAX_STATUS_WIDTH = 40

    BARE_USERNAME_REGEX = r"^[A-Za-z\d_]+$"

    def __init__(self, settings, parent=None):
        """
        Constructs the LoginWidget.

        :param settings: client wide settings
        :type settings: LeapSettings
        :param parent: The parent widget for this widget
        :type parent: QWidget or None
        """
        QtGui.QWidget.__init__(self, parent)

        self._settings = settings
        self._selected_provider_index = -1

        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.lnPassword.setEchoMode(QtGui.QLineEdit.Password)

        self.ui.btnLogin.clicked.connect(self.login)
        self.ui.lnPassword.returnPressed.connect(self.login)

        self.ui.lnUser.returnPressed.connect(self._focus_password)

        self.ui.cmbProviders.currentIndexChanged.connect(
            self._current_provider_changed)
        self.ui.btnCreateAccount.clicked.connect(
            self.show_wizard)

        username_re = QtCore.QRegExp(self.BARE_USERNAME_REGEX)
        self.ui.lnUser.setValidator(
            QtGui.QRegExpValidator(username_re, self))

    def _remember_state_changed(self, state):
        """
        Saves the remember state in the LeapSettings

        :param state: possible stats can be Checked, Unchecked and
        PartiallyChecked
        :type state: QtCore.Qt.CheckState
        """
        enable = True if state == QtCore.Qt.Checked else False
        self._settings.set_remember(enable)

    def set_providers(self, provider_list):
        """
        Set the provider list to provider_list plus an "Other..." item
        that triggers the wizard

        :param provider_list: list of providers
        :type provider_list: list of str
        """
        self.ui.cmbProviders.blockSignals(True)
        self.ui.cmbProviders.clear()
        self.ui.cmbProviders.addItems(provider_list + [self.tr("Other...")])
        self.ui.cmbProviders.blockSignals(False)

    def select_provider_by_name(self, name):
        """
        Given a provider name/domain, it selects it in the combobox

        :param name: name or domain for the provider
        :type name: str
        """
        provider_index = self.ui.cmbProviders.findText(name)
        self.ui.cmbProviders.setCurrentIndex(provider_index)

    def get_selected_provider(self):
        """
        Returns the selected provider in the combobox
        """
        return self.ui.cmbProviders.currentText()

    def set_remember(self, value):
        """
        Checks 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):
        """
        Returns the user that appears in the widget.

        :rtype: str
        """
        return self.ui.lnUser.text()

    def set_password(self, password):
        """
        Sets the password for the widget

        :param password: password to set
        :type password: str
        """
        self.ui.lnPassword.setText(password)

    def get_password(self):
        """
        Returns the password that appears in the widget

        :rtype: str
        """
        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:
            status = "<font color='red'><b>%s</b></font>" % (status,)
        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)
        self.ui.cmbProviders.setEnabled(enabled)

        self._set_cancel(not 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.cancel_login

        if not enabled:
            text = self.tr("Log In")
            login_or_cancel = self.login

        self.ui.btnLogin.setText(text)

        self.ui.btnLogin.clicked.disconnect()
        self.ui.btnLogin.clicked.connect(login_or_cancel)

    def _focus_password(self):
        """
        Focuses in the password lineedit
        """
        self.ui.lnPassword.setFocus()

    def _current_provider_changed(self, param):
        """
        SLOT
        TRIGGERS: self.ui.cmbProviders.currentIndexChanged
        """
        if param == (self.ui.cmbProviders.count() - 1):
            self.show_wizard.emit()
            # Leave the previously selected provider in the combobox
            prev_provider = 0
            if self._selected_provider_index != -1:
                prev_provider = self._selected_provider_index
            self.ui.cmbProviders.blockSignals(True)
            self.ui.cmbProviders.setCurrentIndex(prev_provider)
            self.ui.cmbProviders.blockSignals(False)
        else:
            self._selected_provider_index = param
Example #8
0
class LoginWidget(QtGui.QWidget):
    """
    Login widget that emits signals to display the wizard or to
    perform login.
    """
    # Emitted when the login button is clicked
    login = QtCore.Signal()
    logged_in_signal = QtCore.Signal()
    cancel_login = QtCore.Signal()
    logout = QtCore.Signal()

    # Emitted when the user selects "Other..." in the provider
    # combobox or click "Create Account"
    show_wizard = QtCore.Signal()

    MAX_STATUS_WIDTH = 40

    BARE_USERNAME_REGEX = r"^[A-Za-z\d_]+$"

    def __init__(self, settings, parent=None):
        """
        Constructs the LoginWidget.

        :param settings: client wide settings
        :type settings: LeapSettings
        :param parent: The parent widget for this widget
        :type parent: QWidget or None
        """
        QtGui.QWidget.__init__(self, parent)

        self._settings = settings
        self._selected_provider_index = -1

        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.lnPassword.setEchoMode(QtGui.QLineEdit.Password)

        self.ui.btnLogin.clicked.connect(self.login)
        self.ui.lnPassword.returnPressed.connect(self.login)

        self.ui.lnUser.returnPressed.connect(self._focus_password)

        self.ui.cmbProviders.currentIndexChanged.connect(
            self._current_provider_changed)

        self.ui.btnLogout.clicked.connect(
            self.logout)

        username_re = QtCore.QRegExp(self.BARE_USERNAME_REGEX)
        self.ui.lnUser.setValidator(
            QtGui.QRegExpValidator(username_re, self))

        self.logged_out()

        self.ui.btnLogout.clicked.connect(self.start_logout)

        self.ui.clblErrorMsg.hide()
        self.ui.clblErrorMsg.clicked.connect(self.ui.clblErrorMsg.hide)

    def _remember_state_changed(self, state):
        """
        Saves the remember state in the LeapSettings

        :param state: possible stats can be Checked, Unchecked and
        PartiallyChecked
        :type state: QtCore.Qt.CheckState
        """
        enable = True if state == QtCore.Qt.Checked else False
        self._settings.set_remember(enable)

    def set_providers(self, provider_list):
        """
        Set the provider list to provider_list plus an "Other..." item
        that triggers the wizard

        :param provider_list: list of providers
        :type provider_list: list of str
        """
        self.ui.cmbProviders.blockSignals(True)
        self.ui.cmbProviders.clear()
        self.ui.cmbProviders.addItems(provider_list + [self.tr("Other...")])
        self.ui.cmbProviders.blockSignals(False)

    def select_provider_by_name(self, name):
        """
        Given a provider name/domain, it selects it in the combobox

        :param name: name or domain for the provider
        :type name: str
        """
        provider_index = self.ui.cmbProviders.findText(name)
        self.ui.cmbProviders.setCurrentIndex(provider_index)

    def get_selected_provider(self):
        """
        Returns the selected provider in the combobox
        """
        return self.ui.cmbProviders.currentText()

    def set_remember(self, value):
        """
        Checks 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):
        """
        Returns the user that appears in the widget.

        :rtype: str
        """
        return self.ui.lnUser.text()

    def set_password(self, password):
        """
        Sets the password for the widget

        :param password: password to set
        :type password: str
        """
        self.ui.lnPassword.setText(password)

    def get_password(self):
        """
        Returns the password that appears in the widget

        :rtype: str
        """
        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)
        self.ui.cmbProviders.setEnabled(enabled)

        self._set_cancel(not 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.cancel_login
        hide_remember = enabled

        if not enabled:
            text = self.tr("Log In")
            login_or_cancel = self.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 _current_provider_changed(self, param):
        """
        SLOT
        TRIGGERS: self.ui.cmbProviders.currentIndexChanged
        """
        if param == (self.ui.cmbProviders.count() - 1):
            self.show_wizard.emit()
            # Leave the previously selected provider in the combobox
            prev_provider = 0
            if self._selected_provider_index != -1:
                prev_provider = self._selected_provider_index
            self.ui.cmbProviders.blockSignals(True)
            self.ui.cmbProviders.setCurrentIndex(prev_provider)
            self.ui.cmbProviders.blockSignals(False)
        else:
            self._selected_provider_index = param

    def start_login(self):
        """
        Setups the login widgets for actually performing the login and
        performs some basic checks.

        :returns: True if everything's good to go, False otherwise
        :rtype: bool
        """
        username = self.get_user()
        password = self.get_password()
        provider = self.get_selected_provider()

        self._enabled_services = self._settings.get_enabled_services(
            self.get_selected_provider())

        if len(provider) == 0:
            self.set_status(
                self.tr("Please select a valid provider"))
            return False

        if len(username) == 0:
            self.set_status(
                self.tr("Please provide a valid username"))
            return False

        if len(password) == 0:
            self.set_status(
                self.tr("Please provide a valid password"))
            return False

        self.set_status(self.tr("Logging in..."), error=False)
        self.set_enabled(False)
        self.ui.clblErrorMsg.hide()

        if self.get_remember() and has_keyring():
            # in the keyring and in the settings
            # we store the value 'usename@provider'
            username_domain = (username + '@' + provider).encode("utf8")
            try:
                keyring.set_password(self.KEYRING_KEY,
                                     username_domain,
                                     password.encode("utf8"))
                # Only save the username if it was saved correctly in
                # the keyring
                self._settings.set_user(username_domain)
            except Exception as e:
                logger.exception("Problem saving data to keyring. %r"
                                 % (e,))
        return True

    def logged_in(self):
        """
        Sets the widgets to the logged in state
        """
        self.ui.login_widget.hide()
        self.ui.logged_widget.show()
        self.ui.lblUser.setText("%s@%s" % (self.get_user(),
                                           self.get_selected_provider()))
        self.set_login_status("")
        self.logged_in_signal.emit()

    def logged_out(self):
        """
        Sets the widgets to the logged out state
        """
        self.ui.login_widget.show()
        self.ui.logged_widget.hide()

        self.set_password("")
        self.set_enabled(True)
        self.set_status("", error=False)

    def set_login_status(self, msg, error=False):
        """
        Sets the status label for the logged in state.

        :param msg: status message
        :type msg: str or unicode
        :param error: if the status is an erroneous one, then set this
                      to True
        :type error: bool
        """
        leap_assert_type(error, bool)
        if error:
            msg = "<font color='red'><b>%s</b></font>" % (msg,)
        self.ui.lblLoginStatus.setText(msg)
        self.ui.lblLoginStatus.show()

    def start_logout(self):
        """
        Sets the widgets to the logging out state
        """
        self.ui.btnLogout.setText(self.tr("Loggin out..."))
        self.ui.btnLogout.setEnabled(False)

    def done_logout(self):
        """
        Sets the widgets to the logged out state
        """
        self.ui.btnLogout.setText(self.tr("Logout"))
        self.ui.btnLogout.setEnabled(True)
        self.ui.clblErrorMsg.hide()