diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/leap/bitmask/backend.py | 84 | ||||
| -rw-r--r-- | src/leap/bitmask/crypto/srpregister.py | 63 | ||||
| -rw-r--r-- | src/leap/bitmask/gui/wizard.py | 106 | 
3 files changed, 190 insertions, 63 deletions
| diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index 99e5a04b..45ea451c 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -19,6 +19,7 @@ Backend for everything  """  import logging +from functools import partial  from Queue import Queue, Empty  from twisted.internet import threads, defer @@ -28,6 +29,7 @@ from twisted.python import log  import zope.interface  from leap.bitmask.config.providerconfig import ProviderConfig +from leap.bitmask.crypto.srpregister import SRPRegister  from leap.bitmask.provider import get_provider_path  from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper @@ -134,6 +136,9 @@ class Provider(object):          :param provider: URL for the provider          :type provider: unicode + +        :returns: the defer for the operation running in a thread. +        :rtype: twisted.internet.defer.Deferred          """          log.msg("Setting up provider %s..." % (provider.encode("idna"),))          pb = self._provider_bootstrapper @@ -155,8 +160,10 @@ class Provider(object):          :param provider: URL for the provider          :type provider: unicode -        """ +        :returns: the defer for the operation running in a thread. +        :rtype: twisted.internet.defer.Deferred +        """          d = None          # If there's no loaded provider or @@ -180,6 +187,57 @@ class Provider(object):          return d +class Register(object): +    """ +    Interfaces with setup and bootstrapping operations for a provider +    """ + +    zope.interface.implements(ILEAPComponent) + +    def __init__(self, signaler=None): +        """ +        Constructor for the Register component + +        :param signaler: Object in charge of handling communication +                         back to the frontend +        :type signaler: Signaler +        """ +        object.__init__(self) +        self.key = "register" +        self._signaler = signaler +        self._provider_config = ProviderConfig() + +    def register_user(self, domain, username, password): +        """ +        Register a user using the domain and password given as parameters. + +        :param domain: the domain we need to register the user. +        :type domain: unicode +        :param username: the user name +        :type username: unicode +        :param password: the password for the username +        :type password: unicode + +        :returns: the defer for the operation running in a thread. +        :rtype: twisted.internet.defer.Deferred +        """ +        # If there's no loaded provider or +        # we want to connect to other provider... +        if (not self._provider_config.loaded() or +                self._provider_config.get_domain() != domain): +            self._provider_config.load(get_provider_path(domain)) + +        if self._provider_config.loaded(): +            srpregister = SRPRegister(signaler=self._signaler, +                                      provider_config=self._provider_config) +            return threads.deferToThread( +                partial(srpregister.register_user, username, password)) +        else: +            if self._signaler is not None: +                self._signaler.signal(self._signaler.srp_registration_failed) +            logger.error("Could not load provider configuration.") + +  class Signaler(QtCore.QObject):      """      Signaler object, handles converting string commands to Qt signals. @@ -188,8 +246,9 @@ class Signaler(QtCore.QObject):      live in the frontend.      """ -    # Signals for the ProviderBootstrapper +    ####################      # These will only exist in the frontend +    # Signals for the ProviderBootstrapper      prov_name_resolution = QtCore.Signal(object)      prov_https_connection = QtCore.Signal(object)      prov_download_provider_info = QtCore.Signal(object) @@ -205,7 +264,13 @@ class Signaler(QtCore.QObject):      prov_cancelled_setup = QtCore.Signal(object) -    # These will exist both in the backend and the front end. +    # Signals for SRPRegister +    srp_registration_finished = QtCore.Signal(object) +    srp_registration_failed = QtCore.Signal(object) +    srp_registration_taken = QtCore.Signal(object) + +    #################### +    # These will exist both in the backend AND the front end.      # The frontend might choose to not "interpret" all the signals      # from the backend, but the backend needs to have all the signals      # it's going to emit defined here @@ -220,6 +285,10 @@ class Signaler(QtCore.QObject):      PROV_UNSUPPORTED_API = "prov_unsupported_api"      PROV_CANCELLED_SETUP = "prov_cancelled_setup" +    SRP_REGISTRATION_FINISHED = "srp_registration_finished" +    SRP_REGISTRATION_FAILED = "srp_registration_failed" +    SRP_REGISTRATION_TAKEN = "srp_registration_taken" +      def __init__(self):          """          Constructor for the Signaler @@ -238,6 +307,10 @@ class Signaler(QtCore.QObject):              self.PROV_UNSUPPORTED_CLIENT,              self.PROV_UNSUPPORTED_API,              self.PROV_CANCELLED_SETUP, + +            self.SRP_REGISTRATION_FINISHED, +            self.SRP_REGISTRATION_FAILED, +            self.SRP_REGISTRATION_TAKEN,          ]          for sig in signals: @@ -296,6 +369,7 @@ class Backend(object):          # Component registration          self._register(Provider(self._signaler, bypass_checks)) +        self._register(Register(self._signaler))          # We have a looping call on a thread executing all the          # commands in queue. Right now this queue is an actual Queue @@ -409,3 +483,7 @@ class Backend(object):      def provider_bootstrap(self, provider):          self._call_queue.put(("provider", "bootstrap", None, provider)) + +    def register_user(self, provider, username, password): +        self._call_queue.put(("register", "register_user", None, provider, +                              username, password)) diff --git a/src/leap/bitmask/crypto/srpregister.py b/src/leap/bitmask/crypto/srpregister.py index 02a1ea63..4c52db42 100644 --- a/src/leap/bitmask/crypto/srpregister.py +++ b/src/leap/bitmask/crypto/srpregister.py @@ -16,6 +16,7 @@  # along with this program.  If not, see <http://www.gnu.org/licenses/>.  import binascii +import json  import logging  import requests @@ -26,6 +27,7 @@ from urlparse import urlparse  from leap.bitmask.config.providerconfig import ProviderConfig  from leap.bitmask.util.constants import SIGNUP_TIMEOUT +from leap.bitmask.util.request_helpers import get_content  from leap.common.check import leap_assert, leap_assert_type  logger = logging.getLogger(__name__) @@ -40,16 +42,22 @@ class SRPRegister(QtCore.QObject):      USER_VERIFIER_KEY = 'user[password_verifier]'      USER_SALT_KEY = 'user[password_salt]' +    STATUS_OK = (200, 201) +    STATUS_TAKEN = 422 +    STATUS_ERROR = -999  # Custom error status +      registration_finished = QtCore.Signal(bool, object) -    def __init__(self, -                 provider_config=None, -                 register_path="users"): +    def __init__(self, signaler=None, +                 provider_config=None, register_path="users"):          """          Constructor +        :param signaler: Signaler object used to receive notifications +                         from the backend +        :type signaler: Signaler          :param provider_config: provider configuration instance, -        properly loaded +                                properly loaded          :type privider_config: ProviderConfig          :param register_path: webapp path for registering users          :type register_path; str @@ -59,6 +67,7 @@ class SRPRegister(QtCore.QObject):          leap_assert_type(provider_config, ProviderConfig)          self._provider_config = provider_config +        self._signaler = signaler          # **************************************************** #          # Dependency injection helpers, override this for more @@ -104,8 +113,8 @@ class SRPRegister(QtCore.QObject):          :param password: password for this username          :type password: str -        :rtype: tuple -        :rparam: (ok, request) +        :returns: if the registration went ok or not. +        :rtype: bool          """          username = username.lower().encode('utf-8') @@ -129,11 +138,7 @@ class SRPRegister(QtCore.QObject):          logger.debug("Will try to register user = %s" % (username,))          ok = False -        # This should be None, but we don't like when PySide segfaults, -        # so it something else. -        # To reproduce it, just do: -        # self.registration_finished.emit(False, None) -        req = [] +        req = None          try:              req = self._session.post(uri,                                       data=user_data, @@ -143,13 +148,45 @@ class SRPRegister(QtCore.QObject):          except requests.exceptions.RequestException as exc:              logger.error(exc.message) -            ok = False          else:              ok = req.ok -        self.registration_finished.emit(ok, req) +        status_code = self.STATUS_ERROR +        if req is not None: +            status_code = req.status_code +        self._emit_result(status_code) + +        if not ok: +            try: +                content, _ = get_content(req) +                json_content = json.loads(content) +                error_msg = json_content.get("errors").get("login")[0] +                if not error_msg.istitle(): +                    error_msg = "%s %s" % (username, error_msg) +                logger.error(error_msg) +            except Exception as e: +                logger.error("Unknown error: %r" % (e, )) +          return ok +    def _emit_result(self, status_code): +        """ +        Emit the corresponding signal depending on the status code. + +        :param status_code: the status code received. +        :type status_code: int or str +        """ +        logger.debug("Status code is: {0}".format(status_code)) +        if self._signaler is None: +            return + +        if status_code in self.STATUS_OK: +            self._signaler.signal(self._signaler.SRP_REGISTRATION_FINISHED) +        elif status_code == self.STATUS_TAKEN: +            self._signaler.signal(self._signaler.SRP_REGISTRATION_TAKEN) +        else: +            self._signaler.signal(self._signaler.SRP_REGISTRATION_FAILED) +  if __name__ == "__main__":      logger = logging.getLogger(name='leap') diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index 024b23bc..2f274e33 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -18,22 +18,18 @@  First run wizard  """  import logging -import json  import random  from functools import partial  from PySide import QtCore, QtGui -from twisted.internet import threads  from leap.bitmask.config.leapsettings import LeapSettings  from leap.bitmask.config.providerconfig import ProviderConfig -from leap.bitmask.crypto.srpregister import SRPRegister  from leap.bitmask.provider import get_provider_path  from leap.bitmask.services import get_service_display_name, get_supported  from leap.bitmask.util.keyring_helpers import has_keyring  from leap.bitmask.util.password import basic_password_checks -from leap.bitmask.util.request_helpers import get_content  from ui_wizard import Ui_Wizard @@ -253,16 +249,11 @@ class Wizard(QtGui.QWizard):          ok, msg = basic_password_checks(username, password, password2)          if ok: -            register = SRPRegister(provider_config=self._provider_config) -            register.registration_finished.connect( -                self._registration_finished) - -            threads.deferToThread( -                partial(register.register_user, username, password)) +            self._set_register_status(self.tr("Starting registration...")) +            self._backend.register_user(self._domain, username, password)              self._username = username              self._password = password -            self._set_register_status(self.tr("Starting registration..."))          else:              self._set_register_status(msg, error=True)              self._focus_password() @@ -289,42 +280,59 @@ class Wizard(QtGui.QWizard):          # register button          self.ui.btnRegister.setVisible(visible) -    def _registration_finished(self, ok, req): -        if ok: -            user_domain = self._username + "@" + self._domain -            message = "<font color='green'><h3>" -            message += self.tr("User %s successfully registered.") % ( -                user_domain, ) -            message += "</h3></font>" -            self._set_register_status(message) - -            self.ui.lblPassword2.clearFocus() -            self._set_registration_fields_visibility(False) - -            # Allow the user to remember his password -            if has_keyring(): -                self.ui.chkRemember.setVisible(True) -                self.ui.chkRemember.setEnabled(True) - -            self.page(self.REGISTER_USER_PAGE).set_completed() -            self.button(QtGui.QWizard.BackButton).setEnabled(False) -        else: -            old_username = self._username -            self._username = None -            self._password = None -            error_msg = self.tr("Something has gone wrong. " -                                "Please try again.") -            try: -                content, _ = get_content(req) -                json_content = json.loads(content) -                error_msg = json_content.get("errors").get("login")[0] -                if not error_msg.istitle(): -                    error_msg = "%s %s" % (old_username, error_msg) -            except Exception as e: -                logger.error("Unknown error: %r" % (e,)) - -            self._set_register_status(error_msg, error=True) -            self.ui.btnRegister.setEnabled(True) +    def _registration_finished(self): +        """ +        SLOT +        TRIGGERS: +          self._backend.signaler.srp_registration_finished + +        The registration has finished successfully, so we do some final steps. +        """ +        user_domain = self._username + "@" + self._domain +        message = "<font color='green'><h3>" +        message += self.tr("User %s successfully registered.") % ( +            user_domain, ) +        message += "</h3></font>" +        self._set_register_status(message) + +        self.ui.lblPassword2.clearFocus() +        self._set_registration_fields_visibility(False) + +        # Allow the user to remember his password +        if has_keyring(): +            self.ui.chkRemember.setVisible(True) +            self.ui.chkRemember.setEnabled(True) + +        self.page(self.REGISTER_USER_PAGE).set_completed() +        self.button(QtGui.QWizard.BackButton).setEnabled(False) + +    def _registration_failed(self): +        """ +        SLOT +        TRIGGERS: +          self._backend.signaler.srp_registration_failed + +        The registration has failed, so we report the problem. +        """ +        self._username = self._password = None + +        error_msg = self.tr("Something has gone wrong. Please try again.") +        self._set_register_status(error_msg, error=True) +        self.ui.btnRegister.setEnabled(True) + +    def _registration_taken(self): +        """ +        SLOT +        TRIGGERS: +          self._backend.signaler.srp_registration_taken + +        The requested username is taken, warn the user about that. +        """ +        self._username = self._password = None + +        error_msg = self.tr("The requested username is taken, choose another.") +        self._set_register_status(error_msg, error=True) +        self.ui.btnRegister.setEnabled(True)      def _set_register_status(self, status, error=False):          """ @@ -688,6 +696,10 @@ class Wizard(QtGui.QWizard):          sig.prov_check_ca_fingerprint.connect(self._check_ca_fingerprint)          sig.prov_check_api_certificate.connect(self._check_api_certificate) +        sig.srp_registration_finished.connect(self._registration_finished) +        sig.srp_registration_failed.connect(self._registration_failed) +        sig.srp_registration_taken.connect(self._registration_taken) +      def _backend_disconnect(self):          """          This method is called when the wizard dialog is closed. | 
