diff options
| -rw-r--r-- | src/leap/bitmask/backend.py | 166 | ||||
| -rw-r--r-- | src/leap/bitmask/gui/mail_status.py | 2 | ||||
| -rw-r--r-- | src/leap/bitmask/gui/mainwindow.py | 122 | ||||
| -rw-r--r-- | src/leap/bitmask/services/mail/conductor.py | 6 | ||||
| -rw-r--r-- | src/leap/bitmask/services/soledad/soledadbootstrapper.py | 101 | 
5 files changed, 253 insertions, 144 deletions
| diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index 65f12685..327131b3 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -31,6 +31,7 @@ from twisted.internet.task import LoopingCall  from twisted.python import log  import zope.interface +import zope.proxy  from leap.bitmask.config.providerconfig import ProviderConfig  from leap.bitmask.crypto.srpauth import SRPAuth @@ -45,6 +46,9 @@ from leap.bitmask.services.eip.eipbootstrapper import EIPBootstrapper  from leap.bitmask.services.eip import vpnlauncher, vpnprocess  from leap.bitmask.services.eip import linuxvpnlauncher, darwinvpnlauncher +from leap.bitmask.services.soledad.soledadbootstrapper import \ +    SoledadBootstrapper +  from leap.common import certs as leap_certs  # Frontend side @@ -545,6 +549,89 @@ class EIP(object):                  self._signaler.signal(self._signaler.EIP_CANNOT_START) +class Soledad(object): +    """ +    Interfaces with setup of Soledad. +    """ +    zope.interface.implements(ILEAPComponent) + +    def __init__(self, signaler=None): +        """ +        Constructor for the Soledad component. + +        :param signaler: Object in charge of handling communication +                         back to the frontend +        :type signaler: Signaler +        """ +        self.key = "soledad" +        self._signaler = signaler +        self._soledad_bootstrapper = SoledadBootstrapper(signaler) +        self._soledad_defer = None + +    def bootstrap(self, username, domain, password): +        """ +        Bootstrap Soledad with the user credentials. + +        Signals: +            soledad_download_config +            soledad_gen_key + +        :param user: user's login +        :type user: unicode +        :param domain: the domain that we are using. +        :type domain: unicode +        :param password: user's password +        :type password: unicode +        """ +        provider_config = ProviderConfig.get_provider_config(domain) +        if provider_config is not None: +            self._soledad_defer = threads.deferToThread( +                self._soledad_bootstrapper.run_soledad_setup_checks, +                provider_config, username, password, +                download_if_needed=True) +        else: +            if self._signaler is not None: +                self._signaler.signal(self._signaler.SOLEDAD_BOOTSTRAP_FAILED) +            logger.error("Could not load provider configuration.") + +        return self._soledad_defer + +    def load_offline(self, username, password, uuid): +        """ +        Load the soledad database in offline mode. + +        :param username: full user id (user@provider) +        :type username: str or unicode +        :param password: the soledad passphrase +        :type password: unicode +        :param uuid: the user uuid +        :type uuid: str or unicode + +        Signals: +            Signaler.soledad_offline_finished +            Signaler.soledad_offline_failed +        """ +        self._soledad_bootstrapper.load_offline_soledad( +            username, password, uuid) + +    def cancel_bootstrap(self): +        """ +        Cancel the ongoing soledad bootstrap (if any). +        """ +        if self._soledad_defer is not None: +            logger.debug("Cancelling soledad defer.") +            self._soledad_defer.cancel() +            self._soledad_defer = None + +    def close(self): +        """ +        Close soledad database. +        """ +        soledad = self._soledad_bootstrapper.soledad +        if soledad is not None: +            soledad.close() + +  class Authenticate(object):      """      Interfaces with setup and bootstrapping operations for a provider @@ -738,6 +825,14 @@ class Signaler(QtCore.QObject):      eip_can_start = QtCore.Signal(object)      eip_cannot_start = QtCore.Signal(object) +    # Signals for Soledad +    soledad_bootstrap_failed = QtCore.Signal(object) +    soledad_bootstrap_finished = QtCore.Signal(object) +    soledad_offline_failed = QtCore.Signal(object) +    soledad_offline_finished = QtCore.Signal(object) +    soledad_invalid_auth_token = QtCore.Signal(object) +    soledad_cancelled_bootstrap = QtCore.Signal(object) +      # This signal is used to warn the backend user that is doing something      # wrong      backend_bad_call = QtCore.Signal(object) @@ -807,6 +902,14 @@ class Signaler(QtCore.QObject):      EIP_CAN_START = "eip_can_start"      EIP_CANNOT_START = "eip_cannot_start" +    SOLEDAD_BOOTSTRAP_FAILED = "soledad_bootstrap_failed" +    SOLEDAD_BOOTSTRAP_FINISHED = "soledad_bootstrap_finished" +    SOLEDAD_OFFLINE_FAILED = "soledad_offline_failed" +    SOLEDAD_OFFLINE_FINISHED = "soledad_offline_finished" +    SOLEDAD_INVALID_AUTH_TOKEN = "soledad_invalid_auth_token" + +    SOLEDAD_CANCELLED_BOOTSTRAP = "soledad_cancelled_bootstrap" +      BACKEND_BAD_CALL = "backend_bad_call"      def __init__(self): @@ -878,6 +981,13 @@ class Signaler(QtCore.QObject):              self.SRP_STATUS_LOGGED_IN,              self.SRP_STATUS_NOT_LOGGED_IN, +            self.SOLEDAD_BOOTSTRAP_FAILED, +            self.SOLEDAD_BOOTSTRAP_FINISHED, +            self.SOLEDAD_OFFLINE_FAILED, +            self.SOLEDAD_OFFLINE_FINISHED, +            self.SOLEDAD_INVALID_AUTH_TOKEN, +            self.SOLEDAD_CANCELLED_BOOTSTRAP, +              self.BACKEND_BAD_CALL,          ] @@ -937,6 +1047,7 @@ class Backend(object):          self._register(Register(self._signaler))          self._register(Authenticate(self._signaler))          self._register(EIP(self._signaler)) +        self._register(Soledad(self._signaler))          # We have a looping call on a thread executing all the          # commands in queue. Right now this queue is an actual Queue @@ -1282,6 +1393,53 @@ class Backend(object):          """          self._call_queue.put(("authenticate", "get_logged_in_status", None)) +    def soledad_bootstrap(self, username, domain, password): +        """ +        Bootstrap the soledad database. + +        :param username: the user name +        :type username: unicode +        :param domain: the domain that we are using. +        :type domain: unicode +        :param password: the password for the username +        :type password: unicode + +        Signals: +            soledad_bootstrap_finished +            soledad_bootstrap_failed +            soledad_invalid_auth_token +        """ +        self._call_queue.put(("soledad", "bootstrap", None, +                              username, domain, password)) + +    def load_offline_soledad(self, username, password, uuid): +        """ +        Load the soledad database in offline mode. + +        :param username: full user id (user@provider) +        :type username: str or unicode +        :param password: the soledad passphrase +        :type password: unicode +        :param uuid: the user uuid +        :type uuid: str or unicode + +        Signals: +        """ +        self._call_queue.put(("soledad", "load_offline", None, +                              username, password, uuid)) + +    def cancel_soledad_bootstrap(self): +        """ +        Cancel the ongoing soledad bootstrapping process (if any). +        """ +        self._call_queue.put(("soledad", "cancel_bootstrap", None)) + +    def close_soledad(self): +        """ +        Close soledad database. +        """ +        self._call_queue.put(("soledad", "close", None)) +      ###########################################################################      # XXX HACK: this section is meant to be a place to hold methods and      # variables needed in the meantime while we migrate all to the backend. @@ -1289,3 +1447,11 @@ class Backend(object):      def get_provider_config(self):          provider_config = self._components["provider"]._provider_config          return provider_config + +    def get_soledad(self): +        soledad = self._components["soledad"]._soledad_bootstrapper._soledad +        return soledad + +    def get_keymanager(self): +        km = self._components["soledad"]._soledad_bootstrapper._keymanager +        return km diff --git a/src/leap/bitmask/gui/mail_status.py b/src/leap/bitmask/gui/mail_status.py index d3346780..5caef745 100644 --- a/src/leap/bitmask/gui/mail_status.py +++ b/src/leap/bitmask/gui/mail_status.py @@ -188,7 +188,7 @@ class MailStatusWidget(QtGui.QWidget):      def set_soledad_failed(self):          """          TRIGGERS: -            SoledadBootstrapper.soledad_failed +            Signaler.soledad_bootstrap_failed          This method is called whenever soledad has a failure.          """ diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index e3848c46..cceb1efe 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -57,8 +57,6 @@ from leap.bitmask.services.mail import conductor as mail_conductor  from leap.bitmask.services import EIP_SERVICE, MX_SERVICE  from leap.bitmask.services.eip.connection import EIPConnection -from leap.bitmask.services.soledad.soledadbootstrapper import \ -    SoledadBootstrapper  from leap.bitmask.util import make_address  from leap.bitmask.util.keyring_helpers import has_keyring @@ -195,7 +193,6 @@ class MainWindow(QtGui.QMainWindow):          self._provisional_provider_config = ProviderConfig()          self._already_started_eip = False -        self._already_started_soledad = False          # This is created once we have a valid provider config          self._srp_auth = None @@ -205,18 +202,6 @@ class MainWindow(QtGui.QMainWindow):          self._backend_connected_signals = {}          self._backend_connect() -        self._soledad_bootstrapper = SoledadBootstrapper() -        self._soledad_bootstrapper.download_config.connect( -            self._soledad_intermediate_stage) -        self._soledad_bootstrapper.gen_key.connect( -            self._soledad_bootstrapped_stage) -        self._soledad_bootstrapper.local_only_ready.connect( -            self._soledad_bootstrapped_stage) -        self._soledad_bootstrapper.soledad_invalid_auth_token.connect( -            self._mail_status.set_soledad_invalid_auth_token) -        self._soledad_bootstrapper.soledad_failed.connect( -            self._mail_status.set_soledad_failed) -          self.ui.action_preferences.triggered.connect(self._show_preferences)          self.ui.action_eip_preferences.triggered.connect(              self._show_eip_preferences) @@ -302,8 +287,6 @@ class MainWindow(QtGui.QMainWindow):          self._soledad = ProxyBase(None)          self._keymanager = ProxyBase(None) -        self._soledad_defer = None -          self._mail_conductor = mail_conductor.MailConductor(              self._soledad, self._keymanager)          self._mail_conductor.connect_mail_signals(self._mail_status) @@ -461,6 +444,21 @@ class MainWindow(QtGui.QMainWindow):          sig.eip_can_start.connect(self._backend_can_start_eip)          sig.eip_cannot_start.connect(self._backend_cannot_start_eip) +        # Soledad signals +        sig.soledad_bootstrap_failed.connect( +            self._mail_status.set_soledad_failed) +        sig.soledad_bootstrap_finished.connect(self._on_soledad_ready) + +        sig.soledad_offline_failed.connect( +            self._mail_status.set_soledad_failed) +        sig.soledad_offline_finished.connect(self._on_soledad_ready) + +        sig.soledad_invalid_auth_token.connect( +            self._mail_status.set_soledad_invalid_auth_token) + +        # TODO: connect this with something +        # sig.soledad_cancelled_bootstrap.connect() +      def _disconnect_and_untrack(self):          """          Helper to disconnect the tracked signals. @@ -1252,11 +1250,7 @@ class MainWindow(QtGui.QMainWindow):          # XXX: Should we stop all the backend defers?          self._backend.cancel_setup_provider()          self._backend.cancel_login() - -        if self._soledad_defer is not None: -            logger.debug("Cancelling soledad defer.") -            self._soledad_defer.cancel() -            self._soledad_defer = None +        self._backend.cancel_soledad_bootstrap()      @QtCore.Slot()      def _set_login_cancelled(self): @@ -1317,9 +1311,9 @@ class MainWindow(QtGui.QMainWindow):          if MX_SERVICE in self._enabled_services:              btn_enabled = self._login_widget.set_logout_btn_enabled              btn_enabled(False) -            self.soledad_ready.connect(lambda: btn_enabled(True)) -            self._soledad_bootstrapper.soledad_failed.connect( -                lambda: btn_enabled(True)) +            sig = self._backend.signaler +            sig.soledad_bootstrap_failed.connect(lambda: btn_enabled(True)) +            sig.soledad_bootstrap_finished.connect(lambda: btn_enabled(True))          if not self._get_best_provider_config().provides_mx():              self._set_mx_visible(False) @@ -1372,9 +1366,6 @@ class MainWindow(QtGui.QMainWindow):          Conditionally start Soledad.          """          # TODO split. -        if self._already_started_soledad is True: -            return -          if not self._provides_mx_and_enabled():              return @@ -1382,11 +1373,7 @@ class MainWindow(QtGui.QMainWindow):          password = unicode(self._login_widget.get_password())          provider_domain = self._login_widget.get_selected_provider() -        sb = self._soledad_bootstrapper          if flags.OFFLINE is True: -            provider_domain = self._login_widget.get_selected_provider() -            sb._password = password -              self._provisional_provider_config.load(                  provider.get_provider_path(provider_domain)) @@ -1399,74 +1386,32 @@ class MainWindow(QtGui.QMainWindow):                  # this is mostly for internal use/debug for now.                  logger.warning("Sorry! Log-in at least one time.")                  return -            fun = sb.load_offline_soledad -            fun(full_user_id, password, uuid) +            self._backend.load_offline_soledad(full_user_id, password, uuid)          else: -            provider_config = self._provider_config -              if self._logged_user is not None: -                self._soledad_defer = sb.run_soledad_setup_checks( -                    provider_config, username, password, -                    download_if_needed=True) +                domain = self._provider_config.get_domain() +                self._backend.soledad_bootstrap(username, domain, password)      ###################################################################      # Service control methods: soledad -    @QtCore.Slot(dict) -    def _soledad_intermediate_stage(self, data): -        # TODO missing param docstring -        """ -        TRIGGERS: -            self._soledad_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._soledad_bootstrapper.PASSED_KEY] -        if not passed: -            # TODO display in the GUI: -            # should pass signal to a slot in status_panel -            # that sets the global status -            logger.error("Soledad failed to start: %s" % -                         (data[self._soledad_bootstrapper.ERROR_KEY],)) - -    @QtCore.Slot(dict) -    def _soledad_bootstrapped_stage(self, data): +    @QtCore.Slot() +    def _on_soledad_ready(self):          """          TRIGGERS: -            self._soledad_bootstrapper.gen_key -            self._soledad_bootstrapper.local_only_ready +            Signaler.soledad_bootstrap_finished -        If there was a problem, displays it, otherwise it does nothing. -        This is used for intermediate bootstrapping stages, in case -        they fail. - -        :param data: result from the bootstrapping stage for Soledad -        :type data: dict +        Actions to take when Soledad is ready.          """ -        passed = data[self._soledad_bootstrapper.PASSED_KEY] -        if not passed: -            # TODO should actually *display* on the panel. -            logger.debug("ERROR on soledad bootstrapping:") -            logger.error("%r" % data[self._soledad_bootstrapper.ERROR_KEY]) -            return -          logger.debug("Done bootstrapping Soledad")          # Update the proxy objects to point to          # the initialized instances. -        setProxiedObject(self._soledad, -                         self._soledad_bootstrapper.soledad) -        setProxiedObject(self._keymanager, -                         self._soledad_bootstrapper.keymanager) +        setProxiedObject(self._soledad, self._backend.get_soledad()) +        setProxiedObject(self._keymanager, self._backend.get_keymanager()) -        # Ok, now soledad is ready, so we can allow other things that -        # depend on soledad to start. -        self._soledad_defer = None +        self._soledad_started = True -        # this will trigger start_imap_service -        # and start_smtp_boostrapping          self.soledad_ready.emit()      ################################################################### @@ -1991,9 +1936,6 @@ class MainWindow(QtGui.QMainWindow):          self._cancel_ongoing_defers() -        # reset soledad status flag -        self._already_started_soledad = False -          # XXX: If other defers are doing authenticated stuff, this          # might conflict with those. CHECK!          self._backend.logout() @@ -2101,11 +2043,7 @@ class MainWindow(QtGui.QMainWindow):          if self._logged_user is not None:              self._backend.logout() -        if self._soledad_bootstrapper.soledad is not None: -            logger.debug("Closing soledad...") -            self._soledad_bootstrapper.soledad.close() -        else: -            logger.error("No instance of soledad was found.") +        self._backend.close_soledad()          logger.debug('Terminating vpn')          self._backend.stop_eip(shutdown=True) diff --git a/src/leap/bitmask/services/mail/conductor.py b/src/leap/bitmask/services/mail/conductor.py index 1766a39d..b4e97ac1 100644 --- a/src/leap/bitmask/services/mail/conductor.py +++ b/src/leap/bitmask/services/mail/conductor.py @@ -72,11 +72,9 @@ class IMAPControl(object):          from leap.bitmask.config import flags          logger.debug('Starting imap service') -        leap_assert(sameProxiedObjects(self._soledad, None) -                    is not True, +        leap_assert(not sameProxiedObjects(self._soledad, None),                      "We need a non-null soledad for initializing imap service") -        leap_assert(sameProxiedObjects(self._keymanager, None) -                    is not True, +        leap_assert(not sameProxiedObjects(self._keymanager, None),                      "We need a non-null keymanager for initializing imap "                      "service") diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index 6bb7c036..c015f5b7 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -25,7 +25,6 @@ import sys  from ssl import SSLError  from sqlite3 import ProgrammingError as sqlite_ProgrammingError -from PySide import QtCore  from u1db import errors as u1db_errors  from twisted.internet import threads  from zope.proxy import sameProxiedObjects @@ -134,16 +133,11 @@ class SoledadBootstrapper(AbstractBootstrapper):      MAX_INIT_RETRIES = 10      MAX_SYNC_RETRIES = 10 -    # All dicts returned are of the form -    # {"passed": bool, "error": str} -    download_config = QtCore.Signal(dict) -    gen_key = QtCore.Signal(dict) -    local_only_ready = QtCore.Signal(dict) -    soledad_invalid_auth_token = QtCore.Signal() -    soledad_failed = QtCore.Signal() +    def __init__(self, signaler=None): +        AbstractBootstrapper.__init__(self, signaler) -    def __init__(self): -        AbstractBootstrapper.__init__(self) +        if signaler is not None: +            self._cancel_signal = signaler.SOLEDAD_CANCELLED_BOOTSTRAP          self._provider_config = None          self._soledad_config = None @@ -181,16 +175,22 @@ class SoledadBootstrapper(AbstractBootstrapper):          Instantiate Soledad for offline use.          :param username: full user id (user@provider) -        :type username: basestring +        :type username: str or unicode          :param password: the soledad passphrase          :type password: unicode          :param uuid: the user uuid -        :type uuid: basestring +        :type uuid: str or unicode          """          print "UUID ", uuid          self._address = username +        self._password = password          self._uuid = uuid -        return self.load_and_sync_soledad(uuid, offline=True) +        try: +            self.load_and_sync_soledad(uuid, offline=True) +            self._signaler.signal(self._signaler.SOLEDAD_OFFLINE_FINISHED) +        except Exception: +            # TODO: we should handle more specific exceptions in here +            self._signaler.signal(self._signaler.SOLEDAD_OFFLINE_FAILED)      def _get_soledad_local_params(self, uuid, offline=False):          """ @@ -245,7 +245,7 @@ class SoledadBootstrapper(AbstractBootstrapper):      def _do_soledad_init(self, uuid, secrets_path, local_db_path,                           server_url, cert_file, token):          """ -        Initialize soledad, retry if necessary and emit soledad_failed if we +        Initialize soledad, retry if necessary and raise an exception if we          can't succeed.          :param uuid: user identifier @@ -263,19 +263,22 @@ class SoledadBootstrapper(AbstractBootstrapper):          :param auth token: auth token          :type auth_token: str          """ -        init_tries = self.MAX_INIT_RETRIES -        while init_tries > 0: +        init_tries = 1 +        while init_tries <= self.MAX_INIT_RETRIES:              try: +                logger.debug("Trying to init soledad....")                  self._try_soledad_init(                      uuid, secrets_path, local_db_path,                      server_url, cert_file, token)                  logger.debug("Soledad has been initialized.")                  return              except Exception: -                init_tries -= 1 +                init_tries += 1 +                msg = "Init failed, retrying... (retry {0} of {1})".format( +                    init_tries, self.MAX_INIT_RETRIES) +                logger.warning(msg)                  continue -        self.soledad_failed.emit()          raise SoledadInitError()      def load_and_sync_soledad(self, uuid=None, offline=False): @@ -306,9 +309,8 @@ class SoledadBootstrapper(AbstractBootstrapper):          leap_assert(not sameProxiedObjects(self._soledad, None),                      "Null soledad, error while initializing") -        if flags.OFFLINE is True: +        if flags.OFFLINE:              self._init_keymanager(self._address, token) -            self.local_only_ready.emit({self.PASSED_KEY: True})          else:              try:                  address = make_address( @@ -353,9 +355,10 @@ class SoledadBootstrapper(AbstractBootstrapper):          Do several retries to get an initial soledad sync.          """          # and now, let's sync -        sync_tries = self.MAX_SYNC_RETRIES -        while sync_tries > 0: +        sync_tries = 1 +        while sync_tries <= self.MAX_SYNC_RETRIES:              try: +                logger.debug("Trying to sync soledad....")                  self._try_soledad_sync()                  logger.debug("Soledad has been synced.")                  # so long, and thanks for all the fish @@ -368,19 +371,20 @@ class SoledadBootstrapper(AbstractBootstrapper):                  # retry strategy can be pushed to u1db, or at least                  # it's something worthy to talk about with the                  # ubuntu folks. -                sync_tries -= 1 +                sync_tries += 1 +                msg = "Sync failed, retrying... (retry {0} of {1})".format( +                    sync_tries, self.MAX_SYNC_RETRIES) +                logger.warning(msg)                  continue              except InvalidAuthTokenError: -                self.soledad_invalid_auth_token.emit() +                self._signaler.signal( +                    self._signaler.SOLEDAD_INVALID_AUTH_TOKEN)                  raise              except Exception as e:                  logger.exception("Unhandled error while syncing "                                   "soledad: %r" % (e,))                  break -        # reached bottom, failed to sync -        # and there's nothing we can do... -        self.soledad_failed.emit()          raise SoledadSyncError()      def _try_soledad_init(self, uuid, secrets_path, local_db_path, @@ -443,7 +447,6 @@ class SoledadBootstrapper(AbstractBootstrapper):          Raises SoledadSyncError if not successful.          """          try: -            logger.debug("trying to sync soledad....")              self._soledad.sync()          except SSLError as exc:              logger.error("%r" % (exc,)) @@ -467,7 +470,6 @@ class SoledadBootstrapper(AbstractBootstrapper):          """          Download the Soledad config for the given provider          """ -          leap_assert(self._provider_config,                      "We need a provider configuration!")          logger.debug("Downloading Soledad config for %s" % @@ -480,14 +482,6 @@ class SoledadBootstrapper(AbstractBootstrapper):              self._session,              self._download_if_needed) -        # soledad config is ok, let's proceed to load and sync soledad -        # XXX but honestly, this is a pretty strange entry point for that. -        # it feels like it should be the other way around: -        # load_and_sync, and from there, if needed, call download_config - -        uuid = self.srpauth.get_uuid() -        self.load_and_sync_soledad(uuid) -      def _get_gpg_bin_path(self):          """          Return the path to gpg binary. @@ -574,7 +568,7 @@ class SoledadBootstrapper(AbstractBootstrapper):                  logger.exception(exc)                  # but we do not raise -    def _gen_key(self, _): +    def _gen_key(self):          """          Generates the key pair if needed, uploads it to the webapp and          nickserver @@ -613,10 +607,7 @@ class SoledadBootstrapper(AbstractBootstrapper):          logger.debug("Key generated successfully.") -    def run_soledad_setup_checks(self, -                                 provider_config, -                                 user, -                                 password, +    def run_soledad_setup_checks(self, provider_config, user, password,                                   download_if_needed=False):          """          Starts the checks needed for a new soledad setup @@ -640,9 +631,25 @@ class SoledadBootstrapper(AbstractBootstrapper):          self._user = user          self._password = password -        cb_chain = [ -            (self._download_config, self.download_config), -            (self._gen_key, self.gen_key) -        ] +        if flags.OFFLINE: +            signal_finished = self._signaler.SOLEDAD_OFFLINE_FINISHED +            signal_failed = self._signaler.SOLEDAD_OFFLINE_FAILED +        else: +            signal_finished = self._signaler.SOLEDAD_BOOTSTRAP_FINISHED +            signal_failed = self._signaler.SOLEDAD_BOOTSTRAP_FAILED + +        try: +            self._download_config() + +            # soledad config is ok, let's proceed to load and sync soledad +            uuid = self.srpauth.get_uuid() +            self.load_and_sync_soledad(uuid) + +            if not flags.OFFLINE: +                self._gen_key() -        return self.addCallbackChain(cb_chain) +            self._signaler.signal(signal_finished) +        except Exception as e: +            # TODO: we should handle more specific exceptions in here +            logger.exception("Error while bootstrapping Soledad: %r" % (e, )) +            self._signaler.signal(signal_failed) | 
