diff options
-rw-r--r-- | changes/feature_2774-check_provider_api_version | 1 | ||||
-rw-r--r-- | changes/feature_granular_login | 2 | ||||
-rw-r--r-- | changes/feature_mergesystray | 2 | ||||
-rw-r--r-- | changes/feature_saveprovider | 2 | ||||
-rw-r--r-- | src/leap/config/leapsettings.py | 19 | ||||
-rw-r--r-- | src/leap/crypto/srpauth.py | 84 | ||||
-rw-r--r-- | src/leap/gui/mainwindow.py | 61 | ||||
-rw-r--r-- | src/leap/gui/ui/wizard.ui | 3 | ||||
-rw-r--r-- | src/leap/gui/wizard.py | 4 | ||||
-rw-r--r-- | src/leap/provider/__init__.py | 0 | ||||
-rw-r--r-- | src/leap/provider/supportedapis.py | 38 | ||||
-rw-r--r-- | src/leap/services/eip/providerbootstrapper.py | 21 |
12 files changed, 177 insertions, 60 deletions
diff --git a/changes/feature_2774-check_provider_api_version b/changes/feature_2774-check_provider_api_version new file mode 100644 index 00000000..82294ce4 --- /dev/null +++ b/changes/feature_2774-check_provider_api_version @@ -0,0 +1 @@ + o Check if the provider api version is supported. Closes feature #2774. diff --git a/changes/feature_granular_login b/changes/feature_granular_login new file mode 100644 index 00000000..8cef2c5e --- /dev/null +++ b/changes/feature_granular_login @@ -0,0 +1,2 @@ + o Make the login steps be a chain of defers in order to be able to + have more cancel points for the whole procedure. Closes #2571
\ No newline at end of file diff --git a/changes/feature_mergesystray b/changes/feature_mergesystray new file mode 100644 index 00000000..6bb6819e --- /dev/null +++ b/changes/feature_mergesystray @@ -0,0 +1,2 @@ + o Only use one systray icon, repesenting the status for EIP. Closes + #2762
\ No newline at end of file diff --git a/changes/feature_saveprovider b/changes/feature_saveprovider new file mode 100644 index 00000000..98c911c2 --- /dev/null +++ b/changes/feature_saveprovider @@ -0,0 +1,2 @@ + o Save the default provider to be used for autostart EIP as + DefaultProvider in leap.conf. Closes #2793
\ No newline at end of file diff --git a/src/leap/config/leapsettings.py b/src/leap/config/leapsettings.py index 59a0a16d..006be851 100644 --- a/src/leap/config/leapsettings.py +++ b/src/leap/config/leapsettings.py @@ -65,6 +65,7 @@ class LeapSettings(object): AUTOLOGIN_KEY = "AutoLogin" PROPERPROVIDER_KEY = "ProperProvider" REMEMBER_KEY = "RememberUserAndPass" + DEFAULTPROVIDER_KEY = "DefaultProvider" def __init__(self, standalone=False): """ @@ -230,3 +231,21 @@ class LeapSettings(object): """ leap_assert_type(properprovider, bool) self._settings.setValue(self.PROPERPROVIDER_KEY, properprovider) + + def get_defaultprovider(self): + """ + Returns the default provider to be used for autostarting EIP + + :rtype: str or None + """ + return self._settings.value(self.DEFAULTPROVIDER_KEY, None) + + def set_defaultprovider(self, provider): + """ + Sets the default provider to be used for autostarting EIP + + :param provider: provider to use + :type provider: str + """ + leap_assert(len(provider) > 0, "We cannot save an empty provider") + self._settings.setValue(self.DEFAULTPROVIDER_KEY, provider) diff --git a/src/leap/crypto/srpauth.py b/src/leap/crypto/srpauth.py index 28086279..52267b3b 100644 --- a/src/leap/crypto/srpauth.py +++ b/src/leap/crypto/srpauth.py @@ -25,10 +25,10 @@ import json #this error is raised from requests from simplejson.decoder import JSONDecodeError -from PySide import QtCore, QtGui +from PySide import QtCore +from twisted.internet import threads from leap.common.check import leap_assert -from leap.config.providerconfig import ProviderConfig from leap.util.request_helpers import get_content from leap.common.events import signal as events_signal from leap.common.events import events_pb2 as proto @@ -124,13 +124,15 @@ class SRPAuth(QtCore.QObject): self._srp_a = A - def _start_authentication(self, username, password): + def _start_authentication(self, _, username, password): """ Sends the first request for authentication to retrieve the salt and B parameter Might raise SRPAuthenticationError + :param _: IGNORED, output from the previous callback (None) + :type _: IGNORED :param username: username to login :type username: str :param password: password for the username @@ -187,17 +189,15 @@ class SRPAuth(QtCore.QObject): return salt, B - def _process_challenge(self, salt, B, username): + def _process_challenge(self, salt_B, username): """ Given the salt and B processes the auth challenge and generates the M2 parameter Might throw SRPAuthenticationError - :param salt: salt for the username - :type salt: str - :param B: B SRP parameter - :type B: str + :param salt_B: salt and B parameters for the username + :type salt_B: tuple :param username: username for this session :type username: str @@ -206,6 +206,7 @@ class SRPAuth(QtCore.QObject): """ logger.debug("Processing challenge...") try: + salt, B = salt_B unhex_salt = self._safe_unhexlify(salt) unhex_B = self._safe_unhexlify(B) except TypeError as e: @@ -254,9 +255,14 @@ class SRPAuth(QtCore.QObject): (auth_result.status_code,)) json_content = json.loads(content) - M2 = json_content.get("M2", None) - uid = json_content.get("id", None) - token = json_content.get("token", None) + + try: + M2 = json_content.get("M2", None) + uid = json_content.get("id", None) + token = json_content.get("token", None) + except Exception as e: + logger.error(e) + raise Exception("Something went wrong with the login") events_signal(proto.CLIENT_UID, content=uid) @@ -318,17 +324,22 @@ class SRPAuth(QtCore.QObject): :type username: str :param password: password for this user :type password: str + + :returns: A defer on a different thread + :rtype: twisted.internet.defer.Deferred """ leap_assert(self.get_session_id() is None, "Already logged in") - self._authentication_preprocessing(username, password) - salt, B = self._start_authentication(username, password) - M2 = self._process_challenge(salt, B, username) + d = threads.deferToThread(self._authentication_preprocessing, + username=username, + password=password) - self._verify_session(M2) + d.addCallback(self._start_authentication, username=username, + password=password) + d.addCallback(self._process_challenge, username=username) + d.addCallback(self._verify_session) - leap_assert(self.get_session_id(), "Something went wrong because" - " we don't have the auth cookie afterwards") + return d def logout(self): """ @@ -388,10 +399,6 @@ class SRPAuth(QtCore.QObject): authentication_finished = QtCore.Signal(bool, str) logout_finished = QtCore.Signal(bool, str) - DO_NOTHING = 0 - DO_LOGIN = 1 - DO_LOGOUT = 2 - def __init__(self, provider_config): """ Creates a singleton instance if needed @@ -406,8 +413,6 @@ class SRPAuth(QtCore.QObject): # Store instance reference as the only member in the handle self.__dict__['_SRPAuth__instance'] = SRPAuth.__instance - self._should_login = self.DO_NOTHING - self._should_login_lock = QtCore.QMutex() self._username = None self._password = None @@ -423,16 +428,31 @@ class SRPAuth(QtCore.QObject): :type password: str """ - try: - self.__instance.authenticate(username, password) + d = self.__instance.authenticate(username, password) + d.addCallback(self._gui_notify) + d.addErrback(self._errback) + return d - logger.debug("Successful login!") - self.authentication_finished.emit(True, self.tr("Succeeded")) - return True - except Exception as e: - logger.error("Error logging in %s" % (e,)) - self.authentication_finished.emit(False, "%s" % (e,)) - return False + def _gui_notify(self, _): + """ + Callback that notifies the UI with the proper signal. + + :param _: IGNORED, output from the previous callback (None) + :type _: IGNORED + """ + logger.debug("Successful login!") + self.authentication_finished.emit(True, self.tr("Succeeded")) + + def _errback(self, failure): + """ + General errback for the whole login process. Will notify the + UI with the proper signal. + + :param failure: Failure object captured from a callback. + :type failure: twisted.python.failure.Failure + """ + logger.error("Error logging in %s" % (failure,)) + self.authentication_finished.emit(False, "%s" % (failure,)) def get_session_id(self): return self.__instance.get_session_id() diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 96c50201..d9fe478c 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -217,12 +217,11 @@ class MainWindow(QtGui.QMainWindow): self._really_quit = False self._systray = None - self._vpn_systray = None - self._action_eip_status = QtGui.QAction(self.tr("Encryption is OFF"), + self._action_eip_status = QtGui.QAction(self.tr("Encrypted internet is OFF"), self) self._action_eip_status.setEnabled(False) - self._action_eip_startstop = QtGui.QAction(self.tr("Stop"), self) + self._action_eip_startstop = QtGui.QAction(self.tr("Turn encryption ON"), self) self._action_eip_startstop.triggered.connect( self._stop_eip) self._action_eip_write = QtGui.QAction( @@ -234,7 +233,7 @@ class MainWindow(QtGui.QMainWindow): "%12.2f Kb" % (0.0,), self) self._action_eip_read.setEnabled(False) - self._action_visible = QtGui.QAction(self.tr("Hide"), self) + self._action_visible = QtGui.QAction(self.tr("Hide Main Window"), self) self._action_visible.triggered.connect(self._toggle_visible) self._enabled_services = [] @@ -259,6 +258,8 @@ class MainWindow(QtGui.QMainWindow): self._soledad = None self._keymanager = None + self._login_defer = None + self._smtp_config = SMTPConfig() if self._first_run(): @@ -453,22 +454,15 @@ class MainWindow(QtGui.QMainWindow): systrayMenu.addAction(self.ui.action_sign_out) systrayMenu.addSeparator() systrayMenu.addAction(self.ui.action_quit) + systrayMenu.addSeparator() + systrayMenu.addAction(self._action_eip_status) + systrayMenu.addAction(self._action_eip_startstop) self._systray = QtGui.QSystemTrayIcon(self) self._systray.setContextMenu(systrayMenu) - self._systray.setIcon(QtGui.QIcon(self.LOGGED_OUT_ICON)) + self._systray.setIcon(QtGui.QIcon(self.ERROR_ICON)) self._systray.setVisible(True) self._systray.activated.connect(self._toggle_visible) - vpn_systrayMenu = QtGui.QMenu(self) - vpn_systrayMenu.addAction(self._action_eip_status) - vpn_systrayMenu.addAction(self._action_eip_startstop) - vpn_systrayMenu.addAction(self._action_eip_read) - vpn_systrayMenu.addAction(self._action_eip_write) - self._vpn_systray = QtGui.QSystemTrayIcon(self) - self._vpn_systray.setContextMenu(vpn_systrayMenu) - self._vpn_systray.setIcon(QtGui.QIcon(self.ERROR_ICON)) - self._vpn_systray.setVisible(False) - def _toggle_visible(self, reason=None): """ SLOT @@ -612,7 +606,7 @@ class MainWindow(QtGui.QMainWindow): :param status: status message :type status: str """ - self._vpn_systray.setToolTip(status) + self._systray.setToolTip(status) if error: status = "<font color='red'><b>%s</b></font>" % (status,) self.ui.lblEIPStatus.setText(status) @@ -749,10 +743,7 @@ class MainWindow(QtGui.QMainWindow): self._srp_auth.logout_finished.connect( self._done_logging_out) - auth_partial = partial(self._srp_auth.authenticate, - username, - password) - threads.deferToThread(auth_partial) + self._login_defer = self._srp_auth.authenticate(username, password) else: self._set_status(data[self._provider_bootstrapper.ERROR_KEY]) self._login_set_enabled(True) @@ -772,6 +763,7 @@ class MainWindow(QtGui.QMainWindow): # "Succeeded" message and then we switch to the EIP status # panel QtCore.QTimer.singleShot(1000, self._switch_to_status) + self._login_defer = None else: self._login_set_enabled(True) @@ -781,7 +773,6 @@ class MainWindow(QtGui.QMainWindow): triggers the eip bootstrapping """ self.ui.stackedWidget.setCurrentIndex(self.EIP_STATUS_INDEX) - self._systray.setIcon(self.LOGGED_IN_ICON) self._soledad_bootstrapper.run_soledad_setup_checks( self._provider_config, @@ -905,17 +896,29 @@ class MainWindow(QtGui.QMainWindow): return host, port def _start_eip(self): + """ + SLOT + TRIGGERS: + self.ui.btnEipStartStop.clicked + self._action_eip_startstop.triggered + or called from _finish_eip_bootstrap + + Starts EIP + """ try: host, port = self._get_socket_host() self._vpn.start(eipconfig=self._eip_config, providerconfig=self._provider_config, socket_host=host, socket_port=port) - self.ui.btnEipStartStop.setText(self.tr("Stop EIP")) + + self._settings.set_defaultprovider( + self._provider_config.get_domain()) + self.ui.btnEipStartStop.setText(self.tr("Turn Encryption OFF")) self.ui.btnEipStartStop.disconnect(self) self.ui.btnEipStartStop.clicked.connect( self._stop_eip) - self._action_eip_startstop.setText(self.tr("Stop")) + self._action_eip_startstop.setText(self.tr("Turn Encryption OFF")) self._action_eip_startstop.disconnect(self) self._action_eip_startstop.triggered.connect( self._stop_eip) @@ -944,11 +947,11 @@ class MainWindow(QtGui.QMainWindow): self._vpn.set_should_quit() self._set_eip_status(self.tr("EIP has stopped")) self._set_eip_status_icon("error") - self.ui.btnEipStartStop.setText(self.tr("Start EIP")) + self.ui.btnEipStartStop.setText(self.tr("Turn Encryption ON")) self.ui.btnEipStartStop.disconnect(self) self.ui.btnEipStartStop.clicked.connect( self._start_eip) - self._action_eip_startstop.setText(self.tr("Start")) + self._action_eip_startstop.setText(self.tr("Turn Encryption ON")) self._action_eip_startstop.disconnect(self) self._action_eip_startstop.triggered.connect( self._start_eip) @@ -964,7 +967,6 @@ class MainWindow(QtGui.QMainWindow): if self._provider_config.provides_eip() and \ self._enabled_services.count(self.OPENVPN_SERVICE) > 0: - self._vpn_systray.setVisible(True) self._eip_bootstrapper.run_eip_setup_checks( self._provider_config, download_if_needed=True) @@ -989,12 +991,13 @@ class MainWindow(QtGui.QMainWindow): if status in ("WAIT", "AUTH", "GET_CONFIG", "RECONNECTING", "ASSIGN_IP"): selected_pixmap = self.CONNECTING_ICON + tray_message = self.tr("Turning Encryption ON") elif status in ("CONNECTED"): tray_message = self.tr("Encryption is ON") selected_pixmap = self.CONNECTED_ICON self.ui.lblVPNStatusIcon.setPixmap(selected_pixmap) - self._vpn_systray.setIcon(QtGui.QIcon(selected_pixmap)) + self._systray.setIcon(QtGui.QIcon(selected_pixmap)) self._action_eip_status.setText(tray_message) def _update_vpn_state(self, data): @@ -1095,7 +1098,6 @@ class MainWindow(QtGui.QMainWindow): Switches the stackedWidget back to the login stage after logging out """ - self._systray.setIcon(self.LOGGED_OUT_ICON) self.ui.action_sign_out.setEnabled(False) self.ui.stackedWidget.setCurrentIndex(self.LOGIN_INDEX) self.ui.lnPassword.setText("") @@ -1188,6 +1190,9 @@ class MainWindow(QtGui.QMainWindow): self.close() + if self._login_defer: + self._login_defer.cancel() + if self._quit_callback: self._quit_callback() logger.debug('Bye.') diff --git a/src/leap/gui/ui/wizard.ui b/src/leap/gui/ui/wizard.ui index 96cf4621..4b9cab1c 100644 --- a/src/leap/gui/ui/wizard.ui +++ b/src/leap/gui/ui/wizard.ui @@ -696,6 +696,9 @@ <property name="alignment"> <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> </property> + <property name="wordWrap"> + <bool>true</bool> + </property> </widget> </item> </layout> diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py index 8c5ea2a0..d03427db 100644 --- a/src/leap/gui/wizard.py +++ b/src/leap/gui/wizard.py @@ -278,6 +278,9 @@ class Wizard(QtGui.QWizard): self.ui.lblPassword2.clearFocus() self._set_registration_fields_visibility(False) + + # Allow the user to remember his password + self.ui.chkRemember.setVisible(True) self.ui.chkRemember.setEnabled(True) self.page(self.REGISTER_USER_PAGE).set_completed() @@ -575,6 +578,7 @@ class Wizard(QtGui.QWizard): "%s") % (self._provider_config .get_name(),)) + self.ui.chkRemember.setVisible(False) if pageId == self.SERVICES_PAGE: self._populate_services() diff --git a/src/leap/provider/__init__.py b/src/leap/provider/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/leap/provider/__init__.py diff --git a/src/leap/provider/supportedapis.py b/src/leap/provider/supportedapis.py new file mode 100644 index 00000000..3e650ba2 --- /dev/null +++ b/src/leap/provider/supportedapis.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# supportedapis.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/>. + +""" +API Support check. +""" + + +class SupportedAPIs(object): + """ + Class responsible of checking for API compatibility. + """ + SUPPORTED_APIS = ["1"] + + @classmethod + def supports(self, api_version): + """ + :param api_version: the version number of the api that we need to check + :type api_version: str + + :returns: if that version is supported or not. + :return type: bool + """ + return api_version in self.SUPPORTED_APIS diff --git a/src/leap/services/eip/providerbootstrapper.py b/src/leap/services/eip/providerbootstrapper.py index 1339e086..e099eee7 100644 --- a/src/leap/services/eip/providerbootstrapper.py +++ b/src/leap/services/eip/providerbootstrapper.py @@ -32,10 +32,19 @@ from leap.common.check import leap_assert, leap_assert_type from leap.config.providerconfig import ProviderConfig from leap.util.request_helpers import get_content from leap.services.abstractbootstrapper import AbstractBootstrapper +from leap.provider.supportedapis import SupportedAPIs + logger = logging.getLogger(__name__) +class UnsupportedProviderAPI(Exception): + """ + Raised when attempting to use a provider with an incompatible API. + """ + pass + + class ProviderBootstrapper(AbstractBootstrapper): """ Given a provider URL performs a series of checks and emits signals @@ -142,6 +151,18 @@ class ProviderBootstrapper(AbstractBootstrapper): self._domain, "provider.json"]) + api_version = provider_config.get_api_version() + if SupportedAPIs.supports(api_version): + logger.debug("Provider definition has been modified") + else: + api_supported = ', '.join(self._supported_api_versions) + error = ('Unsupported provider API version. ' + 'Supported versions are: {}. ' + 'Found: {}.').format(api_supported, api_version) + + logger.error(error) + raise UnsupportedProviderAPI(error) + def run_provider_select_checks(self, domain, download_if_needed=False): """ Populates the check queue. |