diff options
Diffstat (limited to 'src')
| -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 | 89 | ||||
| -rw-r--r-- | src/leap/gui/ui/wizard.ui | 3 | ||||
| -rw-r--r-- | src/leap/gui/wizard.py | 21 | ||||
| -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 | 
8 files changed, 208 insertions, 67 deletions
| 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 2cad6df3..a7d88aee 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -218,12 +218,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( @@ -235,7 +234,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 = [] @@ -260,6 +259,8 @@ class MainWindow(QtGui.QMainWindow):          self._soledad = None          self._keymanager = None +        self._login_defer = None +          self._smtp_config = SMTPConfig()          if self._first_run(): @@ -378,6 +379,12 @@ class MainWindow(QtGui.QMainWindow):          if self._wizard:              possible_username = self._wizard.get_username()              possible_password = self._wizard.get_password() + +            # select the configured provider in the combo box +            domain = self._wizard.get_domain() +            provider_index = self.ui.cmbProviders.findText(domain) +            self.ui.cmbProviders.setCurrentIndex(provider_index) +              self.ui.chkRemember.setChecked(self._wizard.get_remember())              self._enabled_services = list(self._wizard.get_services())              self._settings.set_enabled_services( @@ -400,8 +407,21 @@ class MainWindow(QtGui.QMainWindow):              saved_user = self._settings.get_user()              auto_login = self._settings.get_autologin() +            try: +                username, domain = saved_user.split('@') +            except (ValueError, AttributeError) as e: +                # if the saved_user does not contain an '@' or its None +                logger.error('Username@provider malformed. %r' % (e, )) +                saved_user = None +              if saved_user is not None: -                self.ui.lnUser.setText(saved_user) +                # fill the username +                self.ui.lnUser.setText(username) + +                # select the configured provider in the combo box +                provider_index = self.ui.cmbProviders.findText(domain) +                self.ui.cmbProviders.setCurrentIndex(provider_index) +                  self.ui.chkRemember.setChecked(True)                  self.ui.chkAutoLogin.setEnabled(self.ui.chkRemember                                                  .isEnabled()) @@ -435,22 +455,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 @@ -594,7 +607,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) @@ -694,13 +707,16 @@ class MainWindow(QtGui.QMainWindow):          self._login_set_enabled(False)          if self.ui.chkRemember.isChecked(): +            # 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.encode("utf8"), +                                     username_domain,                                       password.encode("utf8"))                  # Only save the username if it was saved correctly in                  # the keyring -                self._settings.set_user(username) +                self._settings.set_user(username_domain)              except Exception as e:                  logger.error("Problem saving data to keyring. %r"                               % (e,)) @@ -728,10 +744,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) @@ -751,6 +764,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) @@ -760,7 +774,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, @@ -883,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) @@ -922,11 +947,11 @@ class MainWindow(QtGui.QMainWindow):          self._vpn.terminate()          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) @@ -942,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) @@ -967,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): @@ -1073,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("") @@ -1169,6 +1193,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 405752ef..d03427db 100644 --- a/src/leap/gui/wizard.py +++ b/src/leap/gui/wizard.py @@ -154,6 +154,9 @@ class Wizard(QtGui.QWizard):          self.page(self.FINISH_PAGE).setButtonText(              QtGui.QWizard.FinishButton, self.tr("Connect")) +    def get_domain(self): +        return self._domain +      def get_username(self):          return self._username @@ -275,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() @@ -389,10 +395,11 @@ class Wizard(QtGui.QWizard):          if not passed:              status = self.tr("<font color='red'><b>Non-existent "                               "provider</b></font>") +        else: +            self.ui.lblHTTPS.setPixmap(self.QUESTION_ICON)          self.ui.lblProviderSelectStatus.setText(status)          self.ui.btnCheck.setEnabled(not passed)          self.ui.lnProvider.setEnabled(not passed) -        self.ui.lblHTTPS.setPixmap(self.QUESTION_ICON)      def _https_connection(self, data):          """ @@ -408,9 +415,10 @@ class Wizard(QtGui.QWizard):              status = self.tr("<font color='red'><b>%s</b></font>") \                  % (data[self._provider_bootstrapper.ERROR_KEY])              self.ui.lblProviderSelectStatus.setText(status) +        else: +            self.ui.lblProviderInfo.setPixmap(self.QUESTION_ICON)          self.ui.btnCheck.setEnabled(not passed)          self.ui.lnProvider.setEnabled(not passed) -        self.ui.lblProviderInfo.setPixmap(self.QUESTION_ICON)      def _download_provider_info(self, data):          """ @@ -451,7 +459,9 @@ class Wizard(QtGui.QWizard):          Sets the status for the download of the CA certificate check          """          self._complete_task(data, self.ui.lblDownloadCaCert) -        self.ui.lblCheckCaFpr.setPixmap(self.QUESTION_ICON) +        passed = data[self._provider_bootstrapper.PASSED_KEY] +        if passed: +            self.ui.lblCheckCaFpr.setPixmap(self.QUESTION_ICON)      def _check_ca_fingerprint(self, data):          """ @@ -461,7 +471,9 @@ class Wizard(QtGui.QWizard):          Sets the status for the CA fingerprint check          """          self._complete_task(data, self.ui.lblCheckCaFpr) -        self.ui.lblCheckApiCert.setPixmap(self.QUESTION_ICON) +        passed = data[self._provider_bootstrapper.PASSED_KEY] +        if passed: +            self.ui.lblCheckApiCert.setPixmap(self.QUESTION_ICON)      def _check_api_certificate(self, data):          """ @@ -566,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. | 
