diff options
-rw-r--r-- | changes/feature_move-srpregister-to-backend | 1 | ||||
-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 |
4 files changed, 191 insertions, 63 deletions
diff --git a/changes/feature_move-srpregister-to-backend b/changes/feature_move-srpregister-to-backend new file mode 100644 index 00000000..0625432f --- /dev/null +++ b/changes/feature_move-srpregister-to-backend @@ -0,0 +1 @@ +- Move/refactor srpregister to backend. 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. |