From d9d558d44660777795d3e85611b0cfe848a92a28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Wed, 11 Dec 2013 14:48:49 -0300 Subject: Refactor provider_bootstrapper out of mainwindow --- src/leap/bitmask/backend.py | 381 +++++++++++++++++++++ src/leap/bitmask/gui/mainwindow.py | 139 ++++---- src/leap/bitmask/gui/wizard.py | 68 ++-- src/leap/bitmask/provider/providerbootstrapper.py | 38 +- .../provider/tests/test_providerbootstrapper.py | 3 +- src/leap/bitmask/services/abstractbootstrapper.py | 33 +- 6 files changed, 543 insertions(+), 119 deletions(-) create mode 100644 src/leap/bitmask/backend.py (limited to 'src/leap') diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py new file mode 100644 index 00000000..8a289a79 --- /dev/null +++ b/src/leap/bitmask/backend.py @@ -0,0 +1,381 @@ +# -*- coding: utf-8 -*- +# backend.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 . +""" +Backend for everything +""" +import logging +import os + +from Queue import Queue, Empty + +from twisted.internet import threads, defer +from twisted.internet.task import LoopingCall +from twisted.python import log + +import zope.interface + +from leap.bitmask.config.providerconfig import ProviderConfig +from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper + +# Frontend side +from PySide import QtCore + +logger = logging.getLogger(__name__) + + +class ILEAPComponent(zope.interface.Interface): + """ + Interface that every component for the backend should comply to + """ + + key = zope.interface.Attribute("Key id for this component") + + +class ILEAPService(ILEAPComponent): + """ + Interface that every Service needs to implement + """ + + def start(self): + """ + Starts the service. + """ + pass + + def stop(self): + """ + Stops the service. + """ + pass + + def terminate(self): + """ + Terminates the service, not necessarily in a nice way. + """ + pass + + def status(self): + """ + Returns a json object with the current status for the service. + + :rtype: object (list, str, dict) + """ + # XXX: Use a namedtuple or a specific object instead of a json + # object, since parsing it will be problematic otherwise. + # It has to be something easily serializable though. + pass + + def set_configs(self, keyval): + """ + Sets the config parameters for this Service. + + :param keyval: values to configure + :type keyval: dict, {str: str} + """ + pass + + def get_configs(self, keys): + """ + Returns the configuration values for the list of keys. + + :param keys: keys to retrieve + :type keys: list of str + + :rtype: dict, {str: str} + """ + pass + + +class Provider(object): + """ + Interfaces with setup and bootstrapping operations for a provider + """ + + zope.interface.implements(ILEAPComponent) + + PROBLEM_SIGNAL = "prov_problem_with_provider" + + def __init__(self, signaler=None, bypass_checks=False): + """ + Constructor for the Provider component + + :param signaler: Object in charge of handling communication + back to the frontend + :type signaler: Signaler + :param bypass_checks: Set to true if the app should bypass + first round of checks for CA + certificates at bootstrap + :type bypass_checks: bool + """ + object.__init__(self) + self.key = "provider" + self._provider_bootstrapper = ProviderBootstrapper(signaler, + bypass_checks) + self._download_provider_defer = None + self._provider_config = ProviderConfig() + + def setup_provider(self, provider): + """ + Initiates the setup for a provider + + :param provider: URL for the provider + :type provider: unicode + """ + log.msg("Setting up provider %s..." % (provider.encode("idna"),)) + pb = self._provider_bootstrapper + d = pb.run_provider_select_checks(provider, download_if_needed=True) + self._download_provider_defer = d + return d + + def bootstrap(self, provider): + """ + Second stage of bootstrapping for a provider. + + :param provider: URL for the provider + :type provider: unicode + """ + + d = None + + # 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() != provider): + self._provider_config.load( + os.path.join("leap", "providers", + provider, "provider.json")) + + if self._provider_config.loaded(): + d = self._provider_bootstrapper.run_provider_setup_checks( + self._provider_config, + download_if_needed=True) + else: + if self._signaler is not None: + self._signaler.signal(self.PROBLEM_SIGNAL) + logger.error("Could not load provider configuration.") + self._login_widget.set_enabled(True) + + if d is None: + d = defer.Deferred() + return d + + +class Signaler(QtCore.QObject): + """ + Signaler object, handles converting string commands to Qt signals. + + This is intended for the separation in frontend/backend, this will + live in the frontend. + """ + + # Signals for the ProviderBootstrapper + # These will only exist in the frontend + prov_name_resolution = QtCore.Signal(object) + prov_https_connection = QtCore.Signal(object) + prov_download_provider_info = QtCore.Signal(object) + + prov_download_ca_cert = QtCore.Signal(object) + prov_check_ca_fingerprint = QtCore.Signal(object) + prov_check_api_certificate = QtCore.Signal(object) + + prov_problem_with_provider = 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 + PROV_NAME_RESOLUTION_KEY = "prov_name_resolution" + PROV_HTTPS_CONNECTION_KEY = "prov_https_connection" + PROV_DOWNLOAD_PROVIDER_INFO_KEY = "prov_download_provider_info" + PROV_DOWNLOAD_CA_CERT_KEY = "prov_download_ca_cert" + PROV_CHECK_CA_FINGERPRINT_KEY = "prov_check_ca_fingerprint" + PROV_CHECK_API_CERTIFICATE_KEY = "prov_check_api_certificate" + PROV_PROV_PROBLEM_WITH_PROVIER_KEY = "prov_problem_with_provider" + + def __init__(self): + """ + Constructor for the Signaler + """ + QtCore.QObject.__init__(self) + self._signals = {} + + signals = [ + self.PROV_NAME_RESOLUTION_KEY, + self.PROV_HTTPS_CONNECTION_KEY, + self.PROV_DOWNLOAD_PROVIDER_INFO_KEY, + self.PROV_DOWNLOAD_CA_CERT_KEY, + self.PROV_CHECK_CA_FINGERPRINT_KEY, + self.PROV_CHECK_API_CERTIFICATE_KEY, + self.PROV_PROV_PROBLEM_WITH_PROVIER_KEY + ] + + for sig in signals: + self._signals[sig] = getattr(self, sig) + + def signal(self, key, data=None): + """ + Emits a Qt signal based on the key provided, with the data if provided. + + :param key: string identifying the signal to emit + :type key: str + :param data: object to send with the data + :type data: object + + NOTE: The data object will be a serialized str in the backend, + and an unserialized object in the frontend, but for now we + just care about objects. + """ + # Right now it emits Qt signals. The backend version of this + # will do zmq.send_multipart, and the frontend version will be + # similar to this + log.msg("Signaling %s :: %s" % (key, data)) + try: + self._signals[key].emit(data) + except KeyError: + log.msg("Unknown key for signal %s!" % (key,)) + + +class Backend(object): + """ + Backend for everything, the UI should only use this class. + """ + + PASSED_KEY = "passed" + ERROR_KEY = "error" + + def __init__(self, bypass_checks=False): + """ + Constructor for the backend. + """ + object.__init__(self) + + # Components map for the commands received + self._components = {} + + # Ongoing defers that will be cancelled at stop time + self._ongoing_defers = [] + + # Signaler object to translate commands into Qt signals + self._signaler = Signaler() + + # Component registration + self._register(Provider(self._signaler, bypass_checks)) + + # We have a looping call on a thread executing all the + # commands in queue. Right now this queue is an actual Queue + # object, but it'll become the zmq recv_multipart queue + self._lc = LoopingCall(threads.deferToThread, self._worker) + + # Temporal call_queue for worker, will be replaced with + # recv_multipart os something equivalent in the loopingcall + self._call_queue = Queue() + + @property + def signaler(self): + """ + Public signaler access to let the UI connect to its signals. + """ + return self._signaler + + def start(self): + """ + Starts the looping call + """ + log.msg("Starting worker...") + self._lc.start(0.01) + + def stop(self): + """ + Stops the looping call and tries to cancel all the defers. + """ + log.msg("Stopping worker...") + self._lc.stop() + while len(self._ongoing_defers) > 0: + d = self._ongoing_defers.pop() + d.cancel() + + def _register(self, component): + """ + Registers a component in this backend + + :param component: Component to register + :type component: any object that implements ILEAPComponent + """ + # TODO: assert that the component implements the interfaces + # expected + try: + self._components[component.key] = component + except Exception: + log.msg("There was a problem registering %s" % (component,)) + log.err() + + def _signal_back(self, _, signal): + """ + Helper method to signal back (callback like behavior) to the + UI that an operation finished. + + :param signal: signal name + :type signal: str + """ + self._signaler.signal(signal) + + def _worker(self): + """ + Worker method, called from a different thread and as a part of + a looping call + """ + try: + # this'll become recv_multipart + cmd = self._call_queue.get(block=False) + + # cmd is: component, method, signalback, *args + func = getattr(self._components[cmd[0]], cmd[1]) + d = func(*cmd[3:]) + # A call might not have a callback signal, but if it does, + # we add it to the chain + if cmd[2] is not None: + d.addCallbacks(self._signal_back, log.err, cmd[2]) + d.addCallbacks(self._done_action, log.err, + callbackKeywords={"d": d}) + d.addErrback(log.err) + self._ongoing_defers.append(d) + except Empty: + # If it's just empty we don't have anything to do. + pass + except Exception: + # But we log the rest + log.err() + + def _done_action(self, _, d): + """ + Remover of the defer once it's done + + :param d: defer to remove + :type d: twisted.internet.defer.Deferred + """ + self._ongoing_defers.remove(d) + + # XXX: Temporal interface until we migrate to zmq + # We simulate the calls to zmq.send_multipart. Once we separate + # this in two processes, the methods bellow can be changed to + # send_multipart and this backend class will be really simple. + + def setup_provider(self, provider): + self._call_queue.put(("provider", "setup_provider", None, provider)) + + def provider_bootstrap(self, provider): + self._call_queue.put(("provider", "bootstrap", None, provider)) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 75a16eb9..7dcb9908 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -18,9 +18,9 @@ Main window for Bitmask. """ import logging -import os from PySide import QtCore, QtGui +from functools import partial from twisted.internet import threads from zope.proxy import ProxyBase, setProxiedObject @@ -42,9 +42,10 @@ from leap.bitmask.gui.systray import SysTray from leap.bitmask import provider from leap.bitmask.platform_init import IS_WIN, IS_MAC from leap.bitmask.platform_init.initializers import init_platform -from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper -from leap.bitmask.services import get_service_display_name, EIP_SERVICE +from leap.bitmask import backend + +from leap.bitmask.services import get_service_display_name from leap.bitmask.services.mail import conductor as mail_conductor @@ -138,6 +139,9 @@ class MainWindow(QtGui.QMainWindow): self.ui = Ui_MainWindow() self.ui.setupUi(self) + self._backend = backend.Backend(bypass_checks) + self._backend.start() + self._settings = LeapSettings() self._login_widget = LoginWidget( @@ -180,7 +184,10 @@ class MainWindow(QtGui.QMainWindow): # This is loaded only once, there's a bug when doing that more # than once - self._provider_config = ProviderConfig() + # XXX HACK!! But we need it as long as we are using + # provider_config in here + self._provider_config = ( + self._backend._components["provider"]._provider_config) # Used for automatic start of EIP self._provisional_provider_config = ProviderConfig() self._eip_config = eipconfig.EIPConfig() @@ -191,25 +198,7 @@ class MainWindow(QtGui.QMainWindow): self._srp_auth = None self._logged_user = None - # This thread is always running, although it's quite - # lightweight when it's done setting up provider - # configuration and certificate. - self._provider_bootstrapper = ProviderBootstrapper(bypass_checks) - - # Intermediate stages, only do something if there was an error - self._provider_bootstrapper.name_resolution.connect( - self._intermediate_stage) - self._provider_bootstrapper.https_connection.connect( - self._intermediate_stage) - self._provider_bootstrapper.download_ca_cert.connect( - self._intermediate_stage) - - # Important stages, loads the provider config and checks - # certificates - self._provider_bootstrapper.download_provider_info.connect( - self._load_provider_config) - self._provider_bootstrapper.check_api_certificate.connect( - self._provider_config_loaded) + self._backend_connect() # This thread is similar to the provider bootstrapper self._eip_bootstrapper = EIPBootstrapper() @@ -349,7 +338,9 @@ class MainWindow(QtGui.QMainWindow): if self._first_run(): self._wizard_firstrun = True - self._wizard = Wizard(bypass_checks=bypass_checks) + self._backend_disconnect() + self._wizard = Wizard(backend=self._backend, + bypass_checks=bypass_checks) # Give this window time to finish init and then show the wizard QtCore.QTimer.singleShot(1, self._launch_wizard) self._wizard.accepted.connect(self._finish_init) @@ -359,6 +350,47 @@ class MainWindow(QtGui.QMainWindow): # so this has to be done after eip_machine is started self._finish_init() + def _backend_connect(self): + """ + Helper to connect to backend signals + """ + self._backend.signaler.prov_name_resolution.connect( + self._intermediate_stage) + self._backend.signaler.prov_https_connection.connect( + self._intermediate_stage) + self._backend.signaler.prov_download_ca_cert.connect( + self._intermediate_stage) + + self._backend.signaler.prov_download_provider_info.connect( + self._load_provider_config) + self._backend.signaler.prov_check_api_certificate.connect( + self._provider_config_loaded) + + # Only used at login, no need to disconnect this like we do + # with the other + self._backend.signaler.prov_problem_with_provider.connect( + partial(self._login_widget.set_status, + self.tr("Unable to login: Problem with provider"))) + + def _backend_disconnect(self): + """ + Helper to disconnect from backend signals. + + Some signals are emitted from the wizard, and we want to + ignore those. + """ + self._backend.signaler.prov_name_resolution.disconnect( + self._intermediate_stage) + self._backend.signaler.prov_https_connection.disconnect( + self._intermediate_stage) + self._backend.signaler.prov_download_ca_cert.disconnect( + self._intermediate_stage) + + self._backend.signaler.prov_download_provider_info.disconnect( + self._load_provider_config) + self._backend.signaler.prov_check_api_certificate.disconnect( + self._provider_config_loaded) + def _rejected_wizard(self): """ SLOT @@ -379,6 +411,7 @@ class MainWindow(QtGui.QMainWindow): # This happens if the user finishes the provider # setup but does not register self._wizard = None + self._backend_connect() self._finish_init() def _launch_wizard(self): @@ -394,7 +427,9 @@ class MainWindow(QtGui.QMainWindow): there. """ if self._wizard is None: - self._wizard = Wizard(bypass_checks=self._bypass_checks) + self._backend_disconnect() + self._wizard = Wizard(backend=self._backend, + bypass_checks=self._bypass_checks) self._wizard.accepted.connect(self._finish_init) self._wizard.rejected.connect(self._wizard.close) @@ -628,6 +663,7 @@ class MainWindow(QtGui.QMainWindow): self.eip_needs_login.emit() self._wizard = None + self._backend_connect() else: self._try_autostart_eip() @@ -856,14 +892,12 @@ class MainWindow(QtGui.QMainWindow): # XXX should rename this provider, name clash. provider = self._login_widget.get_selected_provider() - pb = self._provider_bootstrapper - d = pb.run_provider_select_checks(provider, download_if_needed=True) - self._download_provider_defer = d + self._backend.setup_provider(provider) def _load_provider_config(self, data): """ SLOT - TRIGGER: self._provider_bootstrapper.download_provider_info + TRIGGER: self._backend.signaler.prov_download_provider_info Once the provider config has been downloaded, this loads the self._provider_config instance with it and starts the second @@ -873,31 +907,13 @@ class MainWindow(QtGui.QMainWindow): run_provider_select_checks :type data: dict """ - if data[self._provider_bootstrapper.PASSED_KEY]: - # XXX should rename this provider, name clash. - provider = self._login_widget.get_selected_provider() - - # 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() != provider): - self._provider_config.load( - os.path.join("leap", "providers", - provider, "provider.json")) - - if self._provider_config.loaded(): - self._provider_bootstrapper.run_provider_setup_checks( - self._provider_config, - download_if_needed=True) - else: - self._login_widget.set_status( - self.tr("Unable to login: Problem with provider")) - logger.error("Could not load provider configuration.") - self._login_widget.set_enabled(True) + if data[self._backend.PASSED_KEY]: + selected_provider = self._login_widget.get_selected_provider() + self._backend.provider_bootstrap(selected_provider) else: self._login_widget.set_status( self.tr("Unable to login: Problem with provider")) - logger.error(data[self._provider_bootstrapper.ERROR_KEY]) + logger.error(data[self._backend.ERROR_KEY]) self._login_widget.set_enabled(True) def _login(self): @@ -939,14 +955,14 @@ class MainWindow(QtGui.QMainWindow): def _provider_config_loaded(self, data): """ SLOT - TRIGGER: self._provider_bootstrapper.check_api_certificate + TRIGGER: self._backend.signaler.prov_check_api_certificate Once the provider configuration is loaded, this starts the SRP authentication """ leap_assert(self._provider_config, "We need a provider config!") - if data[self._provider_bootstrapper.PASSED_KEY]: + if data[self._backend.PASSED_KEY]: username = self._login_widget.get_user() password = self._login_widget.get_password() @@ -964,7 +980,7 @@ class MainWindow(QtGui.QMainWindow): else: self._login_widget.set_status( "Unable to login: Problem with provider") - logger.error(data[self._provider_bootstrapper.ERROR_KEY]) + logger.error(data[self._backend.ERROR_KEY]) self._login_widget.set_enabled(True) def _authentication_finished(self, ok, message): @@ -1538,11 +1554,11 @@ class MainWindow(QtGui.QMainWindow): This is used for intermediate bootstrapping stages, in case they fail. """ - passed = data[self._provider_bootstrapper.PASSED_KEY] + passed = data[self._backend.PASSED_KEY] if not passed: self._login_widget.set_status( self.tr("Unable to connect: Problem with provider")) - logger.error(data[self._provider_bootstrapper.ERROR_KEY]) + logger.error(data[self._backend.ERROR_KEY]) self._already_started_eip = False # end of EIP methods --------------------------------------------- @@ -1615,21 +1631,21 @@ class MainWindow(QtGui.QMainWindow): """ SLOT TRIGGERS: - self._provider_bootstrapper.name_resolution - self._provider_bootstrapper.https_connection - self._provider_bootstrapper.download_ca_cert + self._backend.signaler.prov_name_resolution + self._backend.signaler.prov_https_connection + self._backend.signaler.prov_download_ca_cert self._eip_bootstrapper.download_config If there was a problem, displays it, otherwise it does nothing. This is used for intermediate bootstrapping stages, in case they fail. """ - passed = data[self._provider_bootstrapper.PASSED_KEY] + passed = data[self._backend.PASSED_KEY] if not passed: self._login_widget.set_enabled(True) self._login_widget.set_status( self.tr("Unable to connect: Problem with provider")) - logger.error(data[self._provider_bootstrapper.ERROR_KEY]) + logger.error(data[self._backend.ERROR_KEY]) # # window handling methods @@ -1719,6 +1735,7 @@ class MainWindow(QtGui.QMainWindow): # Set this in case that the app is hidden QtGui.QApplication.setQuitOnLastWindowClosed(True) + self._backend.stop() self._cleanup_and_quit() self._really_quit = True diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index ad0565e4..ec007110 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -30,7 +30,6 @@ 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.providerbootstrapper import ProviderBootstrapper from leap.bitmask.services import get_service_display_name, get_supported from leap.bitmask.util.request_helpers import get_content from leap.bitmask.util.keyring_helpers import has_keyring @@ -55,12 +54,15 @@ class Wizard(QtGui.QWizard): BARE_USERNAME_REGEX = r"^[A-Za-z\d_]+$" - def __init__(self, bypass_checks=False): + def __init__(self, backend, bypass_checks=False): """ Constructor for the main Wizard. + :param backend: Backend being used + :type backend: Backend :param bypass_checks: Set to true if the app should bypass - first round of checks for CA certificates at bootstrap + first round of checks for CA + certificates at bootstrap :type bypass_checks: bool """ QtGui.QWizard.__init__(self) @@ -68,6 +70,8 @@ class Wizard(QtGui.QWizard): self.ui = Ui_Wizard() self.ui.setupUi(self) + self._backend = backend + self.setPixmap(QtGui.QWizard.LogoPixmap, QtGui.QPixmap(":/images/mask-icon.png")) @@ -86,23 +90,25 @@ class Wizard(QtGui.QWizard): self.ui.btnCheck.clicked.connect(self._check_provider) self.ui.lnProvider.returnPressed.connect(self._check_provider) - self._provider_bootstrapper = ProviderBootstrapper(bypass_checks) - self._provider_bootstrapper.name_resolution.connect( + self._backend.signaler.prov_name_resolution.connect( self._name_resolution) - self._provider_bootstrapper.https_connection.connect( + self._backend.signaler.prov_https_connection.connect( self._https_connection) - self._provider_bootstrapper.download_provider_info.connect( + self._backend.signaler.prov_download_provider_info.connect( self._download_provider_info) - self._provider_bootstrapper.download_ca_cert.connect( + self._backend.signaler.prov_download_ca_cert.connect( self._download_ca_cert) - self._provider_bootstrapper.check_ca_fingerprint.connect( + self._backend.signaler.prov_check_ca_fingerprint.connect( self._check_ca_fingerprint) - self._provider_bootstrapper.check_api_certificate.connect( + self._backend.signaler.prov_check_api_certificate.connect( self._check_api_certificate) self._domain = None - self._provider_config = ProviderConfig() + # HACK!! We need provider_config for the time being, it'll be + # removed + self._provider_config = ( + self._backend._components["provider"]._provider_config) # We will store a reference to the defers for eventual use # (eg, to cancel them) but not doing anything with them right now. @@ -385,8 +391,8 @@ class Wizard(QtGui.QWizard): self._domain = self.ui.lnProvider.text() self.ui.lblNameResolution.setPixmap(self.QUESTION_ICON) - self._provider_select_defer = self._provider_bootstrapper.\ - run_provider_select_checks(self._domain) + self._provider_select_defer = self._backend.\ + setup_provider(self._domain) def _skip_provider_checks(self, skip): """ @@ -423,8 +429,8 @@ class Wizard(QtGui.QWizard): :param complete_page: page id to complete :type complete_page: int """ - passed = data[self._provider_bootstrapper.PASSED_KEY] - error = data[self._provider_bootstrapper.ERROR_KEY] + passed = data[self._backend.PASSED_KEY] + error = data[self._backend.ERROR_KEY] if passed: label.setPixmap(self.OK_ICON) if complete: @@ -437,13 +443,13 @@ class Wizard(QtGui.QWizard): def _name_resolution(self, data): """ SLOT - TRIGGER: self._provider_bootstrapper.name_resolution + TRIGGER: self._backend.signaler.prov_name_resolution Sets the status for the name resolution check """ self._complete_task(data, self.ui.lblNameResolution) status = "" - passed = data[self._provider_bootstrapper.PASSED_KEY] + passed = data[self._backend.PASSED_KEY] if not passed: status = self.tr("Non-existent " "provider") @@ -456,16 +462,16 @@ class Wizard(QtGui.QWizard): def _https_connection(self, data): """ SLOT - TRIGGER: self._provider_bootstrapper.https_connection + TRIGGER: self._backend.signaler.prov_https_connection Sets the status for the https connection check """ self._complete_task(data, self.ui.lblHTTPS) status = "" - passed = data[self._provider_bootstrapper.PASSED_KEY] + passed = data[self._backend.PASSED_KEY] if not passed: status = self.tr("%s") \ - % (data[self._provider_bootstrapper.ERROR_KEY]) + % (data[self._backend.ERROR_KEY]) self.ui.lblProviderSelectStatus.setText(status) else: self.ui.lblProviderInfo.setPixmap(self.QUESTION_ICON) @@ -475,7 +481,7 @@ class Wizard(QtGui.QWizard): def _download_provider_info(self, data): """ SLOT - TRIGGER: self._provider_bootstrapper.download_provider_info + TRIGGER: self._backend.signaler.prov_download_provider_info Sets the status for the provider information download check. Since this check is the last of this set, it also @@ -490,14 +496,14 @@ class Wizard(QtGui.QWizard): self._provider_checks_ok = True else: new_data = { - self._provider_bootstrapper.PASSED_KEY: False, - self._provider_bootstrapper.ERROR_KEY: + self._backend.PASSED_KEY: False, + self._backend.ERROR_KEY: self.tr("Unable to load provider configuration") } self._complete_task(new_data, self.ui.lblProviderInfo) status = "" - if not data[self._provider_bootstrapper.PASSED_KEY]: + if not data[self._backend.PASSED_KEY]: status = self.tr("Not a valid provider" "") self.ui.lblProviderSelectStatus.setText(status) @@ -507,31 +513,31 @@ class Wizard(QtGui.QWizard): def _download_ca_cert(self, data): """ SLOT - TRIGGER: self._provider_bootstrapper.download_ca_cert + TRIGGER: self._backend.signaler.prov_download_ca_cert Sets the status for the download of the CA certificate check """ self._complete_task(data, self.ui.lblDownloadCaCert) - passed = data[self._provider_bootstrapper.PASSED_KEY] + passed = data[self._backend.PASSED_KEY] if passed: self.ui.lblCheckCaFpr.setPixmap(self.QUESTION_ICON) def _check_ca_fingerprint(self, data): """ SLOT - TRIGGER: self._provider_bootstrapper.check_ca_fingerprint + TRIGGER: self._backend.signaler.prov_check_ca_fingerprint Sets the status for the CA fingerprint check """ self._complete_task(data, self.ui.lblCheckCaFpr) - passed = data[self._provider_bootstrapper.PASSED_KEY] + passed = data[self._backend.PASSED_KEY] if passed: self.ui.lblCheckApiCert.setPixmap(self.QUESTION_ICON) def _check_api_certificate(self, data): """ SLOT - TRIGGER: self._provider_bootstrapper.check_api_certificate + TRIGGER: self._backend.signaler.prov_check_api_certificate Sets the status for the API certificate check. Also finishes the provider bootstrapper thread since it's not needed anymore @@ -612,8 +618,8 @@ class Wizard(QtGui.QWizard): sub_title = sub_title.format(self._provider_config.get_name()) self.page(pageId).setSubTitle(sub_title) self.ui.lblDownloadCaCert.setPixmap(self.QUESTION_ICON) - self._provider_setup_defer = self._provider_bootstrapper.\ - run_provider_setup_checks(self._provider_config) + self._provider_setup_defer = self._backend.\ + provider_bootstrap(self._domain) if pageId == self.PRESENT_PROVIDER_PAGE: self.page(pageId).setSubTitle(self.tr("Description of services " diff --git a/src/leap/bitmask/provider/providerbootstrapper.py b/src/leap/bitmask/provider/providerbootstrapper.py index f5a2003f..947ba0c9 100644 --- a/src/leap/bitmask/provider/providerbootstrapper.py +++ b/src/leap/bitmask/provider/providerbootstrapper.py @@ -24,8 +24,6 @@ import sys import requests -from PySide import QtCore - from leap.bitmask.config.providerconfig import ProviderConfig, MissingCACert from leap.bitmask.util.request_helpers import get_content from leap.bitmask import util @@ -61,25 +59,19 @@ class ProviderBootstrapper(AbstractBootstrapper): If a check fails, the subsequent checks are not executed """ - # All dicts returned are of the form - # {"passed": bool, "error": str} - name_resolution = QtCore.Signal(dict) - https_connection = QtCore.Signal(dict) - download_provider_info = QtCore.Signal(dict) - - download_ca_cert = QtCore.Signal(dict) - check_ca_fingerprint = QtCore.Signal(dict) - check_api_certificate = QtCore.Signal(dict) - - def __init__(self, bypass_checks=False): + def __init__(self, signaler=None, bypass_checks=False): """ Constructor for provider bootstrapper object + :param signaler: Signaler object used to receive notifications + from the backend + :type signaler: Signaler :param bypass_checks: Set to true if the app should bypass - first round of checks for CA certificates at bootstrap + first round of checks for CA + certificates at bootstrap :type bypass_checks: bool """ - AbstractBootstrapper.__init__(self, bypass_checks) + AbstractBootstrapper.__init__(self, signaler, bypass_checks) self._domain = None self._provider_config = None @@ -238,9 +230,11 @@ class ProviderBootstrapper(AbstractBootstrapper): self._download_if_needed = download_if_needed cb_chain = [ - (self._check_name_resolution, self.name_resolution), - (self._check_https, self.https_connection), - (self._download_provider_info, self.download_provider_info) + (self._check_name_resolution, + self._signaler.PROV_NAME_RESOLUTION_KEY), + (self._check_https, self._signaler.PROV_HTTPS_CONNECTION_KEY), + (self._download_provider_info, + self._signaler.PROV_DOWNLOAD_PROVIDER_INFO_KEY) ] return self.addCallbackChain(cb_chain) @@ -367,9 +361,11 @@ class ProviderBootstrapper(AbstractBootstrapper): self._download_if_needed = download_if_needed cb_chain = [ - (self._download_ca_cert, self.download_ca_cert), - (self._check_ca_fingerprint, self.check_ca_fingerprint), - (self._check_api_certificate, self.check_api_certificate) + (self._download_ca_cert, self._signaler.PROV_DOWNLOAD_CA_CERT_KEY), + (self._check_ca_fingerprint, + self._signaler.PROV_CHECK_CA_FINGERPRINT_KEY), + (self._check_api_certificate, + self._signaler.PROV_CHECK_API_CERTIFICATE_KEY) ] return self.addCallbackChain(cb_chain) diff --git a/src/leap/bitmask/provider/tests/test_providerbootstrapper.py b/src/leap/bitmask/provider/tests/test_providerbootstrapper.py index 88a4ff0b..d8336fec 100644 --- a/src/leap/bitmask/provider/tests/test_providerbootstrapper.py +++ b/src/leap/bitmask/provider/tests/test_providerbootstrapper.py @@ -42,6 +42,7 @@ from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper from leap.bitmask.provider.providerbootstrapper import UnsupportedProviderAPI from leap.bitmask.provider.providerbootstrapper import WrongFingerprint from leap.bitmask.provider.supportedapis import SupportedAPIs +from leap.bitmask.backend import Signaler from leap.bitmask import util from leap.common.files import mkdir_p from leap.common.testing.https_server import where @@ -50,7 +51,7 @@ from leap.common.testing.basetest import BaseLeapTest class ProviderBootstrapperTest(BaseLeapTest): def setUp(self): - self.pb = ProviderBootstrapper() + self.pb = ProviderBootstrapper(Signaler()) def tearDown(self): pass diff --git a/src/leap/bitmask/services/abstractbootstrapper.py b/src/leap/bitmask/services/abstractbootstrapper.py index 6d4d319b..3bee8e01 100644 --- a/src/leap/bitmask/services/abstractbootstrapper.py +++ b/src/leap/bitmask/services/abstractbootstrapper.py @@ -25,6 +25,8 @@ import requests from functools import partial from PySide import QtCore + +from twisted.python import log from twisted.internet import threads from leap.common.check import leap_assert, leap_assert_type @@ -40,10 +42,13 @@ class AbstractBootstrapper(QtCore.QObject): PASSED_KEY = "passed" ERROR_KEY = "error" - def __init__(self, bypass_checks=False): + def __init__(self, signaler=None, bypass_checks=False): """ Constructor for the abstract bootstrapper + :param signaler: Signaler object used to receive notifications + from the backend + :type signaler: Signaler :param bypass_checks: Set to true if the app should bypass first round of checks for CA certificates at bootstrap @@ -71,6 +76,7 @@ class AbstractBootstrapper(QtCore.QObject): self._bypass_checks = bypass_checks self._signal_to_emit = None self._err_msg = None + self._signaler = signaler def _gui_errback(self, failure): """ @@ -89,10 +95,20 @@ class AbstractBootstrapper(QtCore.QObject): err_msg = self._err_msg \ if self._err_msg is not None \ else str(failure.value) - self._signal_to_emit.emit({ + data = { self.PASSED_KEY: False, self.ERROR_KEY: err_msg - }) + } + # TODO: Remove this check when all the bootstrappers are + # in the backend form + if isinstance(self._signal_to_emit, basestring): + if self._signaler is not None: + self._signaler.signal(self._signal_to_emit, data) + else: + logger.warning("Tried to notify but no signaler found") + else: + self._signal_to_emit.emit(data) + log.err(failure) failure.trap(Exception) def _errback(self, failure, signal=None): @@ -127,8 +143,15 @@ class AbstractBootstrapper(QtCore.QObject): :param signal: Signal to emit if it fails here first :type signal: QtCore.SignalInstance """ - if signal: - signal.emit({self.PASSED_KEY: True, self.ERROR_KEY: ""}) + if signal is not None: + data = {self.PASSED_KEY: True, self.ERROR_KEY: ""} + if isinstance(signal, basestring): + if self._signaler is not None: + self._signaler.signal(signal, data) + else: + logger.warning("Tried to notify but no signaler found") + else: + signal.emit(data) def _callback_threader(self, cb, res, *args, **kwargs): return threads.deferToThread(cb, res, *args, **kwargs) -- cgit v1.2.3