diff options
| -rw-r--r-- | changes/bug-handle-closed-soledad | 1 | ||||
| -rw-r--r-- | changes/bug_cancel-login-does-not-work | 1 | ||||
| -rw-r--r-- | src/leap/bitmask/backend.py | 36 | ||||
| -rw-r--r-- | src/leap/bitmask/crypto/srpauth.py | 16 | ||||
| -rwxr-xr-x | src/leap/bitmask/crypto/tests/fake_provider.py | 1 | ||||
| -rw-r--r-- | src/leap/bitmask/gui/mainwindow.py | 72 | ||||
| -rw-r--r-- | src/leap/bitmask/platform_init/locks.py | 2 | ||||
| -rw-r--r-- | src/leap/bitmask/services/abstractbootstrapper.py | 7 | ||||
| -rw-r--r-- | src/leap/bitmask/services/soledad/soledadbootstrapper.py | 19 | 
9 files changed, 107 insertions, 48 deletions
| diff --git a/changes/bug-handle-closed-soledad b/changes/bug-handle-closed-soledad new file mode 100644 index 00000000..bf0a905e --- /dev/null +++ b/changes/bug-handle-closed-soledad @@ -0,0 +1 @@ +- Handle closed Soledad database on quit, speedup exit. Closes #5130. diff --git a/changes/bug_cancel-login-does-not-work b/changes/bug_cancel-login-does-not-work new file mode 100644 index 00000000..8fbccb5a --- /dev/null +++ b/changes/bug_cancel-login-does-not-work @@ -0,0 +1 @@ +- Cancel login does not work or needs to be pressed twice. Closes #4869, #4973. diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index df79381c..34457e34 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -141,6 +141,14 @@ class Provider(object):          self._download_provider_defer = d          return d +    def cancel_setup_provider(self): +        """ +        Cancel the ongoing setup provider defer (if any). +        """ +        d = self._download_provider_defer +        if d is not None: +            d.cancel() +      def bootstrap(self, provider):          """          Second stage of bootstrapping for a provider. @@ -195,6 +203,8 @@ class Signaler(QtCore.QObject):      prov_unsupported_client = QtCore.Signal(object)      prov_unsupported_api = QtCore.Signal(object) +    prov_cancelled_setup = 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 @@ -208,6 +218,7 @@ class Signaler(QtCore.QObject):      PROV_PROBLEM_WITH_PROVIDER_KEY = "prov_problem_with_provider"      PROV_UNSUPPORTED_CLIENT = "prov_unsupported_client"      PROV_UNSUPPORTED_API = "prov_unsupported_api" +    PROV_CANCELLED_SETUP = "prov_cancelled_setup"      def __init__(self):          """ @@ -225,7 +236,8 @@ class Signaler(QtCore.QObject):              self.PROV_CHECK_API_CERTIFICATE_KEY,              self.PROV_PROBLEM_WITH_PROVIDER_KEY,              self.PROV_UNSUPPORTED_CLIENT, -            self.PROV_UNSUPPORTED_API +            self.PROV_UNSUPPORTED_API, +            self.PROV_CANCELLED_SETUP,          ]          for sig in signals: @@ -355,17 +367,20 @@ class Backend(object):              # 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) +            if d is not None:  # d may be None if a defer chain is cancelled. +                # 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 defer.CancelledError: +            logger.debug("defer cancelled somewhere (CancelledError).")          except Exception:              # But we log the rest              log.err() @@ -387,5 +402,8 @@ class Backend(object):      def setup_provider(self, provider):          self._call_queue.put(("provider", "setup_provider", None, provider)) +    def cancel_setup_provider(self): +        self._call_queue.put(("provider", "cancel_setup_provider", None)) +      def provider_bootstrap(self, provider):          self._call_queue.put(("provider", "bootstrap", None, provider)) diff --git a/src/leap/bitmask/crypto/srpauth.py b/src/leap/bitmask/crypto/srpauth.py index bdd38db2..b46f0ea6 100644 --- a/src/leap/bitmask/crypto/srpauth.py +++ b/src/leap/bitmask/crypto/srpauth.py @@ -600,7 +600,8 @@ class SRPAuth(QtCore.QObject):              QtCore.QMutexLocker(self._uuid_lock)              full_uid = "%s@%s" % (                  self._username, self._provider_config.get_domain()) -            self._settings.set_uuid(full_uid, uuid) +            if uuid is not None:  # avoid removing the uuid from settings +                self._settings.set_uuid(full_uid, uuid)              self._uuid = uuid          def get_uuid(self): @@ -655,7 +656,6 @@ class SRPAuth(QtCore.QObject):          username = username.lower()          d = self.__instance.authenticate(username, password)          d.addCallback(self._gui_notify) -        d.addErrback(self._errback)          return d      def change_password(self, current_password, new_password): @@ -695,18 +695,6 @@ class SRPAuth(QtCore.QObject):          logger.debug("Successful login!")          self.authentication_finished.emit(True, self.tr("Succeeded")) -    def _errback(self, failure): -        """ -        General errback for the whole login process. Will notify the -        UI with the proper signal. - -        :param failure: Failure object captured from a callback. -        :type failure: twisted.python.failure.Failure -        """ -        logger.error("Error logging in %s" % (failure,)) -        self.authentication_finished.emit(False, "%s" % (failure.value,)) -        failure.trap(Exception) -      def get_session_id(self):          return self.__instance.get_session_id() diff --git a/src/leap/bitmask/crypto/tests/fake_provider.py b/src/leap/bitmask/crypto/tests/fake_provider.py index 54af485d..b8cdbb12 100755 --- a/src/leap/bitmask/crypto/tests/fake_provider.py +++ b/src/leap/bitmask/crypto/tests/fake_provider.py @@ -280,7 +280,6 @@ class FakeSession(Resource):          if HAMK is None:              print '[server] verification failed!!!'              raise Exception("Authentication failed!") -            #import ipdb;ipdb.set_trace()          assert svr.authenticated()          print "***" diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index db24a1c8..6512ffce 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -20,10 +20,12 @@ Main window for Bitmask.  import logging  from threading import Condition -from PySide import QtCore, QtGui  from datetime import datetime -from twisted.internet import threads + +from PySide import QtCore, QtGui  from zope.proxy import ProxyBase, setProxiedObject +from twisted.internet import threads +from twisted.internet.defer import CancelledError  from leap.bitmask import __version__ as VERSION  from leap.bitmask import __version_hash__ as VERSION_HASH @@ -341,7 +343,7 @@ class MainWindow(QtGui.QMainWindow):          self._keymanager = ProxyBase(None)          self._login_defer = None -        self._download_provider_defer = None +        self._soledad_defer = None          self._mail_conductor = mail_conductor.MailConductor(              self._soledad, self._keymanager) @@ -394,6 +396,8 @@ class MainWindow(QtGui.QMainWindow):          sig.prov_unsupported_client.connect(self._needs_update)          sig.prov_unsupported_api.connect(self._incompatible_api) +        sig.prov_cancelled_setup.connect(self._set_login_cancelled) +      def _backend_disconnect(self):          """          Helper to disconnect from backend signals. @@ -1034,6 +1038,25 @@ class MainWindow(QtGui.QMainWindow):              if self._login_widget.start_login():                  self._download_provider_config() +    def _login_errback(self, failure): +        """ +        Error handler for the srpauth.authenticate method. + +        :param failure: failure object that Twisted generates +        :type failure: twisted.python.failure.Failure +        """ +        # NOTE: this behavior needs to be managed through the signaler, +        # as we are doing with the prov_cancelled_setup signal. +        # After we move srpauth to the backend, we need to update this. +        logger.error("Error logging in, {0!r}".format(failure)) +        if failure.check(CancelledError): +            logger.debug("Defer cancelled.") +            failure.trap(Exception) +            self._set_login_cancelled() +        else: +            self._login_widget.set_status(str(failure.value)) +            self._login_widget.set_enabled(True) +      def _cancel_login(self):          """          SLOT @@ -1042,17 +1065,29 @@ class MainWindow(QtGui.QMainWindow):          Stops the login sequence.          """ -        logger.debug("Cancelling log in.") +        logger.debug("Cancelling setup provider defer.") +        self._backend.cancel_setup_provider() -        if self._download_provider_defer: -            logger.debug("Cancelling download provider defer.") -            self._download_provider_defer.cancel() - -        if self._login_defer: +        if self._login_defer is not None:              logger.debug("Cancelling login defer.")              self._login_defer.cancel() +        if self._soledad_defer is not None: +            logger.debug("Cancelling soledad defer.") +            self._soledad_defer.cancel() + +    def _set_login_cancelled(self): +        """ +        SLOT +        TRIGGERS: +            Signaler.prov_cancelled_setup fired by +            self._backend.cancel_setup_provider() + +        This method re-enables the login widget and display a message for +        the cancelled operation. +        """          self._login_widget.set_status(self.tr("Log in cancelled by the user.")) +        self._login_widget.set_enabled(True)      def _provider_config_loaded(self, data):          """ @@ -1079,6 +1114,7 @@ class MainWindow(QtGui.QMainWindow):              # TODO Add errback!              self._login_defer = self._srp_auth.authenticate(username, password) +            self._login_defer.addErrback(self._login_errback)          else:              self._login_widget.set_status(                  "Unable to login: Problem with provider") @@ -1167,8 +1203,8 @@ class MainWindow(QtGui.QMainWindow):              provider_config = self._provider_config              if self._logged_user is not None: -                fun = sb.run_soledad_setup_checks -                fun(provider_config, username, password, +                self._soledad_defer = sb.run_soledad_setup_checks( +                    provider_config, username, password,                      download_if_needed=True)      ################################################################### @@ -1242,6 +1278,7 @@ class MainWindow(QtGui.QMainWindow):          # Ok, now soledad is ready, so we can allow other things that          # depend on soledad to start. +        self._soledad_defer = None          # this will trigger start_imap_service          # and start_smtp_boostrapping @@ -1813,9 +1850,9 @@ class MainWindow(QtGui.QMainWindow):          """          passed = data[self._backend.PASSED_KEY]          if not passed: +            msg = self.tr("Unable to connect: Problem with provider") +            self._login_widget.set_status(msg)              self._login_widget.set_enabled(True) -            self._login_widget.set_status( -                self.tr("Unable to connect: Problem with provider"))              logger.error(data[self._backend.ERROR_KEY])      # @@ -1887,9 +1924,12 @@ class MainWindow(QtGui.QMainWindow):              logger.debug("Cancelling login defer.")              self._login_defer.cancel() -        if self._download_provider_defer: -            logger.debug("Cancelling download provider defer.") -            self._download_provider_defer.cancel() +        logger.debug("Cancelling setup provider defer.") +        self._backend.cancel_setup_provider() + +        if self._soledad_defer is not None: +            logger.debug("Cancelling soledad defer.") +            self._soledad_defer.cancel()          # TODO missing any more cancels? diff --git a/src/leap/bitmask/platform_init/locks.py b/src/leap/bitmask/platform_init/locks.py index 34f884dc..78ebf4cd 100644 --- a/src/leap/bitmask/platform_init/locks.py +++ b/src/leap/bitmask/platform_init/locks.py @@ -83,8 +83,6 @@ if platform_init.IS_UNIX:                  flock(self._fd, LOCK_EX | LOCK_NB)              except IOError as exc:                  # could not get the lock -                #import ipdb; ipdb.set_trace() -                  if exc.args[0] in (errno.EDEADLK, errno.EAGAIN):                      # errno 11 or 35                      # Resource temporarily unavailable diff --git a/src/leap/bitmask/services/abstractbootstrapper.py b/src/leap/bitmask/services/abstractbootstrapper.py index 3bee8e01..fc6bd3e9 100644 --- a/src/leap/bitmask/services/abstractbootstrapper.py +++ b/src/leap/bitmask/services/abstractbootstrapper.py @@ -28,6 +28,7 @@ from PySide import QtCore  from twisted.python import log  from twisted.internet import threads +from twisted.internet.defer import CancelledError  from leap.common.check import leap_assert, leap_assert_type @@ -91,6 +92,12 @@ class AbstractBootstrapper(QtCore.QObject):          :param failure: failure object that Twisted generates          :type failure: twisted.python.failure.Failure          """ +        if failure.check(CancelledError): +            logger.debug("Defer cancelled.") +            failure.trap(Exception) +            self._signaler.signal(self._signaler.PROV_CANCELLED_SETUP) +            return +          if self._signal_to_emit:              err_msg = self._err_msg \                  if self._err_msg is not None \ diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index f7217af6..b61d0d43 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -20,6 +20,7 @@ Soledad bootstrapping  import logging  import os  import socket +import sqlite3  import sys  from ssl import SSLError @@ -348,6 +349,10 @@ class SoledadBootstrapper(AbstractBootstrapper):                  # ubuntu folks.                  sync_tries -= 1                  continue +            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... @@ -435,7 +440,9 @@ class SoledadBootstrapper(AbstractBootstrapper):          except u1db_errors.InvalidGeneration as exc:              logger.error("%r" % (exc,))              raise SoledadSyncError("u1db: InvalidGeneration") - +        except sqlite3.ProgrammingError as e: +            logger.exception("%r" % (e,)) +            raise          except Exception as exc:              logger.exception("Unhandled error while syncing "                               "soledad: %r" % (exc,)) @@ -508,10 +515,10 @@ class SoledadBootstrapper(AbstractBootstrapper):          if flags.OFFLINE is True:              args = (address, "https://localhost", self._soledad)              kwargs = { -                "session_id":  "", -                "ca_cert_path":  "", -                "api_uri":  "", -                "api_version":  "", +                "session_id": "", +                "ca_cert_path": "", +                "api_uri": "", +                "api_version": "",                  "uid": self._uuid,                  "gpgbinary": self._get_gpg_bin_path()              } @@ -617,4 +624,4 @@ class SoledadBootstrapper(AbstractBootstrapper):              (self._gen_key, self.gen_key)          ] -        self.addCallbackChain(cb_chain) +        return self.addCallbackChain(cb_chain) | 
