diff options
| -rw-r--r-- | changes/feature_refactor_provider_bootstrapper | 2 | ||||
| -rw-r--r-- | src/leap/bitmask/backend.py | 381 | ||||
| -rw-r--r-- | src/leap/bitmask/gui/mainwindow.py | 139 | ||||
| -rw-r--r-- | src/leap/bitmask/gui/wizard.py | 68 | ||||
| -rw-r--r-- | src/leap/bitmask/provider/providerbootstrapper.py | 38 | ||||
| -rw-r--r-- | src/leap/bitmask/provider/tests/test_providerbootstrapper.py | 3 | ||||
| -rw-r--r-- | src/leap/bitmask/services/abstractbootstrapper.py | 33 | 
7 files changed, 545 insertions, 119 deletions
| diff --git a/changes/feature_refactor_provider_bootstrapper b/changes/feature_refactor_provider_bootstrapper new file mode 100644 index 00000000..969b259f --- /dev/null +++ b/changes/feature_refactor_provider_bootstrapper @@ -0,0 +1,2 @@ +- Refactor ProviderBootstrapper out of the UI modules to a Backend +  module, obscuring all the details.
\ No newline at end of file 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 <http://www.gnu.org/licenses/>. +""" +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("<font color='red'><b>Non-existent "                               "provider</b></font>") @@ -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("<font color='red'><b>%s</b></font>") \ -                % (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("<font color='red'><b>Not a valid provider"                               "</b></font>")              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) | 
