diff options
20 files changed, 210 insertions, 69 deletions
| @@ -1,3 +1,4 @@ +dropin.cache  *.log  *.pyc  dist/ diff --git a/client/changes/VERSION_COMPAT b/client/changes/VERSION_COMPAT index cc00ecf7..a24751f9 100644 --- a/client/changes/VERSION_COMPAT +++ b/client/changes/VERSION_COMPAT @@ -8,3 +8,5 @@  #  # BEGIN DEPENDENCY LIST -------------------------  # leap.foo.bar>=x.y.z +# +leap.common >= 0.4.1  # collect_plugins diff --git a/client/changes/bug_fix-sync-enc-close-queue-error b/client/changes/bug_fix-sync-enc-close-queue-error new file mode 100644 index 00000000..71af7c67 --- /dev/null +++ b/client/changes/bug_fix-sync-enc-close-queue-error @@ -0,0 +1 @@ +  o Fix sync encrypter pool close queue error. Closes #7088. diff --git a/client/changes/feature_6996-post-sync-hooks b/client/changes/feature_6996-post-sync-hooks new file mode 100644 index 00000000..027c0b2a --- /dev/null +++ b/client/changes/feature_6996-post-sync-hooks @@ -0,0 +1 @@ +o Expose post-sync hooks via plugin system. Related: #6996 diff --git a/client/pkg/requirements.pip b/client/pkg/requirements.pip index 9fffdbe3..26f7c979 100644 --- a/client/pkg/requirements.pip +++ b/client/pkg/requirements.pip @@ -8,7 +8,7 @@ zope.proxy  twisted  # leap deps -- bump me! -leap.common>=0.4 +leap.common>=0.4.0  leap.soledad.common>=0.6.5  # XXX -- fix me! diff --git a/client/src/leap/soledad/client/adbapi.py b/client/src/leap/soledad/client/adbapi.py index 5b882bbe..bc0ab7a5 100644 --- a/client/src/leap/soledad/client/adbapi.py +++ b/client/src/leap/soledad/client/adbapi.py @@ -217,6 +217,9 @@ class U1DBConnectionPool(adbapi.ConnectionPool):          """          meth = getattr(trans, meth)          return meth(*args, **kw) +        # XXX should return a fetchall? + +    # XXX add _runOperation too      def _runInteraction(self, interaction, *args, **kw):          """ diff --git a/client/src/leap/soledad/client/api.py b/client/src/leap/soledad/client/api.py index 76d6acc3..6c2b3673 100644 --- a/client/src/leap/soledad/client/api.py +++ b/client/src/leap/soledad/client/api.py @@ -38,13 +38,16 @@ try:      import cchardet as chardet  except ImportError:      import chardet +from itertools import chain  from StringIO import StringIO  from u1db.remote import http_client  from u1db.remote.ssl_match_hostname import match_hostname +from twisted.plugin import getPlugins  from zope.interface import implements  from leap.common.config import get_path_prefix +from leap.common.plugins import collect_plugins  from leap.soledad.common import SHARED_DB_NAME  from leap.soledad.common import soledad_assert @@ -656,6 +659,20 @@ class Soledad(object):              defer_decryption=defer_decryption)          def _sync_callback(local_gen): +            self._last_received_docs = docs = self._dbsyncer.received_docs + +            # Post-Sync Hooks +            if docs: +                iface = soledad_interfaces.ISoledadPostSyncPlugin +                suitable_plugins = collect_plugins(iface) +                for plugin in suitable_plugins: +                    watched = plugin.watched_doc_types +                    r = [filter( +                        lambda s: s.startswith(preffix), +                        docs) for preffix in watched] +                    filtered = list(chain(*r)) +                    plugin.process_received_docs(filtered) +              soledad_events.emit(                  soledad_events.SOLEDAD_DONE_DATA_SYNC, self.uuid)              return local_gen @@ -788,10 +805,18 @@ class Soledad(object):      def raw_sqlcipher_query(self, *args, **kw):          """ -        Run a raw sqlcipher query in the local database. +        Run a raw sqlcipher query in the local database, and return a deferred +        that will be fired with the result.          """          return self._dbpool.runQuery(*args, **kw) +    def raw_sqlcipher_operation(self, *args, **kw): +        """ +        Run a raw sqlcipher operation in the local database, and return a +        deferred that will be fired with None. +        """ +        return self._dbpool.runOperation(*args, **kw) +  def _convert_to_unicode(content):      """ diff --git a/client/src/leap/soledad/client/encdecpool.py b/client/src/leap/soledad/client/encdecpool.py index d9a72b25..f81cd2d1 100644 --- a/client/src/leap/soledad/client/encdecpool.py +++ b/client/src/leap/soledad/client/encdecpool.py @@ -29,7 +29,7 @@ import logging  from twisted.internet import reactor  from twisted.internet import defer -from twisted.internet.threads import deferToThread +from twisted.python import log  from leap.soledad.common.document import SoledadDocument  from leap.soledad.common import soledad_assert @@ -147,7 +147,7 @@ class SyncEncrypterPool(SyncEncryptDecryptPool):      TABLE_NAME = "docs_tosync"      FIELD_NAMES = "doc_id PRIMARY KEY, rev, content" -    ENCRYPT_LOOP_PERIOD = 0.5 +    ENCRYPT_LOOP_PERIOD = 2      def __init__(self, *args, **kwargs):          """ @@ -159,9 +159,8 @@ class SyncEncrypterPool(SyncEncryptDecryptPool):          self._sync_queue = multiprocessing.Queue()          # start the encryption loop -        self._deferred_loop = deferToThread(self._encrypt_docs_loop) -        self._deferred_loop.addCallback( -            lambda _: logger.debug("Finished encrypter thread.")) +        logger.debug("Starting the encryption loop...") +        reactor.callWhenRunning(self._maybe_encrypt_and_recurse)      def enqueue_doc_for_encryption(self, doc):          """ @@ -171,24 +170,28 @@ class SyncEncrypterPool(SyncEncryptDecryptPool):          :type doc: SoledadDocument          """          try: -            self.sync_queue.put_nowait(doc) -        except multiprocessing.Queue.Full: +            self._sync_queue.put_nowait(doc) +        except Queue.Full:              # do not asynchronously encrypt this file if the queue is full              pass -    def _encrypt_docs_loop(self): +    def _maybe_encrypt_and_recurse(self):          """          Process the syncing queue and send the documents there to be encrypted          in the sync db. They will be read by the SoledadSyncTarget during the          sync_exchange.          """ -        logger.debug("Starting encrypter thread.") -        while not self._stopped: +        if not self._stopped:              try: -                doc = self._sync_queue.get(True, self.ENCRYPT_LOOP_PERIOD) +                doc = self._sync_queue.get(False)                  self._encrypt_doc(doc)              except Queue.Empty:                  pass +            reactor.callLater( +                self.ENCRYPT_LOOP_PERIOD, +                self._maybe_encrypt_and_recurse) +        else: +            logger.debug("Finished encrypter thread.")      def _encrypt_doc(self, doc):          """ @@ -374,9 +377,9 @@ class SyncDecrypterPool(SyncEncryptDecryptPool):          self.source_replica_uid = kwargs.pop("source_replica_uid")          SyncEncryptDecryptPool.__init__(self, *args, **kwargs) -        self._last_inserted_idx = 0          self._docs_to_process = None          self._processed_docs = 0 +        self._last_inserted_idx = 0          self._async_results = []          self._failure = None @@ -386,6 +389,7 @@ class SyncDecrypterPool(SyncEncryptDecryptPool):          #     asynchronous call, so we have to somehow make sure that it is          #     executed before any other call to the database, without          #     blocking. +        # XXX in mail and keymanager we have a pattern for that -- kali.          self._empty()      def _launch_decrypt_and_process(self): @@ -402,6 +406,7 @@ class SyncDecrypterPool(SyncEncryptDecryptPool):          return self._failure      def _set_failure(self, failure): +        log.err(failure)          self._failure = failure          self._finished = True @@ -419,6 +424,7 @@ class SyncDecrypterPool(SyncEncryptDecryptPool):          :type docs_to_process: int          """          self._docs_to_process = docs_to_process +        self._finished = False          self._schedule_decrypt_and_process()      def insert_encrypted_received_doc( @@ -729,7 +735,10 @@ class SyncDecrypterPool(SyncEncryptDecryptPool):          :rtype: twisted.internet.defer.Deferred          """          if not self.failed(): -            if self._processed_docs < self._docs_to_process: +            processed = self._processed_docs +            pending = self._docs_to_process + +            if not self.has_finished() and processed < pending:                  yield self._async_decrypt_received_docs()                  yield self._collect_async_decryption_results()                  docs = yield self._process_decrypted_docs() @@ -737,7 +746,12 @@ class SyncDecrypterPool(SyncEncryptDecryptPool):                  # recurse                  self._schedule_decrypt_and_process()              else: -                self._finished = True +                self._mark_finished() + +    def _mark_finished(self): +        self._finished = True +        self._processed_docs = 0 +        self._last_inserted_idx = 0      def has_finished(self):          """ diff --git a/client/src/leap/soledad/client/http_target.py b/client/src/leap/soledad/client/http_target.py index 30590ae1..ac078f39 100644 --- a/client/src/leap/soledad/client/http_target.py +++ b/client/src/leap/soledad/client/http_target.py @@ -75,8 +75,6 @@ class SoledadHTTPSyncTarget(SyncTarget):          :param source_replica_uid: The source replica uid which we use when                                     deferring decryption.          :type source_replica_uid: str -        :param url: The url of the target replica to sync with. -        :type url: str          :param creds: A dictionary containing the uuid and token.          :type creds: creds          :param crypto: An instance of SoledadCrypto so we can encrypt/decrypt @@ -98,7 +96,7 @@ class SoledadHTTPSyncTarget(SyncTarget):          """          if url.endswith("/"):              url = url[:-1] -        self._url = str(url) + "/sync-from/" + source_replica_uid +        self._url = str(url) + "/sync-from/" + str(source_replica_uid)          self.source_replica_uid = source_replica_uid          self._auth_header = None          self.set_creds(creds) @@ -111,6 +109,9 @@ class SoledadHTTPSyncTarget(SyncTarget):          self._sync_decr_pool = None          self._http = HTTPClient(cert_file) +    def close(self): +        self._http.close() +      def set_creds(self, creds):          """          Update credentials. diff --git a/client/src/leap/soledad/client/interfaces.py b/client/src/leap/soledad/client/interfaces.py index 4f7b0779..14b34d24 100644 --- a/client/src/leap/soledad/client/interfaces.py +++ b/client/src/leap/soledad/client/interfaces.py @@ -19,6 +19,37 @@ Interfaces used by the Soledad Client.  """  from zope.interface import Interface, Attribute +# +# Plugins +# + + +class ISoledadPostSyncPlugin(Interface): +    """ +    I implement the minimal methods and attributes for a plugin that can be +    called after a soledad synchronization has ended. +    """ + +    def process_received_docs(self, doc_id_list): +        """ +        Do something with the passed list of doc_ids received after the last +        sync. + +        :param doc_id_list: a list of strings for the received doc_ids +        """ + +    watched_doc_types = Attribute(""" +        a tuple of the watched doc types for this plugin. So far, the +        `doc-types` convention is just the preffix of the doc_id, which is +        basically its first character, followed by a dash. So, for instance, +        `M-` is used for meta-docs in mail, and `F-` is used for flag-docs in +        mail. For now there's no central register of all the doc-types +        used.""") + + +# +# Soledad storage +#  class ILocalStorage(Interface):      """ diff --git a/client/src/leap/soledad/client/sqlcipher.py b/client/src/leap/soledad/client/sqlcipher.py index b2025130..75d786a6 100644 --- a/client/src/leap/soledad/client/sqlcipher.py +++ b/client/src/leap/soledad/client/sqlcipher.py @@ -456,6 +456,9 @@ class SQLCipherU1DBSync(SQLCipherDatabase):          self._syncers = {} +        # Storage for the documents received during a sync +        self.received_docs = [] +          self.running = False          self._sync_threadpool = None          self._initialize_sync_threadpool() @@ -587,8 +590,16 @@ class SQLCipherU1DBSync(SQLCipherDatabase):          # the following context manager blocks until the syncing lock can be          # acquired.          with self._syncer(url, creds=creds) as syncer: + +            def _record_received_docs(result): +                # beware, closure. syncer is in scope. +                self.received_docs = syncer.received_docs +                return result +              # XXX could mark the critical section here... -            return syncer.sync(defer_decryption=defer_decryption) +            d = syncer.sync(defer_decryption=defer_decryption) +            d.addCallback(_record_received_docs) +            return d      @contextmanager      def _syncer(self, url, creds=None): diff --git a/client/src/leap/soledad/client/sync.py b/client/src/leap/soledad/client/sync.py index 53172f31..14c547cf 100644 --- a/client/src/leap/soledad/client/sync.py +++ b/client/src/leap/soledad/client/sync.py @@ -39,6 +39,7 @@ class SoledadSynchronizer(Synchronizer):      Also modified to allow for interrupting the synchronization process.      """ +    received_docs = []      @defer.inlineCallbacks      def sync(self, defer_decryption=True): @@ -62,6 +63,7 @@ class SoledadSynchronizer(Synchronizer):          :rtype: twisted.internet.defer.Deferred          """          sync_target = self.sync_target +        self.received_docs = []          # get target identifier, its current generation,          # and its last-seen database generation for this source @@ -123,12 +125,14 @@ class SoledadSynchronizer(Synchronizer):          changed_doc_ids = [doc_id for doc_id, _, _ in changes]          docs_to_send = self.source.get_docs(              changed_doc_ids, check_for_conflicts=False, include_deleted=True) +        ids_sent = []          docs_by_generation = []          idx = 0          for doc in docs_to_send:              _, gen, trans = changes[idx]              docs_by_generation.append((doc, gen, trans))              idx += 1 +            ids_sent.append(doc.doc_id)          # exchange documents and try to insert the returned ones with          # the target, return target synced-up-to gen. @@ -151,6 +155,12 @@ class SoledadSynchronizer(Synchronizer):          self._syncing_info = info          yield self.complete_sync() +        _, _, changes = self.source.whats_changed(target_my_gen) +        changed_doc_ids = [doc_id for doc_id, _, _ in changes] + +        just_received = list(set(changed_doc_ids) - set(ids_sent)) +        self.received_docs = just_received +          defer.returnValue(my_gen)      def complete_sync(self): diff --git a/common/pkg/requirements-testing.pip b/common/pkg/requirements-testing.pip index c72c9fc4..e23135ac 100644 --- a/common/pkg/requirements-testing.pip +++ b/common/pkg/requirements-testing.pip @@ -1,6 +1,6 @@  mock  testscenarios -leap.common -leap.soledad.server -leap.soledad.client +leap.common>=0.4.0 +leap.soledad.server>=0.7.0 +leap.soledad.client>=0.7.0  setuptools-trial diff --git a/common/pkg/requirements.pip b/common/pkg/requirements.pip index 005d6884..b91186e7 100644 --- a/common/pkg/requirements.pip +++ b/common/pkg/requirements.pip @@ -2,7 +2,7 @@ simplejson  u1db  # leap deps -- bump me! -leap.common>=0.7.0 +leap.common>=0.4.0  # XXX -- fix me!  # oauth is not strictly needed by us, but we need it until u1db adds it to its diff --git a/common/src/leap/soledad/common/couch.py b/common/src/leap/soledad/common/couch.py index 8d262ccd..ae9e7d2a 100644 --- a/common/src/leap/soledad/common/couch.py +++ b/common/src/leap/soledad/common/couch.py @@ -240,8 +240,12 @@ def raise_server_error(exc, ddoc_path):                                           document for an yet unknown reason.      """      path = "".join(ddoc_path) -    if exc.message[1][0] == 'unnamed_error': +    msg = exc.message[1][0] +    if msg == 'unnamed_error':          raise errors.MissingDesignDocListFunctionError(path) +    elif msg == 'TypeError': +        if 'point is undefined' in exc.message[1][1]: +            raise errors.MissingDesignDocListFunctionError      # other errors are unknown for now      raise errors.DesignDocUnknownError(path) diff --git a/common/src/leap/soledad/common/tests/test_https.py b/common/src/leap/soledad/common/tests/test_https.py index 4dd55754..6907e3ed 100644 --- a/common/src/leap/soledad/common/tests/test_https.py +++ b/common/src/leap/soledad/common/tests/test_https.py @@ -50,16 +50,22 @@ LEAP_SCENARIOS = [  # The following tests come from `u1db.tests.test_https`.  #----------------------------------------------------------------------------- -def token_leap_https_sync_target(test, host, path): +def token_leap_https_sync_target(test, host, path, cert_file=None):      _, port = test.server.server_address -    st = client.target.SoledadSyncTarget( +    #source_replica_uid = test._soledad._dbpool.replica_uid +    creds = {'token': {'uuid': 'user-uuid', 'token': 'auth-token'}} +    if not cert_file: +        cert_file = test.cacert_pem +    st = client.http_target.SoledadHTTPSyncTarget(          'https://%s:%d/%s' % (host, port, path), -        crypto=test._soledad._crypto) -    st.set_token_credentials('user-uuid', 'auth-token') +        source_replica_uid='other-id', +        creds=creds, +        crypto=test._soledad._crypto, +        cert_file=cert_file)      return st -class TestSoledadSyncTargetHttpsSupport( +class TestSoledadHTTPSyncTargetHttpsSupport(          TestWithScenarios,          test_https.TestHttpSyncTargetHttpsSupport,          BaseSoledadTest): @@ -80,6 +86,29 @@ class TestSoledadSyncTargetHttpsSupport(          http_client._VerifiedHTTPSConnection = client.api.VerifiedHTTPSConnection          client.api.SOLEDAD_CERT = http_client.CA_CERTS +    def test_cannot_verify_cert(self): +        self.startServer() +        # don't print expected traceback server-side +        self.server.handle_error = lambda req, cli_addr: None +        self.request_state._create_database('test') +        remote_target = self.getSyncTarget( +            'localhost', 'test', cert_file=http_client.CA_CERTS) +        d = remote_target.record_sync_info('other-id', 2, 'T-id') + +        def _assert_raises(result): +            from twisted.python.failure import Failure +            if isinstance(result, Failure): +                from OpenSSL.SSL import Error +                error = result.value.message[0].value +                if isinstance(error, Error): +                    msg = error.message[0][2] +                    self.assertEqual("certificate verify failed", msg) +                    return +            self.fail("certificate verification should have failed.") + +        d.addCallbacks(_assert_raises, _assert_raises) +        return d +      def test_working(self):          """          Test that SSL connections work well. @@ -89,24 +118,19 @@ class TestSoledadSyncTargetHttpsSupport(          """          self.startServer()          db = self.request_state._create_database('test') -        self.patch(client.api, 'SOLEDAD_CERT', self.cacert_pem)          remote_target = self.getSyncTarget('localhost', 'test') -        remote_target.record_sync_info('other-id', 2, 'T-id') -        self.assertEqual( -            (2, 'T-id'), db._get_replica_gen_and_trans_id('other-id')) +        d = remote_target.record_sync_info('other-id', 2, 'T-id') +        d.addCallback(lambda _: +            self.assertEqual( +                (2, 'T-id'), db._get_replica_gen_and_trans_id('other-id'))) +        d.addCallback(lambda _: +            remote_target.close()) +        return d      def test_host_mismatch(self):          """ -        Test that SSL connections to a hostname different than the one in the -        certificate raise CertificateError. - -        This test was adapted to patch Soledad's HTTPS connection custom class -        with the intended CA certificates. +        This test is disabled because soledad's twisted-based http agent uses +        pyOpenSSL, which will complain if we try to use an IP to connect to +        the remote host (see the original test in u1db_tests/test_https.py).          """ -        self.startServer() -        self.request_state._create_database('test') -        self.patch(client.api, 'SOLEDAD_CERT', self.cacert_pem) -        remote_target = self.getSyncTarget('127.0.0.1', 'test') -        self.assertRaises( -            http_client.CertificateError, remote_target.record_sync_info, -            'other-id', 2, 'T-id') +        pass diff --git a/common/src/leap/soledad/common/tests/test_server.py b/common/src/leap/soledad/common/tests/test_server.py index 2b653a1c..a8012e08 100644 --- a/common/src/leap/soledad/common/tests/test_server.py +++ b/common/src/leap/soledad/common/tests/test_server.py @@ -22,6 +22,7 @@ import tempfile  import mock  import time  import binascii +from uuid import uuid4  from urlparse import urljoin  from twisted.internet import defer @@ -93,7 +94,7 @@ class ServerAuthorizationTestCase(BaseSoledadTest):              /user-db/doc/{id}             | -              /user-db/sync-from/{source}   | GET, PUT, POST          """ -        uuid = 'myuuid' +        uuid = uuid4().hex          authmap = URLToAuthorization(uuid,)          dbname = authmap._user_db_name          # test global auth @@ -208,7 +209,7 @@ class ServerAuthorizationTestCase(BaseSoledadTest):          """          Test if authorization fails for a wrong dbname.          """ -        uuid = 'myuuid' +        uuid = uuid4().hex          authmap = URLToAuthorization(uuid)          dbname = 'somedb'          # test wrong-db database resource auth @@ -283,7 +284,7 @@ class EncryptedSyncTestCase(      sync_target = token_soledad_sync_target -    def _soledad_instance(self, user='user-uuid', passphrase=u'123', +    def _soledad_instance(self, user=None, passphrase=u'123',                            prefix='',                            secrets_path='secrets.json',                            local_db_path='soledad.u1db', @@ -336,15 +337,17 @@ class EncryptedSyncTestCase(          TestCaseWithServer.tearDown(self)      def _test_encrypted_sym_sync(self, passphrase=u'123', doc_size=2, -            number_of_docs=1): +                                 number_of_docs=1):          """          Test the complete syncing chain between two soledad dbs using a          Soledad server backed by a couch database.          """          self.startServer() +        user = 'user-' + uuid4().hex          # instantiate soledad and create a document          sol1 = self._soledad_instance( +            user=user,              # token is verified in test_target.make_token_soledad_app              auth_token='auth-token',              passphrase=passphrase) @@ -352,6 +355,7 @@ class EncryptedSyncTestCase(          # instantiate another soledad using the same secret as the previous          # one (so we can correctly verify the mac of the synced document)          sol2 = self._soledad_instance( +            user=user,              prefix='x',              auth_token='auth-token',              secrets_path=sol1._secrets_path, @@ -359,7 +363,7 @@ class EncryptedSyncTestCase(          # ensure remote db exists before syncing          db = CouchDatabase.open_database( -            urljoin(self._couch_url, 'user-user-uuid'), +            urljoin(self._couch_url, 'user-' + user),              create=True,              ensure_ddocs=True) @@ -370,7 +374,7 @@ class EncryptedSyncTestCase(          def _db1CreateDocs(results):              deferreds = []              for i in xrange(number_of_docs): -                content = binascii.hexlify(os.urandom(doc_size/2))   +                content = binascii.hexlify(os.urandom(doc_size/2))                  deferreds.append(sol1.create_doc({'data': content}))              return defer.DeferredList(deferreds) @@ -461,6 +465,7 @@ class EncryptedSyncTestCase(          """          return self._test_encrypted_sym_sync(doc_size=2, number_of_docs=100) +  class LockResourceTestCase(          CouchDBTestCase, TestCaseWithServer):      """ @@ -506,7 +511,8 @@ class LockResourceTestCase(      def test__try_obtain_filesystem_lock(self):          responder = mock.Mock() -        lr = LockResource('uuid', self._state, responder) +        lock_uuid = uuid4().hex +        lr = LockResource(lock_uuid, self._state, responder)          self.assertFalse(lr._lock.locked)          self.assertTrue(lr._try_obtain_filesystem_lock())          self.assertTrue(lr._lock.locked) @@ -514,7 +520,8 @@ class LockResourceTestCase(      def test__try_release_filesystem_lock(self):          responder = mock.Mock() -        lr = LockResource('uuid', self._state, responder) +        lock_uuid = uuid4().hex +        lr = LockResource(lock_uuid, self._state, responder)          lr._try_obtain_filesystem_lock()          self.assertTrue(lr._lock.locked)          lr._try_release_filesystem_lock() @@ -522,11 +529,12 @@ class LockResourceTestCase(      def test_put(self):          responder = mock.Mock() -        lr = LockResource('uuid', self._state, responder) +        lock_uuid = uuid4().hex +        lr = LockResource(lock_uuid, self._state, responder)          # lock!          lr.put({}, None)          # assert lock document was correctly written -        lock_doc = lr._shared_db.get_doc('lock-uuid') +        lock_doc = lr._shared_db.get_doc('lock-' + lock_uuid)          self.assertIsNotNone(lock_doc)          self.assertTrue(LockResource.TIMESTAMP_KEY in lock_doc.content)          self.assertTrue(LockResource.LOCK_TOKEN_KEY in lock_doc.content) @@ -541,20 +549,22 @@ class LockResourceTestCase(      def test_delete(self):          responder = mock.Mock() -        lr = LockResource('uuid', self._state, responder) +        lock_uuid = uuid4().hex +        lr = LockResource(lock_uuid, self._state, responder)          # lock!          lr.put({}, None) -        lock_doc = lr._shared_db.get_doc('lock-uuid') +        lock_doc = lr._shared_db.get_doc('lock-' + lock_uuid)          token = lock_doc.content[LockResource.LOCK_TOKEN_KEY]          # unlock!          lr.delete({'token': token}, None)          self.assertFalse(lr._lock.locked) -        self.assertIsNone(lr._shared_db.get_doc('lock-uuid')) +        self.assertIsNone(lr._shared_db.get_doc('lock-' + lock_uuid))          responder.send_response_json.assert_called_with(200)      def test_put_while_locked_fails(self):          responder = mock.Mock() -        lr = LockResource('uuid', self._state, responder) +        lock_uuid = uuid4().hex +        lr = LockResource(lock_uuid, self._state, responder)          # lock!          lr.put({}, None)          # try to lock again! @@ -572,7 +582,8 @@ class LockResourceTestCase(      def test_unlock_unexisting_lock_fails(self):          responder = mock.Mock() -        lr = LockResource('uuid', self._state, responder) +        lock_uuid = uuid4().hex +        lr = LockResource(lock_uuid, self._state, responder)          # unlock!          lr.delete({'token': 'anything'}, None)          responder.send_response_json.assert_called_with( @@ -580,11 +591,12 @@ class LockResourceTestCase(      def test_unlock_with_wrong_token_fails(self):          responder = mock.Mock() -        lr = LockResource('uuid', self._state, responder) +        lock_uuid = uuid4().hex +        lr = LockResource(lock_uuid, self._state, responder)          # lock!          lr.put({}, None)          # unlock!          lr.delete({'token': 'wrongtoken'}, None) -        self.assertIsNotNone(lr._shared_db.get_doc('lock-uuid')) +        self.assertIsNotNone(lr._shared_db.get_doc('lock-' + lock_uuid))          responder.send_response_json.assert_called_with(              401, error='unlock unauthorized') diff --git a/common/src/leap/soledad/common/tests/u1db_tests/test_https.py b/common/src/leap/soledad/common/tests/u1db_tests/test_https.py index cea175d6..f22ce51e 100644 --- a/common/src/leap/soledad/common/tests/u1db_tests/test_https.py +++ b/common/src/leap/soledad/common/tests/u1db_tests/test_https.py @@ -80,10 +80,10 @@ class TestHttpSyncTargetHttpsSupport(tests.TestCaseWithServer):              soledad.client.api.old__VerifiedHTTPSConnection          super(TestHttpSyncTargetHttpsSupport, self).setUp() -    def getSyncTarget(self, host, path=None): +    def getSyncTarget(self, host, path=None, cert_file=None):          if self.server is None:              self.startServer() -        return self.sync_target(self, host, path) +        return self.sync_target(self, host, path, cert_file=cert_file)      def test_working(self):          self.startServer() diff --git a/common/src/leap/soledad/common/tests/util.py b/common/src/leap/soledad/common/tests/util.py index 17ed3855..60bab81c 100644 --- a/common/src/leap/soledad/common/tests/util.py +++ b/common/src/leap/soledad/common/tests/util.py @@ -50,7 +50,7 @@ from leap.soledad.common.couch import CouchDatabase, CouchServerState  from leap.soledad.common.crypto import ENC_SCHEME_KEY  from leap.soledad.client import Soledad -from leap.soledad.client import target +from leap.soledad.client import http_target  from leap.soledad.client import auth  from leap.soledad.client.crypto import decrypt_doc_dict @@ -102,7 +102,7 @@ def make_token_soledad_app(state):      app = SoledadApp(state)      def _verify_authentication_data(uuid, auth_data): -        if uuid == 'user-uuid' and auth_data == 'auth-token': +        if uuid.startswith('user-') and auth_data == 'auth-token':              return True          return False @@ -165,6 +165,7 @@ class MockedSharedDBTest(object):              lock = Mock(return_value=('atoken', 300))              unlock = Mock(return_value=True)              open = Mock(return_value=None) +            close = Mock(return_value=None)              syncable = True              def __call__(self): @@ -173,7 +174,7 @@ class MockedSharedDBTest(object):  def soledad_sync_target(test, path): -    return target.SoledadSyncTarget( +    return http_target.SoledadSyncTarget(          test.getURL(path), crypto=test._soledad._crypto) diff --git a/server/pkg/requirements.pip b/server/pkg/requirements.pip index df6ad95d..43088222 100644 --- a/server/pkg/requirements.pip +++ b/server/pkg/requirements.pip @@ -3,7 +3,7 @@ couchdb  simplejson  u1db  routes -PyOpenSSL<0.14 +PyOpenSSL  twisted  # leap deps -- bump me! | 
