summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvan Alejandro <ivanalejandro0@gmail.com>2014-09-04 13:06:56 -0300
committerIvan Alejandro <ivanalejandro0@gmail.com>2014-12-19 17:05:13 -0300
commit98c874503e14b40896a63ea36b86d39edccb8b30 (patch)
treecd2fbcbc665fe9879d70445223f74abce1fd9d09
parent6378638e94df93c3a13f10adc3b6946f6b132be0 (diff)
Refactor login usage.
- factor out the signal tracking code as a helper class, - move login logic from MainWindow to LoginWidget, - add new signals to the LoginWidget to interact with MainWindow, - add login sequence docs to LoginWidget class, - improve docs for login methods, - add LoginState class to handle login states, - disable login button until data is entered, - move some properties and actions to .ui file.
-rw-r--r--src/leap/bitmask/gui/login.py537
-rw-r--r--src/leap/bitmask/gui/mainwindow.py424
-rw-r--r--src/leap/bitmask/gui/signaltracker.py55
-rw-r--r--src/leap/bitmask/gui/ui/login.ui51
-rw-r--r--src/leap/bitmask/gui/wizard.py73
5 files changed, 680 insertions, 460 deletions
diff --git a/src/leap/bitmask/gui/login.py b/src/leap/bitmask/gui/login.py
index 2a79fafd..7487e888 100644
--- a/src/leap/bitmask/gui/login.py
+++ b/src/leap/bitmask/gui/login.py
@@ -16,13 +16,31 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Login widget implementation
+
+The login sequence is the following:
+ - _do_login
+ - backend.provider_setup (check_name_resolution, check_https, download_provider_info)
+ - on error: _provider_setup_intermediate
+ - on success: _load_provider_config
+ - backend.provider_bootstrap (download_ca_cert, check_ca_fingerprint, check_api_certificate)
+ - on error: _provider_setup_intermediate
+ - on success: _provider_config_loaded
+ - backend.user_login
+ - on error: _authentication_error
+ - on success: _authentication_finished
+
"""
import logging
from PySide import QtCore, QtGui
from ui_login import Ui_LoginWidget
+# TODO: we should use a more granular signaling instead of passing error/ok as
+# a result.
+from leap.bitmask.backend.leapbackend import ERROR_KEY, PASSED_KEY
from leap.bitmask.config import flags
+from leap.bitmask.config.leapsettings import LeapSettings
+from leap.bitmask.gui.signaltracker import SignalTracker
from leap.bitmask.util import make_address
from leap.bitmask.util.credentials import USERNAME_REGEX
from leap.bitmask.util.keyring_helpers import has_keyring
@@ -32,59 +50,71 @@ from leap.common.check import leap_assert_type
logger = logging.getLogger(__name__)
-class LoginWidget(QtGui.QWidget):
+class LoginState(object):
+ """
+ This class holds the states related to the login sequence.
+ """
+
+ def __init__(self):
+ # `wait_to_login` defines whether we should wait to start the login
+ # sequence or we should start right away.
+ # This is used, for instance, to hold on until EIP is started since the
+ # firewall could block the login attempt.
+ self.wait_to_login = False
+
+ # This state indicates that the login sequence was required to start
+ # but it was set on hold since we `wait_to_login` was True
+ self.login_waiting = False
+
+ # Full username of the logged user, with the format: 'user@provider'
+ self.full_logged_username = None
+
+
+class LoginWidget(QtGui.QWidget, SignalTracker):
"""
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()
+ 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, settings, parent=None):
+ def __init__(self, backend, signaler, parent=None):
"""
Constructs the LoginWidget.
- :param settings: client wide settings
- :type settings: LeapSettings
+ :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)
-
- self._settings = settings
+ SignalTracker.__init__(self)
self.ui = Ui_LoginWidget()
self.ui.setupUi(self)
- self.ui.chkRemember.stateChanged.connect(
- self._remember_state_changed)
+ 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.textChanged.connect(self._credentials_changed)
+ self.ui.lnPassword.textChanged.connect(self._credentials_changed)
- self.ui.lnUser.returnPressed.connect(self._focus_password)
+ self.ui.btnLogin.clicked.connect(self._do_login)
+ self.ui.btnLogout.clicked.connect(self.do_logout)
- 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)
+ QtGui.QRegExpValidator(QtCore.QRegExp(USERNAME_REGEX), self))
self.ui.clblErrorMsg.hide()
self.ui.clblErrorMsg.clicked.connect(self.ui.clblErrorMsg.hide)
@@ -92,20 +122,73 @@ class LoginWidget(QtGui.QWidget):
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()
+
+ @QtCore.Slot(int)
def _remember_state_changed(self, state):
"""
- Saves the remember state in the LeapSettings
+ 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)
+
+ @QtCore.Slot(unicode)
+ def _credentials_changed(self, text):
+ """
+ TRIGGER:
+ self.ui.lnUser.textChanged
+ self.ui.lnPassword.textChanged
- :param state: possible stats can be Checked, Unchecked and
- PartiallyChecked
- :type state: QtCore.Qt.CheckState
+ Update the 'enabled' status of the login button depending if we have
+ all the fields needed set.
"""
- enable = True if state == QtCore.Qt.Checked else False
- self._settings.set_remember(enable)
+ 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):
"""
- Checks the remember user and password checkbox
+ Check the remember user and password checkbox
:param value: True to mark it checked, False otherwise
:type value: bool
@@ -132,12 +215,21 @@ class LoginWidget(QtGui.QWidget):
def get_user(self):
"""
- Returns the user that appears in the widget.
+ 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
@@ -200,12 +292,12 @@ class LoginWidget(QtGui.QWidget):
:type enabled: bool
"""
text = self.tr("Cancel")
- login_or_cancel = self.cancel_login
+ login_or_cancel = self._do_cancel
hide_remember = enabled
if not enabled:
text = self.tr("Log In")
- login_or_cancel = self.login
+ login_or_cancel = self._do_login
self.ui.btnLogin.setText(text)
@@ -220,40 +312,49 @@ class LoginWidget(QtGui.QWidget):
"""
self.ui.lnPassword.setFocus()
- def start_login(self, provider):
+ def _check_login(self):
"""
- Setups the login widgets for actually performing the login and
- performs some basic checks.
+ Check that we have the needed fields to do the actual login: provider,
+ username and password.
- :param provider: the domain of the current provider
- :type provider: unicode str
- :returns: True if everything's good to go, False otherwise
+ :return: True if everything's good to go, False otherwise.
:rtype: bool
"""
+ provider = self._provider
username = self.get_user()
password = self.get_password()
- self._enabled_services = self._settings.get_enabled_services(provider)
-
- if len(provider) == 0:
- self.set_status(
- self.tr("Please select a valid provider"))
+ if not provider:
+ 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"))
+ if not username:
+ 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"))
+ 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
@@ -262,36 +363,209 @@ class LoginWidget(QtGui.QWidget):
try:
keyring = get_keyring()
keyring.set_password(self.KEYRING_KEY,
- full_user_id,
- password.encode("utf8"))
+ 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
+ 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.")
- def logged_in(self, provider):
+ self._backend.provider_cancel_setup()
+ self._backend.user_cancel_login()
+ self._set_logged_out()
+
+ @QtCore.Slot()
+ 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)
+
+ @QtCore.Slot(dict)
+ 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()
+
+ @QtCore.Slot()
+ 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)
+
+ @QtCore.Slot(dict)
+ 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
"""
- Sets the widgets to the logged in state
+ if not data[PASSED_KEY]:
+ logger.error(data[ERROR_KEY])
+ self._login_problem_provider()
+ return
- :param provider: the domain of the current provider
- :type provider: unicode str
+ self._backend.provider_bootstrap(provider=self._provider)
+
+ @QtCore.Slot(dict)
+ 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(make_address(self.get_user(), provider))
+ self.ui.lblUser.setText(fullname)
- if flags.OFFLINE is False:
- self.logged_in_signal.emit()
+ @QtCore.Slot()
+ def _authentication_finished(self):
+ """
+ TRIGGERS:
+ self._srp_auth.authentication_finished
- def logged_out(self):
+ The SRP auth was successful, set the login status.
"""
- Sets the widgets to the logged out state
+ self.set_status(self.tr("Succeeded"), error=False)
+ self._set_logged_in()
+
+ if not flags.OFFLINE:
+ self.login_finished.emit()
+
+ @QtCore.Slot(unicode)
+ def _authentication_error(self, msg):
"""
- # TODO consider "logging out offline" too...
- # how that would be ???
+ 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()
@@ -300,30 +574,75 @@ class LoginWidget(QtGui.QWidget):
self.set_enabled(True)
self.set_status("", error=False)
- def start_logout(self):
+ @QtCore.Slot()
+ def do_logout(self):
"""
- Sets the widgets to the logging out state
+ TRIGGER:
+ self.ui.btnLogout.clicked
+
+ Start the logout sequence and set the widgets to the "logging out"
+ state.
"""
- self.ui.btnLogout.setText(self.tr("Logging out..."))
- self.ui.btnLogout.setEnabled(False)
+ if self._state.full_logged_username is not None:
+ self._set_logging_out()
+ self._backend.user_logout()
+ else:
+ logger.debug("Not logged in.")
- def done_logout(self):
+ def _set_logging_out(self, logging_out=True):
"""
- Sets the widgets to the logged out state
+ 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
"""
- self.ui.btnLogout.setText(self.tr("Logout"))
- self.ui.btnLogout.setEnabled(True)
- self.ui.clblErrorMsg.hide()
+ 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()
+
+ @QtCore.Slot()
+ 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."))
+
+ @QtCore.Slot()
+ 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):
"""
- Tries to load a user from the keyring, returns True if it was
- loaded successfully, False otherwise.
+ Try to load a user from the keyring.
- :param saved_user: String containing the saved username as
- user@domain
+ :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)
@@ -336,15 +655,13 @@ class LoginWidget(QtGui.QWidget):
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"))
+ 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,))
@@ -353,3 +670,49 @@ class LoginWidget(QtGui.QWidget):
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)
+ # sig.srp_not_logged_in_error.connect(self._not_logged_in_error)
diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py
index 6f44ff01..c41c5fc2 100644
--- a/src/leap/bitmask/gui/mainwindow.py
+++ b/src/leap/bitmask/gui/mainwindow.py
@@ -41,6 +41,7 @@ from leap.bitmask.gui.loggerwindow import LoggerWindow
from leap.bitmask.gui.login import LoginWidget
from leap.bitmask.gui.mail_status import MailStatusWidget
from leap.bitmask.gui.preferenceswindow import PreferencesWindow
+from leap.bitmask.gui.signaltracker import SignalTracker
from leap.bitmask.gui.systray import SysTray
from leap.bitmask.gui.wizard import Wizard
from leap.bitmask.gui.providers import Providers
@@ -72,17 +73,15 @@ QtDelayedCall = QtCore.QTimer.singleShot
logger = logging.getLogger(__name__)
-class MainWindow(QtGui.QMainWindow):
+class MainWindow(QtGui.QMainWindow, SignalTracker):
"""
Main window for login and presenting status updates to the user
"""
# Signals
eip_needs_login = QtCore.Signal([])
- offline_mode_bypass_login = QtCore.Signal([])
new_updates = QtCore.Signal(object)
raise_window = QtCore.Signal([])
soledad_ready = QtCore.Signal([])
- logout = QtCore.Signal([])
all_services_stopped = QtCore.Signal()
# We use this flag to detect abnormal terminations
@@ -103,6 +102,8 @@ class MainWindow(QtGui.QMainWindow):
:type start_hidden: bool
"""
QtGui.QMainWindow.__init__(self)
+ SignalTracker.__init__(self)
+
autostart.set_autostart(True)
# register leap events ########################################
@@ -127,7 +128,8 @@ class MainWindow(QtGui.QMainWindow):
self._settings = self.app.settings
# Login Widget
- self._login_widget = LoginWidget(self._settings, self)
+ self._login_widget = LoginWidget(self._backend,
+ self._leap_signaler, self)
self.ui.loginLayout.addWidget(self._login_widget)
# Mail Widget
@@ -142,9 +144,10 @@ class MainWindow(QtGui.QMainWindow):
self.app.service_selection_changed.connect(
self._update_eip_enabled_status)
- self._login_widget.login.connect(self._login)
- self._login_widget.cancel_login.connect(self._cancel_login)
- self._login_widget.logout.connect(self._logout)
+
+ self._login_widget.login_finished.connect(
+ self._on_user_logged_in)
+ self._login_widget.logged_out.connect(self._on_user_logged_out)
self._providers.connect_provider_changed(self._on_provider_changed)
@@ -164,6 +167,8 @@ class MainWindow(QtGui.QMainWindow):
self._eip_conductor.add_eip_widget(self._eip_status)
self._eip_conductor.connect_signals()
+ self._eip_conductor.qtsigs.connecting_signal.connect(
+ self._on_eip_connecting)
self._eip_conductor.qtsigs.connected_signal.connect(
self._on_eip_connection_connected)
self._eip_conductor.qtsigs.disconnected_signal.connect(
@@ -171,7 +176,7 @@ class MainWindow(QtGui.QMainWindow):
self._eip_conductor.qtsigs.connected_signal.connect(
self._maybe_run_soledad_setup_checks)
- self.offline_mode_bypass_login.connect(
+ self._login_widget.login_offline_finished.connect(
self._maybe_run_soledad_setup_checks)
self.eip_needs_login.connect(self._eip_status.disable_eip_start)
@@ -184,8 +189,6 @@ class MainWindow(QtGui.QMainWindow):
self._soledad_started = False
# This is created once we have a valid provider config
- self._srp_auth = None
- self._logged_user = None
self._logged_in_offline = False
# Set used to track the services being stopped and need wait.
@@ -199,7 +202,6 @@ class MainWindow(QtGui.QMainWindow):
# Used to differentiate between a real quit and a close to tray
self._close_to_tray = True
- self._backend_connected_signals = []
self._backend_connect()
self.ui.action_preferences.triggered.connect(self._show_preferences)
@@ -254,8 +256,9 @@ class MainWindow(QtGui.QMainWindow):
self.ui.btnMore.resize(0, 0)
#########################################
self.ui.btnMore.clicked.connect(self._updates_details)
- if flags.OFFLINE is True:
- self._set_label_offline()
+
+ if flags.OFFLINE:
+ self._login_widget._set_label_offline()
# Services signals/slots connection
self.new_updates.connect(self._react_to_new_updates)
@@ -272,8 +275,6 @@ class MainWindow(QtGui.QMainWindow):
self._mail_conductor = mail_conductor.MailConductor(self._backend)
self._mail_conductor.connect_mail_signals(self._mail_status)
- self.logout.connect(self._mail_conductor.stop_mail_services)
-
if not init_platform():
self.quit()
return
@@ -311,18 +312,6 @@ class MainWindow(QtGui.QMainWindow):
self.tr("You are trying to do an operation "
"that requires logging in first."))
- def _connect_and_track(self, signal, method):
- """
- Helper to connect signals and keep track of them.
-
- :param signal: the signal to connect to.
- :type signal: QtCore.Signal
- :param method: the method to call when the signal is triggered.
- :type method: callable, Slot or Signal
- """
- self._backend_connected_signals.append((signal, method))
- signal.connect(method)
-
def _backend_bad_call(self, data):
"""
Callback for debugging bad backend calls
@@ -340,7 +329,7 @@ class MainWindow(QtGui.QMainWindow):
We track some signals in order to disconnect them on demand.
For instance, in the wizard we need to connect to some signals that are
already connected in the mainwindow, so to avoid conflicts we do:
- - disconnect signals needed in wizard (`_disconnect_and_untrack`)
+ - disconnect signals needed in wizard (`disconnect_and_untrack`)
- use wizard
- reconnect disconnected signals (we use the `only_tracked` param)
@@ -349,39 +338,12 @@ class MainWindow(QtGui.QMainWindow):
:type only_tracked: bool
"""
sig = self._leap_signaler
- conntrack = self._connect_and_track
- auth_err = self._authentication_error
-
- conntrack(sig.prov_name_resolution, self._intermediate_stage)
- conntrack(sig.prov_https_connection, self._intermediate_stage)
- conntrack(sig.prov_download_ca_cert, self._intermediate_stage)
- conntrack(sig.prov_download_provider_info, self._load_provider_config)
- conntrack(sig.prov_check_api_certificate, self._provider_config_loaded)
+ conntrack = self.connect_and_track
+ # XXX does this goes in here? this will be triggered when the login or
+ # wizard requests provider data
conntrack(sig.prov_check_api_certificate, self._get_provider_details)
-
- conntrack(sig.prov_problem_with_provider, self._login_problem_provider)
- conntrack(sig.prov_cancelled_setup, self._set_login_cancelled)
-
conntrack(sig.prov_get_details, self._provider_get_details)
- # 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)
-
# EIP bootstrap signals
conntrack(sig.eip_config_ready, self._eip_intermediate_stage)
conntrack(sig.eip_client_certificate_ready, self._finish_eip_bootstrap)
@@ -401,8 +363,9 @@ class MainWindow(QtGui.QMainWindow):
sig.prov_get_all_services.connect(self._provider_get_all_services)
# Logout signals =================================================
- sig.srp_logout_ok.connect(self._logout_ok)
- sig.srp_logout_error.connect(self._logout_error)
+ # This error may be due a 'logout' or a 'password change', as is used
+ # on the login widget and the settings dialog the connection is made
+ # here.
sig.srp_not_logged_in_error.connect(self._not_logged_in_error)
# EIP start signals ==============================================
@@ -433,21 +396,6 @@ class MainWindow(QtGui.QMainWindow):
# TODO: connect this with something
# sig.soledad_cancelled_bootstrap.connect()
- def _disconnect_and_untrack(self):
- """
- Helper to disconnect the tracked signals.
-
- Some signals are emitted from the wizard, and we want to
- ignore those.
- """
- for signal, method in self._backend_connected_signals:
- try:
- signal.disconnect(method)
- except RuntimeError:
- pass # Signal was not connected
-
- self._backend_connected_signals = []
-
@QtCore.Slot()
def _rejected_wizard(self):
"""
@@ -485,7 +433,7 @@ class MainWindow(QtGui.QMainWindow):
there.
"""
if self._wizard is None:
- self._disconnect_and_untrack()
+ self.disconnect_and_untrack()
self._wizard = Wizard(backend=self._backend,
leap_signaler=self._leap_signaler)
self._wizard.accepted.connect(self._finish_init)
@@ -550,8 +498,7 @@ class MainWindow(QtGui.QMainWindow):
Display the Advanced Key Management dialog.
"""
- domain = self._providers.get_selected_provider()
- logged_user = "{0}@{1}".format(self._logged_user, domain)
+ logged_user = self._login_widget.get_logged_user()
details = self._provider_details
mx_provided = False
@@ -572,8 +519,14 @@ class MainWindow(QtGui.QMainWindow):
Display the preferences window.
"""
- account = Account(self._logged_user,
- self._providers.get_selected_provider())
+ logged_user = self._login_widget.get_logged_user()
+ if logged_user is not None:
+ user, domain = logged_user.split('@')
+ else:
+ user = None
+ domain = self._providers.get_selected_provider()
+
+ account = Account(user, domain)
pref_win = PreferencesWindow(self, account, self.app)
pref_win.show()
@@ -767,6 +720,10 @@ class MainWindow(QtGui.QMainWindow):
providers = self._settings.get_configured_providers()
self._providers.set_providers(providers)
+
+ provider = self._providers.get_selected_provider()
+ self._login_widget.set_provider(provider)
+
self._show_systray()
if not self._start_hidden:
@@ -870,12 +827,6 @@ class MainWindow(QtGui.QMainWindow):
self._eip_menu.setVisible(visible)
self._ui_eip_visible = visible
- 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?
-
#
# systray
#
@@ -1127,76 +1078,19 @@ class MainWindow(QtGui.QMainWindow):
return not (has_provider_on_disk and skip_first_run)
@QtCore.Slot()
- def _download_provider_config(self):
- """
- Start the bootstrapping sequence. It will download the
- provider configuration if it's not present, otherwise will
- emit the corresponding signals inmediately
- """
- self._disconnect_scheduled_login()
- domain = self._providers.get_selected_provider()
- self._backend.provider_setup(provider=domain)
-
- @QtCore.Slot(dict)
- 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 data[PASSED_KEY]:
- selected_provider = self._providers.get_selected_provider()
- self._backend.provider_bootstrap(provider=selected_provider)
- else:
- logger.error(data[ERROR_KEY])
- self._login_problem_provider()
-
- @QtCore.Slot()
- def _login_problem_provider(self):
- """
- Warn the user about a problem with the provider during login.
- """
- # XXX triggers?
- self._login_widget.set_status(
- self.tr("Unable to login: Problem with provider"))
- self._login_widget.set_enabled(True)
-
- def _schedule_login(self):
+ def _disconnect_login_wait(self):
"""
- Schedule the login sequence to go after the EIP started.
-
- The login sequence is connected to all finishing status of EIP
- (connected, disconnected, aborted or died) to continue with the login
- after EIP.
- """
- logger.debug('Login scheduled when eip_connected is triggered')
- eip_sigs = self._eip_conductor.qtsigs
- eip_sigs.connected_signal.connect(self._download_provider_config)
- eip_sigs.disconnected_signal.connect(self._download_provider_config)
- eip_sigs.connection_aborted_signal.connect(
- self._download_provider_config)
- eip_sigs.connection_died_signal.connect(self._download_provider_config)
-
- def _disconnect_scheduled_login(self):
- """
- Disconnect scheduled login signals if exists
+ Disconnect the EIP finishing signal to the wait flag on the login
+ widget.
"""
try:
eip_sigs = self._eip_conductor.qtsigs
- eip_sigs.connected_signal.disconnect(
- self._download_provider_config)
- eip_sigs.disconnected_signal.disconnect(
- self._download_provider_config)
- eip_sigs.connection_aborted_signal.disconnect(
- self._download_provider_config)
- eip_sigs.connection_died_signal.disconnect(
- self._download_provider_config)
+ slot = lambda: self._login_widget.wait_for_login(False)
+
+ eip_sigs.connected_signal.disconnect(slot)
+ eip_sigs.disconnected_signal.disconnect(slot)
+ eip_sigs.connection_aborted_signal.disconnect(slot)
+ eip_sigs.connection_died_signal.disconnect(slot)
except Exception:
# signal not connected
pass
@@ -1217,9 +1111,10 @@ class MainWindow(QtGui.QMainWindow):
# TODO: we should handle the case that EIP is autostarting since we
# won't get a warning until EIP has fully started.
# TODO: we need to add a check for the mail status (smtp/imap/soledad)
- something_runing = (self._logged_user is not None or
+ something_runing = (self._login_widget.get_logged_user() is not None or
self._already_started_eip)
provider = self._providers.get_selected_provider()
+ self._login_widget.set_provider(provider)
if not something_runing:
if wizard:
@@ -1269,118 +1164,35 @@ class MainWindow(QtGui.QMainWindow):
start the SRP authentication, and as the last step
bootstrapping the EIP service
"""
- # TODO most of this could ve handled by the login widget,
- provider = self._providers.get_selected_provider()
- if flags.OFFLINE is True:
- logger.debug("OFFLINE mode! bypassing remote login")
- # TODO reminder, we're not handling logout for offline
- # mode.
- self._login_widget.logged_in(provider)
- self._logged_in_offline = True
- self._set_label_offline()
- self.offline_mode_bypass_login.emit()
- else:
- self.ui.action_create_new_account.setEnabled(False)
- if self._login_widget.start_login(provider):
- if self._trying_to_start_eip:
- self._schedule_login()
- else:
- self._download_provider_config()
+ self.ui.action_create_new_account.setEnabled(False)
- @QtCore.Slot(unicode)
- 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._login_widget.set_status(msg)
- self._login_widget.set_enabled(True)
- self.ui.action_create_new_account.setEnabled(True)
-
- @QtCore.Slot()
- def _cancel_login(self):
- """
- TRIGGERS:
- self._login_widget.cancel_login
-
- Stop the login sequence.
- """
- logger.debug("Cancelling log in.")
- self._disconnect_scheduled_login()
-
- self._cancel_ongoing_defers()
-
- # Needed in case of EIP starting and login deferer never set
- self._set_login_cancelled()
+ ok = self._login_widget.do_login()
+ if not ok:
+ logger.error("There was a problem triggering the login.")
+ return
def _cancel_ongoing_defers(self):
"""
Cancel the running defers to avoid app blocking.
"""
# XXX: Should we stop all the backend defers?
- self._backend.provider_cancel_setup()
- self._backend.user_cancel_login()
self._backend.soledad_cancel_bootstrap()
self._backend.soledad_close()
self._soledad_started = False
@QtCore.Slot()
- def _set_login_cancelled(self):
- """
- TRIGGERS:
- Signaler.prov_cancelled_setup fired by
- self._backend.provider_cancel_setup()
-
- Re-enable the login widget and display a message for
- the cancelled operation.
- """
- self._login_widget.set_status(self.tr("Log in cancelled by the user."))
- self._login_widget.set_enabled(True)
-
- @QtCore.Slot(dict)
- 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 data[PASSED_KEY]:
- username = self._login_widget.get_user()
- password = self._login_widget.get_password()
-
- self._show_hide_unsupported_services()
-
- domain = self._providers.get_selected_provider()
- self._backend.user_login(provider=domain,
- username=username, password=password)
- else:
- logger.error(data[ERROR_KEY])
- self._login_problem_provider()
-
- @QtCore.Slot()
- def _authentication_finished(self):
+ def _on_user_logged_in(self):
"""
TRIGGERS:
- self._srp_auth.authentication_finished
+ self._login_widget.logged_in
Once the user is properly authenticated, try starting the EIP
- service
+ service.
"""
- self._login_widget.set_status(self.tr("Succeeded"), error=False)
+ self._disconnect_login_wait()
- self._logged_user = self._login_widget.get_user()
- user = self._logged_user
+ user = self._login_widget.get_logged_user()
domain = self._providers.get_selected_provider()
full_user_id = make_address(user, domain)
self._mail_conductor.userid = full_user_id
@@ -1398,15 +1210,25 @@ class MainWindow(QtGui.QMainWindow):
if MX_SERVICE not in self._provider_details['services']:
self._set_mx_visible(False)
+ @QtCore.Slot()
+ def _on_user_logged_out(self):
+ """
+ TRIGGER:
+ self._login_widget.logged_out
+
+ Switch the stackedWidget back to the login stage after
+ logging out
+ """
+ self._mail_conductor.stop_mail_services()
+ self._mail_status.mail_state_disabled()
+ self._show_hide_unsupported_services()
+
def _start_eip_bootstrap(self):
"""
Change the stackedWidget index to the EIP status one and
triggers the eip bootstrapping.
"""
-
domain = self._providers.get_selected_provider()
- self._login_widget.logged_in(domain)
-
self._enabled_services = self._settings.get_enabled_services(domain)
# TODO separate UI from logic.
@@ -1475,8 +1297,13 @@ class MainWindow(QtGui.QMainWindow):
return eip_enabled and eip_provided
+ @QtCore.Slot()
def _maybe_run_soledad_setup_checks(self):
"""
+ TRIGGERS:
+ self._eip_conductor.qtsigs.connected_signal
+ self._login_widget.login_offline_finished
+
Conditionally start Soledad.
"""
# TODO split.
@@ -1501,8 +1328,9 @@ class MainWindow(QtGui.QMainWindow):
self._backend.soledad_load_offline(username=full_user_id,
password=password, uuid=uuid)
else:
- if self._logged_user is not None:
- domain = self._providers.get_selected_provider()
+ logged_user = self._login_widget.get_logged_user()
+ if logged_user is not None:
+ username, domain = logged_user.split('@')
self._backend.soledad_bootstrap(username=username,
domain=domain,
password=password)
@@ -1550,6 +1378,30 @@ class MainWindow(QtGui.QMainWindow):
self._eip_status.enable_eip_start()
@QtCore.Slot()
+ def _on_eip_connecting(self):
+ """
+ TRIGGERS:
+ self._eip_conductor.qtsigs.connecting_signal
+
+ This is triggered when EIP starts connecting.
+
+ React to any EIP finishing signal[1] that means that the network is
+ ready to be used and trigger the "don't keep waiting" action on the
+ login widget.
+
+ [1] finishing signal => connected, disconnected, aborted or died
+ """
+ self._login_widget.wait_for_login(True)
+
+ eip_sigs = self._eip_conductor.qtsigs
+ slot = lambda: self._login_widget.wait_for_login(False)
+
+ eip_sigs.connected_signal.connect(slot)
+ eip_sigs.disconnected_signal.connect(slot)
+ eip_sigs.connection_aborted_signal.connect(slot)
+ eip_sigs.connection_died_signal.connect(slot)
+
+ @QtCore.Slot()
def _on_eip_connection_connected(self):
"""
TRIGGERS:
@@ -1672,8 +1524,7 @@ class MainWindow(QtGui.QMainWindow):
if not self._already_started_eip:
if EIP_SERVICE in self._enabled_services:
if missing_helpers:
- msg = self.tr(
- "Disabled: missing helper files")
+ msg = self.tr("Disabled: missing helper files")
else:
msg = self.tr("Not supported"),
self._eip_status.set_eip_status(msg, error=True)
@@ -1727,68 +1578,6 @@ class MainWindow(QtGui.QMainWindow):
# end of EIP methods ---------------------------------------------
- @QtCore.Slot()
- def _logout(self):
- """
- TRIGGERS:
- self._login_widget.logout
-
- Start the logout sequence
- """
- self._cancel_ongoing_defers()
-
- # XXX: If other defers are doing authenticated stuff, this
- # might conflict with those. CHECK!
- self._backend.user_logout()
- self.logout.emit()
-
- @QtCore.Slot()
- def _logout_error(self):
- """
- TRIGGER:
- self._srp_auth.logout_error
-
- Inform the user about a logout error.
- """
- self._login_widget.done_logout()
- self._login_widget.set_status(
- self.tr("Something went wrong with the logout."))
-
- @QtCore.Slot()
- def _logout_ok(self):
- """
- TRIGGER:
- self._srp_auth.logout_ok
-
- Switch the stackedWidget back to the login stage after
- logging out
- """
- self._login_widget.done_logout()
-
- self._logged_user = None
- self._login_widget.logged_out()
- self._mail_status.mail_state_disabled()
-
- self._show_hide_unsupported_services()
-
- @QtCore.Slot(dict)
- def _intermediate_stage(self, data):
- # TODO this method name is confusing as hell.
- """
- TRIGGERS:
- self._backend.signaler.prov_name_resolution
- self._backend.signaler.prov_https_connection
- self._backend.signaler.prov_download_ca_cert
-
- If there was a problem, display it, otherwise it does nothing.
- This is used for intermediate bootstrapping stages, in case
- they fail.
- """
- passed = data[PASSED_KEY]
- if not passed:
- logger.error(data[ERROR_KEY])
- self._login_problem_provider()
-
#
# window handling methods
#
@@ -1840,9 +1629,8 @@ class MainWindow(QtGui.QMainWindow):
logger.debug('Stopping mail services')
self._mail_conductor.stop_mail_services()
- if self._logged_user is not None:
- logger.debug("Doing logout")
- self._backend.user_logout()
+ logger.debug("Doing logout")
+ self._login_widget.do_logout()
logger.debug('Terminating vpn')
self._backend.eip_stop(shutdown=True)
diff --git a/src/leap/bitmask/gui/signaltracker.py b/src/leap/bitmask/gui/signaltracker.py
new file mode 100644
index 00000000..4884334d
--- /dev/null
+++ b/src/leap/bitmask/gui/signaltracker.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+# signaltracker.py
+# Copyright (C) 2013 LEAP
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from PySide import QtCore
+
+
+class SignalTracker(QtCore.QObject):
+ """
+ A class meant to be inherited from that helps to do the Qt connect and keep
+ track of the connections made, allowing to disconnect those tracked signals
+ as well.
+ """
+ def __init__(self):
+ # this list contains the connected signals that we want to keep track.
+ # each item of the list is a:
+ # tuple of (Qt signal, callable or Qt slot or Qt signal)
+ self._connected_signals = []
+
+ def connect_and_track(self, signal, method):
+ """
+ Connect the signal and keep track of it.
+
+ :param signal: the signal to connect to.
+ :type signal: QtCore.Signal
+ :param method: the method to call when the signal is triggered.
+ :type method: callable, Slot or Signal
+ """
+ self._connected_signals.append((signal, method))
+ signal.connect(method)
+
+ def disconnect_and_untrack(self):
+ """
+ Disconnect all the tracked signals.
+ """
+ for signal, method in self._connected_signals:
+ try:
+ signal.disconnect(method)
+ except RuntimeError:
+ pass # Signal was not connected
+
+ self._connected_signals = []
diff --git a/src/leap/bitmask/gui/ui/login.ui b/src/leap/bitmask/gui/ui/login.ui
index bfd5f9c0..9ee9a283 100644
--- a/src/leap/bitmask/gui/ui/login.ui
+++ b/src/leap/bitmask/gui/ui/login.ui
@@ -29,12 +29,12 @@
<string notr="true"/>
</property>
<layout class="QGridLayout" name="gridLayout">
- <property name="margin">
- <number>0</number>
- </property>
<property name="verticalSpacing">
<number>6</number>
</property>
+ <property name="margin">
+ <number>0</number>
+ </property>
<item row="2" column="0">
<spacer name="horizontalSpacer_2">
<property name="orientation">
@@ -71,6 +71,9 @@
</property>
<item row="3" column="1">
<widget class="QPushButton" name="btnLogin">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
@@ -134,6 +137,9 @@
<property name="inputMask">
<string/>
</property>
+ <property name="echoMode">
+ <enum>QLineEdit::Password</enum>
+ </property>
</widget>
</item>
</layout>
@@ -283,10 +289,47 @@
</customwidget>
</customwidgets>
<tabstops>
+ <tabstop>lnUser</tabstop>
+ <tabstop>lnPassword</tabstop>
<tabstop>chkRemember</tabstop>
+ <tabstop>btnLogin</tabstop>
+ <tabstop>btnLogout</tabstop>
</tabstops>
<resources>
<include location="../../../../../data/resources/icons.qrc"/>
</resources>
- <connections/>
+ <connections>
+ <connection>
+ <sender>lnPassword</sender>
+ <signal>returnPressed()</signal>
+ <receiver>btnLogin</receiver>
+ <slot>click()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>212</x>
+ <y>171</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>169</x>
+ <y>234</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>lnUser</sender>
+ <signal>returnPressed()</signal>
+ <receiver>lnPassword</receiver>
+ <slot>setFocus()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>309</x>
+ <y>140</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>304</x>
+ <y>163</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
</ui>
diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py
index ff9cae55..f1eab211 100644
--- a/src/leap/bitmask/gui/wizard.py
+++ b/src/leap/bitmask/gui/wizard.py
@@ -30,6 +30,7 @@ from leap.bitmask.backend.leapbackend import ERROR_KEY, PASSED_KEY
from leap.bitmask.config import flags
from leap.bitmask.config.leapsettings import LeapSettings
+from leap.bitmask.gui.signaltracker import SignalTracker
from leap.bitmask.services import get_service_display_name, get_supported
from leap.bitmask.util.credentials import password_checks, username_checks
from leap.bitmask.util.credentials import USERNAME_REGEX
@@ -41,8 +42,7 @@ QtDelayedCall = QtCore.QTimer.singleShot
logger = logging.getLogger(__name__)
-class Wizard(QtGui.QWizard):
-
+class Wizard(QtGui.QWizard, SignalTracker):
"""
First run wizard to register a user and setup a provider
"""
@@ -62,12 +62,11 @@ class Wizard(QtGui.QWizard):
:type backend: Backend
"""
QtGui.QWizard.__init__(self)
+ SignalTracker.__init__(self)
self.ui = Ui_Wizard()
self.ui.setupUi(self)
- self._connected_signals = []
-
self.setPixmap(QtGui.QWizard.LogoPixmap,
QtGui.QPixmap(":/images/mask-icon.png"))
@@ -83,9 +82,9 @@ class Wizard(QtGui.QWizard):
self._use_existing_provider = False
self.ui.grpCheckProvider.setVisible(False)
- self._connect_and_track(self.ui.btnCheck.clicked, self._check_provider)
- self._connect_and_track(self.ui.lnProvider.returnPressed,
- self._check_provider)
+ conntrack = self.connect_and_track
+ conntrack(self.ui.btnCheck.clicked, self._check_provider)
+ conntrack(self.ui.lnProvider.returnPressed, self._check_provider)
self._leap_signaler = leap_signaler
@@ -97,27 +96,22 @@ class Wizard(QtGui.QWizard):
# this details are set when the provider download is complete.
self._provider_details = None
- self._connect_and_track(self.currentIdChanged,
- self._current_id_changed)
+ conntrack(self.currentIdChanged, self._current_id_changed)
- self._connect_and_track(self.ui.lnProvider.textChanged,
- self._enable_check)
- self._connect_and_track(self.ui.rbNewProvider.toggled,
- lambda x: self._enable_check())
- self._connect_and_track(self.ui.cbProviders.currentIndexChanged[int],
- self._reset_provider_check)
+ conntrack(self.ui.lnProvider.textChanged, self._enable_check)
+ conntrack(self.ui.rbNewProvider.toggled,
+ lambda x: self._enable_check())
+ conntrack(self.ui.cbProviders.currentIndexChanged[int],
+ self._reset_provider_check)
- self._connect_and_track(self.ui.lblUser.returnPressed,
- self._focus_password)
- self._connect_and_track(self.ui.lblPassword.returnPressed,
- self._focus_second_password)
- self._connect_and_track(self.ui.lblPassword2.returnPressed,
- self._register)
- self._connect_and_track(self.ui.btnRegister.clicked,
- self._register)
+ conntrack(self.ui.lblUser.returnPressed, self._focus_password)
+ conntrack(self.ui.lblPassword.returnPressed,
+ self._focus_second_password)
+ conntrack(self.ui.lblPassword2.returnPressed, self._register)
+ conntrack(self.ui.btnRegister.clicked, self._register)
- self._connect_and_track(self.ui.rbExistingProvider.toggled,
- self._skip_provider_checks)
+ conntrack(self.ui.rbExistingProvider.toggled,
+ self._skip_provider_checks)
usernameRe = QtCore.QRegExp(USERNAME_REGEX)
self.ui.lblUser.setValidator(
@@ -142,19 +136,7 @@ class Wizard(QtGui.QWizard):
self._provider_checks_ok = False
self._provider_setup_ok = False
- self._connect_and_track(self.finished, self._wizard_finished)
-
- def _connect_and_track(self, signal, method):
- """
- Helper to connect signals and keep track of them.
-
- :param signal: the signal to connect to.
- :type signal: QtCore.Signal
- :param method: the method to call when the signal is triggered.
- :type method: callable, Slot or Signal
- """
- self._connected_signals.append((signal, method))
- signal.connect(method)
+ conntrack(self.finished, self._wizard_finished)
@QtCore.Slot()
def _wizard_finished(self):
@@ -170,7 +152,7 @@ class Wizard(QtGui.QWizard):
self._provider_setup_ok = False
self.ui.lnProvider.setText('')
self.ui.grpCheckProvider.setVisible(False)
- self._disconnect_tracked()
+ self.disconnect_and_untrack()
def _load_configured_providers(self):
"""
@@ -781,7 +763,7 @@ class Wizard(QtGui.QWizard):
Connects all the backend signals with the wizard.
"""
sig = self._leap_signaler
- conntrack = self._connect_and_track
+ conntrack = self.connect_and_track
conntrack(sig.prov_name_resolution, self._name_resolution)
conntrack(sig.prov_https_connection, self._https_connection)
conntrack(sig.prov_download_provider_info,
@@ -797,14 +779,3 @@ class Wizard(QtGui.QWizard):
conntrack(sig.srp_registration_finished, self._registration_finished)
conntrack(sig.srp_registration_failed, self._registration_failed)
conntrack(sig.srp_registration_taken, self._registration_taken)
-
- def _disconnect_tracked(self):
- """
- This method is called when the wizard dialog is closed.
- We disconnect all the signals in here.
- """
- for signal, method in self._connected_signals:
- try:
- signal.disconnect(method)
- except RuntimeError:
- pass # Signal was not connected