diff options
| author | drebs <drebs@leap.se> | 2013-08-19 10:13:51 -0300 | 
|---|---|---|
| committer | drebs <drebs@leap.se> | 2013-08-21 14:40:33 -0300 | 
| commit | 11ac38cb021e97cca77df2e9cf15f9136b24fc43 (patch) | |
| tree | 2f4e3e004f57ac3436d8258937bf2145ca3de576 | |
| parent | f424e2fee278bd9f3a9fb838025db7d4c1bb44bb (diff) | |
Split soledad into common, client and server.
| -rw-r--r-- | client/changes/feature_3118-provide-a-way-to-access-the-saved-password (renamed from soledad/changes/feature_3118-provide-a-way-to-access-the-saved-password) | 0 | ||||
| -rw-r--r-- | client/changes/feature_3487-split-soledad-into-common-client-and-server | 1 | ||||
| -rw-r--r-- | client/setup.py (renamed from soledad/setup.py) | 21 | ||||
| -rw-r--r-- | client/src/leap/__init__.py (renamed from soledad/src/leap/__init__.py) | 0 | ||||
| -rw-r--r-- | client/src/leap/soledad/__init__.py (renamed from soledad_server/src/leap/__init__.py) | 0 | ||||
| -rw-r--r-- | client/src/leap/soledad/client/__init__.py (renamed from soledad/src/leap/soledad/__init__.py) | 57 | ||||
| -rw-r--r-- | client/src/leap/soledad/client/auth.py (renamed from soledad/src/leap/soledad/auth.py) | 3 | ||||
| -rw-r--r-- | client/src/leap/soledad/client/crypto.py (renamed from soledad/src/leap/soledad/crypto.py) | 2 | ||||
| -rw-r--r-- | client/src/leap/soledad/client/dbwrapper.py | 183 | ||||
| -rw-r--r-- | client/src/leap/soledad/client/shared_db.py (renamed from soledad/src/leap/soledad/shared_db.py) | 2 | ||||
| -rw-r--r-- | client/src/leap/soledad/client/sqlcipher.py (renamed from soledad/src/leap/soledad/sqlcipher.py) | 11 | ||||
| -rw-r--r-- | client/src/leap/soledad/client/target.py (renamed from soledad/src/leap/soledad/target.py) | 50 | ||||
| -rw-r--r-- | common/changes/feature_3487-split-soledad-into-common-client-and-server | 1 | ||||
| -rw-r--r-- | common/setup.py | 79 | ||||
| -rw-r--r-- | common/src/leap/__init__.py | 6 | ||||
| -rw-r--r-- | common/src/leap/soledad/__init__.py | 6 | ||||
| -rw-r--r-- | common/src/leap/soledad/common/__init__.py | 64 | ||||
| -rw-r--r-- | common/src/leap/soledad/common/couch.py (renamed from soledad_server/src/leap/soledad_server/couch.py) | 3 | ||||
| -rw-r--r-- | common/src/leap/soledad/common/crypto.py | 55 | ||||
| -rw-r--r-- | common/src/leap/soledad/common/document.py (renamed from soledad/src/leap/soledad/document.py) | 0 | ||||
| -rw-r--r-- | common/src/leap/soledad/common/objectstore.py (renamed from soledad_server/src/leap/soledad_server/objectstore.py) | 3 | ||||
| -rw-r--r-- | common/src/leap/soledad/common/tests/__init__.py (renamed from soledad/src/leap/soledad/tests/__init__.py) | 11 | ||||
| -rw-r--r-- | common/src/leap/soledad/common/tests/couchdb.ini.template (renamed from soledad/src/leap/soledad/tests/couchdb.ini.template) | 0 | ||||
| -rw-r--r-- | common/src/leap/soledad/common/tests/test_couch.py (renamed from soledad/src/leap/soledad/tests/test_couch.py) | 10 | ||||
| -rw-r--r-- | common/src/leap/soledad/common/tests/test_crypto.py (renamed from soledad/src/leap/soledad/tests/test_crypto.py) | 9 | ||||
| -rw-r--r-- | common/src/leap/soledad/common/tests/test_server.py (renamed from soledad/src/leap/soledad/tests/test_server.py) | 22 | ||||
| -rw-r--r-- | common/src/leap/soledad/common/tests/test_soledad.py (renamed from soledad/src/leap/soledad/tests/test_soledad.py) | 90 | ||||
| -rw-r--r-- | common/src/leap/soledad/common/tests/test_sqlcipher.py (renamed from soledad/src/leap/soledad/tests/test_sqlcipher.py) | 23 | ||||
| -rw-r--r-- | common/src/leap/soledad/common/tests/test_target.py (renamed from soledad/src/leap/soledad/tests/test_target.py) | 44 | ||||
| -rw-r--r-- | common/src/leap/soledad/common/tests/u1db_tests/README (renamed from soledad/src/leap/soledad/tests/u1db_tests/README) | 0 | ||||
| -rw-r--r-- | common/src/leap/soledad/common/tests/u1db_tests/__init__.py (renamed from soledad/src/leap/soledad/tests/u1db_tests/__init__.py) | 0 | ||||
| -rw-r--r-- | common/src/leap/soledad/common/tests/u1db_tests/test_backends.py (renamed from soledad/src/leap/soledad/tests/u1db_tests/test_backends.py) | 4 | ||||
| -rw-r--r-- | common/src/leap/soledad/common/tests/u1db_tests/test_document.py (renamed from soledad/src/leap/soledad/tests/u1db_tests/test_document.py) | 2 | ||||
| -rw-r--r-- | common/src/leap/soledad/common/tests/u1db_tests/test_http_app.py (renamed from soledad/src/leap/soledad/tests/u1db_tests/test_http_app.py) | 2 | ||||
| -rw-r--r-- | common/src/leap/soledad/common/tests/u1db_tests/test_http_client.py (renamed from soledad/src/leap/soledad/tests/u1db_tests/test_http_client.py) | 2 | ||||
| -rw-r--r-- | common/src/leap/soledad/common/tests/u1db_tests/test_http_database.py (renamed from soledad/src/leap/soledad/tests/u1db_tests/test_http_database.py) | 4 | ||||
| -rw-r--r-- | common/src/leap/soledad/common/tests/u1db_tests/test_https.py (renamed from soledad/src/leap/soledad/tests/u1db_tests/test_https.py) | 6 | ||||
| -rw-r--r-- | common/src/leap/soledad/common/tests/u1db_tests/test_open.py (renamed from soledad/src/leap/soledad/tests/u1db_tests/test_open.py) | 4 | ||||
| -rw-r--r-- | common/src/leap/soledad/common/tests/u1db_tests/test_remote_sync_target.py (renamed from soledad/src/leap/soledad/tests/u1db_tests/test_remote_sync_target.py) | 2 | ||||
| -rw-r--r-- | common/src/leap/soledad/common/tests/u1db_tests/test_sqlite_backend.py (renamed from soledad/src/leap/soledad/tests/u1db_tests/test_sqlite_backend.py) | 4 | ||||
| -rw-r--r-- | common/src/leap/soledad/common/tests/u1db_tests/test_sync.py (renamed from soledad/src/leap/soledad/tests/u1db_tests/test_sync.py) | 4 | ||||
| -rw-r--r-- | common/src/leap/soledad/common/tests/u1db_tests/testing-certs/Makefile (renamed from soledad/src/leap/soledad/tests/u1db_tests/testing-certs/Makefile) | 0 | ||||
| -rw-r--r-- | common/src/leap/soledad/common/tests/u1db_tests/testing-certs/cacert.pem (renamed from soledad/src/leap/soledad/tests/u1db_tests/testing-certs/cacert.pem) | 0 | ||||
| -rw-r--r-- | common/src/leap/soledad/common/tests/u1db_tests/testing-certs/testing.cert (renamed from soledad/src/leap/soledad/tests/u1db_tests/testing-certs/testing.cert) | 0 | ||||
| -rw-r--r-- | common/src/leap/soledad/common/tests/u1db_tests/testing-certs/testing.key (renamed from soledad/src/leap/soledad/tests/u1db_tests/testing-certs/testing.key) | 0 | ||||
| -rw-r--r-- | server/changes/feature_3487-split-soledad-into-common-client-and-server | 1 | ||||
| -rw-r--r-- | server/pkg/soledad (renamed from soledad_server/pkg/soledad) | 0 | ||||
| -rw-r--r-- | server/setup.py (renamed from soledad_server/setup.py) | 9 | ||||
| -rw-r--r-- | server/src/leap/__init__.py | 6 | ||||
| -rw-r--r-- | server/src/leap/soledad/__init__.py | 6 | ||||
| -rw-r--r-- | server/src/leap/soledad/server/__init__.py (renamed from soledad_server/src/leap/soledad_server/__init__.py) | 6 | ||||
| -rw-r--r-- | server/src/leap/soledad/server/auth.py (renamed from soledad_server/src/leap/soledad_server/auth.py) | 0 | ||||
| -rw-r--r-- | soledad/src/leap/soledad/backends/__init__.py | 0 | ||||
| -rw-r--r-- | soledad/src/leap/soledad/backends/leap_backend.py | 73 | 
54 files changed, 583 insertions, 308 deletions
| diff --git a/soledad/changes/feature_3118-provide-a-way-to-access-the-saved-password b/client/changes/feature_3118-provide-a-way-to-access-the-saved-password index 69cb0b1d..69cb0b1d 100644 --- a/soledad/changes/feature_3118-provide-a-way-to-access-the-saved-password +++ b/client/changes/feature_3118-provide-a-way-to-access-the-saved-password diff --git a/client/changes/feature_3487-split-soledad-into-common-client-and-server b/client/changes/feature_3487-split-soledad-into-common-client-and-server new file mode 100644 index 00000000..2eab6b56 --- /dev/null +++ b/client/changes/feature_3487-split-soledad-into-common-client-and-server @@ -0,0 +1 @@ +  o Split soledad package into common, client and server. Closes #3487. diff --git a/soledad/setup.py b/client/setup.py index 67ef3cc8..291c95fe 100644 --- a/soledad/setup.py +++ b/client/setup.py @@ -29,20 +29,10 @@ install_requirements = [      'oauth',  # this is not strictly needed by us, but we need it                # until u1db adds it to its release as a dep.      'u1db', -    'six==1.1.0',      'scrypt',      'pyxdg',      'pycryptopp', -] - - -tests_requirements = [ -    'mock', -    'nose2', -    'testscenarios', -    'leap.common', -    'leap.soledad_server', -    'pyOpenSSL', +    'leap.soledad.common>=0.3.0',  ] @@ -60,8 +50,9 @@ trove_classifiers = (      "Topic :: Software Development :: Libraries :: Python Modules"  ) +  setup( -    name='leap.soledad', +    name='leap.soledad.client',      version='0.3.0',      url='https://leap.se/',      license='GPLv3+', @@ -73,12 +64,10 @@ setup(          "securely shared among devices. It provides, to other parts of the "          "LEAP client, an API for data storage and sync."      ), -    namespace_packages=["leap"], -    packages=find_packages('src'),  # exclude=['leap.soledad.tests']), +    namespace_packages=["leap", "leap.soledad"], +    packages=find_packages('src'),      package_dir={'': 'src'}, -    test_suite='leap.soledad.tests',      install_requires=install_requirements, -    tests_require=tests_requirements,      classifiers=trove_classifiers,      extras_require={'signaling': ['leap.common']},  ) diff --git a/soledad/src/leap/__init__.py b/client/src/leap/__init__.py index f48ad105..f48ad105 100644 --- a/soledad/src/leap/__init__.py +++ b/client/src/leap/__init__.py diff --git a/soledad_server/src/leap/__init__.py b/client/src/leap/soledad/__init__.py index f48ad105..f48ad105 100644 --- a/soledad_server/src/leap/__init__.py +++ b/client/src/leap/soledad/__init__.py diff --git a/soledad/src/leap/soledad/__init__.py b/client/src/leap/soledad/client/__init__.py index fc0c10ce..fc8219fa 100644 --- a/soledad/src/leap/soledad/__init__.py +++ b/client/src/leap/soledad/client/__init__.py @@ -45,49 +45,6 @@ from u1db.remote.ssl_match_hostname import match_hostname  # -# Assert functions -# - -# we want to use leap.common.check.leap_assert in case it is available, -# because it also logs in a way other parts of leap can access log messages. - -try: -    from leap.common.check import leap_assert as soledad_assert - -except ImportError: - -    def soledad_assert(condition, message): -        """ -        Asserts the condition and displays the message if that's not -        met. - -        @param condition: condition to check -        @type condition: bool -        @param message: message to display if the condition isn't met -        @type message: str -        """ -        assert condition, message - -try: -    from leap.common.check import leap_assert_type as soledad_assert_type - -except ImportError: - -    def soledad_assert_type(var, expectedType): -        """ -        Helper assert check for a variable's expected type - -        @param var: variable to check -        @type var: any -        @param expectedType: type to check agains -        @type expectedType: type -        """ -        soledad_assert(isinstance(var, expectedType), -                       "Expected type %r instead of %r" % -                       (expectedType, type(var))) - - -#  # Signaling function  # @@ -124,12 +81,14 @@ except ImportError:          logger.info("Would signal: %s - %s." % (str(signal), content)) -from leap.soledad.crypto import SoledadCrypto -from leap.soledad.document import SoledadDocument -from leap.soledad.shared_db import SoledadSharedDatabase -from leap.soledad.sqlcipher import open as sqlcipher_open -from leap.soledad.sqlcipher import SQLCipherDatabase -from leap.soledad.target import SoledadSyncTarget +from leap.soledad.common import soledad_assert, soledad_assert_type +from leap.soledad.common.document import SoledadDocument +from leap.soledad.client.crypto import SoledadCrypto +from leap.soledad.client.dbwrapper import SQLCipherWrapper +from leap.soledad.client.shared_db import SoledadSharedDatabase +from leap.soledad.client.sqlcipher import open as sqlcipher_open +from leap.soledad.client.sqlcipher import SQLCipherDatabase +from leap.soledad.client.target import SoledadSyncTarget  logger = logging.getLogger(name=__name__) diff --git a/soledad/src/leap/soledad/auth.py b/client/src/leap/soledad/client/auth.py index 81e838d2..3cd6dabe 100644 --- a/soledad/src/leap/soledad/auth.py +++ b/client/src/leap/soledad/client/auth.py @@ -67,4 +67,5 @@ class TokenBasedAuth(object):              return [('Authorization', 'Token %s' % auth.encode('base64')[:-1])]          else:              raise errors.UnknownAuthMethod( -                'Wrong credentials: %s' % self._creds)
\ No newline at end of file +                'Wrong credentials: %s' % self._creds) + diff --git a/soledad/src/leap/soledad/crypto.py b/client/src/leap/soledad/client/crypto.py index 3c1061d5..9fcff8e9 100644 --- a/soledad/src/leap/soledad/crypto.py +++ b/client/src/leap/soledad/client/crypto.py @@ -31,7 +31,7 @@ from pycryptopp.cipher.aes import AES  from pycryptopp.cipher.xsalsa20 import XSalsa20 -from leap.soledad import ( +from leap.soledad.common import (      soledad_assert,      soledad_assert_type,  ) diff --git a/client/src/leap/soledad/client/dbwrapper.py b/client/src/leap/soledad/client/dbwrapper.py new file mode 100644 index 00000000..a27a933e --- /dev/null +++ b/client/src/leap/soledad/client/dbwrapper.py @@ -0,0 +1,183 @@ +# -*- coding: utf-8 -*- +# dbwrapper.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/>. +""" +Thread-safe wrapper for sqlite/pysqlcipher. + +*TODO* +At some point we surely will want to switch to a twisted way of dealing +with this, using defers and proper callbacks. But I had this tested for +some time so postponing that refactor. +""" +import logging +import threading +import Queue +import time + +import exceptions + +from functools import partial + +from leap.soledad.client import sqlcipher + +logger = logging.getLogger(__name__) + + +class SQLCipherWrapper(threading.Thread): + +    def __init__(self, *args, **kwargs): +        """ +        Initializes a wrapper that proxies method and attribute +        access to an underlying SQLCipher instance. We instantiate sqlcipher +        in a thread, and all method accesses communicate with it using a +        Queue. + +        :param *args: position arguments to pass to pysqlcipher initialization +        :type args: tuple + +        :param **kwargs: keyword arguments to pass to pysqlcipher +                         initialization +        :type kwargs: dict +        """ +        threading.Thread.__init__(self) +        self._db = None +        self._wrargs = args, kwargs + +        self._queue = Queue.Queue() +        self._stopped = threading.Event() + +        self.start() + +    def _init_db(self): +        """ +        Initializes sqlcipher database. + +        This is called on a separate thread. +        """ +        # instantiate u1db +        args, kwargs = self._wrargs +        self._db = sqlcipher.open(*args, **kwargs) + +    def run(self): +        """ +        Main loop for the sqlcipher thread. +        """ +        logger.debug("SQLCipherWrapper thread started.") +        logger.debug("Initializing sqlcipher") +        end_mths = ("__end_thread", "_SQLCipherWrapper__end_thread") + +        self._init_db() +        self._lock = threading.Lock() + +        ct = 0 +        started = False + +        while True: +            if self._db is None: +                if started: +                    break +                if ct > 10: +                    break  # XXX DEBUG +                logger.debug('db not ready yet, waiting...') +                time.sleep(1) +                ct += 1 + +            started = True + +            with self._lock: +                try: +                    mth, q, wrargs = self._queue.get() +                except: +                    logger.error("exception getting args from queue") + +                res = None +                attr = getattr(self._db, mth, None) +                if not attr: +                    if mth not in end_mths: +                        logger.error('method %s does not exist' % (mth,)) +                        res = AttributeError( +                            "_db instance has no attribute %s" % mth) + +                elif callable(attr): +                    # invoke the method with the passed args +                    args = wrargs.get('args', []) +                    kwargs = wrargs.get('kwargs', {}) +                    try: +                        res = attr(*args, **kwargs) +                    except Exception as e: +                        logger.error( +                            "Error on proxied method %s: '%r'." % ( +                            attr, e)) +                        res = e +                else: +                    # non-callable attribute +                    res = attr +                logger.debug('returning proxied db call...') +                q.put(res) + +            if mth in end_mths: +                logger.debug('ending thread') +                break + +        logger.debug("SQLCipherWrapper thread terminated.") +        self._stopped.set() + +    def close(self): +        """ +        Closes the sqlcipher database and finishes the thread. This method +        should always be called explicitely. +        """ +        self.__getattr__('close')() +        self.__end_thread() + +    def __getattr__(self, attr): +        """ +        Returns _db proxied attributes. +        """ + +        def __proxied_mth(method, *args, **kwargs): +            if not self._stopped.isSet(): +                wrargs = {'args': args, 'kwargs': kwargs} +                q = Queue.Queue() +                self._queue.put((method, q, wrargs)) +                res = q.get() +                q.task_done() + +                if isinstance(res, exceptions.BaseException): +                    # XXX should get the original bt +                    raise res +                return res +            else: +                logger.warning("tried to call proxied meth " +                               "but stopped is set: %s" % +                               (method,)) + +        rgetattr = object.__getattribute__ + +        if attr != "_db": +            proxied = partial(__proxied_mth, attr) +            return proxied + +        # fallback to regular behavior +        return rgetattr(self, attr) + +    def __del__(self): +        """ +        Do not trust this get called. No guarantees given. Because of a funny +        dance with the refs and the way the gc works, we should be calling the +        close method explicitely. +        """ +        self.close() diff --git a/soledad/src/leap/soledad/shared_db.py b/client/src/leap/soledad/client/shared_db.py index 33c5c484..a6ca504d 100644 --- a/soledad/src/leap/soledad/shared_db.py +++ b/client/src/leap/soledad/client/shared_db.py @@ -26,7 +26,7 @@ import simplejson as json  from u1db.remote import http_database -from leap.soledad.auth import TokenBasedAuth +from leap.soledad.client.auth import TokenBasedAuth  #----------------------------------------------------------------------------- diff --git a/soledad/src/leap/soledad/sqlcipher.py b/client/src/leap/soledad/client/sqlcipher.py index 94d0fb04..c605c28c 100644 --- a/soledad/src/leap/soledad/sqlcipher.py +++ b/client/src/leap/soledad/client/sqlcipher.py @@ -1,4 +1,4 @@ -    # -*- coding: utf-8 -*- +# -*- coding: utf-8 -*-  # sqlcipher.py  # Copyright (C) 2013 LEAP  # @@ -53,7 +53,7 @@ import threading  from u1db.backends import sqlite_backend  from pysqlcipher import dbapi2  from u1db import errors as u1db_errors -from leap.soledad.document import SoledadDocument +from leap.soledad.common.document import SoledadDocument  logger = logging.getLogger(__name__) @@ -328,7 +328,7 @@ class SQLCipherDatabase(sqlite_backend.SQLitePartialExpandDatabase):          @rtype: int          """          from u1db.sync import Synchronizer -        from leap.soledad.target import SoledadSyncTarget +        from leap.soledad.client.target import SoledadSyncTarget          return Synchronizer(              self,              SoledadSyncTarget(url, @@ -339,10 +339,13 @@ class SQLCipherDatabase(sqlite_backend.SQLitePartialExpandDatabase):          """          Add any extra fields, etc to the basic table definitions. +        This method is called by u1db.backends.sqlite_backend._initialize() +        method, which is executed when the database schema is created. Here, +        we use it to include the "syncable" property for LeapDocuments. +          @param c: The cursor for querying the database.          @type c: dbapi2.cursor          """ -        # XXX used??? private method not used in module          c.execute(              'ALTER TABLE document '              'ADD COLUMN syncable BOOL NOT NULL DEFAULT TRUE') diff --git a/soledad/src/leap/soledad/target.py b/client/src/leap/soledad/client/target.py index cad51b74..d0bc3706 100644 --- a/soledad/src/leap/soledad/target.py +++ b/client/src/leap/soledad/client/target.py @@ -32,13 +32,23 @@ from u1db.errors import BrokenSyncStream  from u1db.remote.http_target import HTTPSyncTarget -from leap.soledad import soledad_assert -from leap.soledad.document import SoledadDocument -from leap.soledad.crypto import ( +from leap.soledad.common import soledad_assert +from leap.soledad.common.crypto import ( +    EncryptionSchemes, +    MacMethods, +    ENC_JSON_KEY, +    ENC_SCHEME_KEY, +    ENC_METHOD_KEY, +    ENC_IV_KEY, +    MAC_KEY, +    MAC_METHOD_KEY, +) +from leap.soledad.common.document import SoledadDocument +from leap.soledad.client.auth import TokenBasedAuth +from leap.soledad.client.crypto import (      EncryptionMethods,      UnknownEncryptionMethod,  ) -from leap.soledad.auth import TokenBasedAuth  # @@ -74,38 +84,9 @@ class WrongMac(Exception):  # -# Encryption schemes used for encryption. -# - -class EncryptionSchemes(object): -    """ -    Representation of encryption schemes used to encrypt documents. -    """ - -    NONE = 'none' -    SYMKEY = 'symkey' -    PUBKEY = 'pubkey' - - -class MacMethods(object): -    """ -    Representation of MAC methods used to authenticate document's contents. -    """ - -    HMAC = 'hmac' - - -#  # Crypto utilities for a SoledadDocument.  # -ENC_JSON_KEY = '_enc_json' -ENC_SCHEME_KEY = '_enc_scheme' -ENC_METHOD_KEY = '_enc_method' -ENC_IV_KEY = '_enc_iv' -MAC_KEY = '_mac' -MAC_METHOD_KEY = '_mac_method' -  def mac_doc(crypto, doc_id, doc_rev, ciphertext, mac_method):      """ @@ -367,7 +348,8 @@ class SoledadSyncTarget(HTTPSyncTarget, TokenBasedAuth):                  #-------------------------------------------------------------                  # if arriving content was symmetrically encrypted, we decrypt                  # it. -                doc = SoledadDocument(entry['id'], entry['rev'], entry['content']) +                doc = SoledadDocument( +                    entry['id'], entry['rev'], entry['content'])                  if doc.content and ENC_SCHEME_KEY in doc.content:                      if doc.content[ENC_SCHEME_KEY] == \                              EncryptionSchemes.SYMKEY: diff --git a/common/changes/feature_3487-split-soledad-into-common-client-and-server b/common/changes/feature_3487-split-soledad-into-common-client-and-server new file mode 100644 index 00000000..2eab6b56 --- /dev/null +++ b/common/changes/feature_3487-split-soledad-into-common-client-and-server @@ -0,0 +1 @@ +  o Split soledad package into common, client and server. Closes #3487. diff --git a/common/setup.py b/common/setup.py new file mode 100644 index 00000000..6a432608 --- /dev/null +++ b/common/setup.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- +# setup.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/>. + + +from setuptools import ( +    setup, +    find_packages +) + + +install_requirements = [ +    'simplejson', +    'oauth',  # this is not strictly needed by us, but we need it +              # until u1db adds it to its release as a dep. +    'u1db', +    'six==1.1.0',  # some tests are incompatible with newer versions of six. +] + + +tests_requirements = [ +    'mock', +    'nose2', +    'testscenarios', +    'leap.common', +    'leap.soledad.server', +    'leap.soledad.client', +] + + +trove_classifiers = ( +    "Development Status :: 3 - Alpha", +    "Intended Audience :: Developers", +    "License :: OSI Approved :: " +    "GNU General Public License v3 or later (GPLv3+)", +    "Environment :: Console", +    "Operating System :: OS Independent", +    "Operating System :: POSIX", +    "Programming Language :: Python :: 2.6", +    "Programming Language :: Python :: 2.7", +    "Topic :: Database :: Front-Ends", +    "Topic :: Software Development :: Libraries :: Python Modules" +) + + +setup( +    name='leap.soledad.common', +    version='0.3.0', +    url='https://leap.se/', +    license='GPLv3+', +    description='Synchronization of locally encrypted data among devices.', +    author='The LEAP Encryption Access Project', +    author_email='info@leap.se', +    long_description=( +        "Soledad is the part of LEAP that allows application data to be " +        "securely shared among devices. It provides, to other parts of the " +        "LEAP client, an API for data storage and sync." +    ), +    namespace_packages=["leap", "leap.soledad"], +    packages=find_packages('src', exclude=['leap.soledad.common.tests']), +    package_dir={'': 'src'}, +    test_suite='leap.soledad.common.tests', +    install_requires=install_requirements, +    tests_require=tests_requirements, +    classifiers=trove_classifiers, +) diff --git a/common/src/leap/__init__.py b/common/src/leap/__init__.py new file mode 100644 index 00000000..f48ad105 --- /dev/null +++ b/common/src/leap/__init__.py @@ -0,0 +1,6 @@ +# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages +try: +    __import__('pkg_resources').declare_namespace(__name__) +except ImportError: +    from pkgutil import extend_path +    __path__ = extend_path(__path__, __name__) diff --git a/common/src/leap/soledad/__init__.py b/common/src/leap/soledad/__init__.py new file mode 100644 index 00000000..f48ad105 --- /dev/null +++ b/common/src/leap/soledad/__init__.py @@ -0,0 +1,6 @@ +# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages +try: +    __import__('pkg_resources').declare_namespace(__name__) +except ImportError: +    from pkgutil import extend_path +    __path__ = extend_path(__path__, __name__) diff --git a/common/src/leap/soledad/common/__init__.py b/common/src/leap/soledad/common/__init__.py new file mode 100644 index 00000000..4e45b828 --- /dev/null +++ b/common/src/leap/soledad/common/__init__.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +# __init__.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/>. + + +""" +Soledad routines common to client and server. +""" + + +# +# Assert functions +# + +# we want to use leap.common.check.leap_assert in case it is available, +# because it also logs in a way other parts of leap can access log messages. + +try: +    from leap.common.check import leap_assert as soledad_assert + +except ImportError: + +    def soledad_assert(condition, message): +        """ +        Asserts the condition and displays the message if that's not +        met. + +        @param condition: condition to check +        @type condition: bool +        @param message: message to display if the condition isn't met +        @type message: str +        """ +        assert condition, message + +try: +    from leap.common.check import leap_assert_type as soledad_assert_type + +except ImportError: + +    def soledad_assert_type(var, expectedType): +        """ +        Helper assert check for a variable's expected type + +        @param var: variable to check +        @type var: any +        @param expectedType: type to check agains +        @type expectedType: type +        """ +        soledad_assert(isinstance(var, expectedType), +                       "Expected type %r instead of %r" % +                       (expectedType, type(var))) diff --git a/soledad_server/src/leap/soledad_server/couch.py b/common/src/leap/soledad/common/couch.py index ed5ad6b3..9642e8f3 100644 --- a/soledad_server/src/leap/soledad_server/couch.py +++ b/common/src/leap/soledad/common/couch.py @@ -18,7 +18,6 @@  """A U1DB backend that uses CouchDB as its persistence layer.""" -# general imports  import uuid  import re  import simplejson as json @@ -34,7 +33,7 @@ from couchdb.client import Server, Document as CouchDocument  from couchdb.http import ResourceNotFound -from leap.soledad_server.objectstore import ( +from leap.soledad.common.objectstore import (      ObjectStoreDatabase,      ObjectStoreSyncTarget,  ) diff --git a/common/src/leap/soledad/common/crypto.py b/common/src/leap/soledad/common/crypto.py new file mode 100644 index 00000000..2c6bd7a3 --- /dev/null +++ b/common/src/leap/soledad/common/crypto.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# crypto.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/>. + + +""" +Soledad common crypto bits. +""" + + +# +# Encryption schemes used for encryption. +# + +class EncryptionSchemes(object): +    """ +    Representation of encryption schemes used to encrypt documents. +    """ + +    NONE = 'none' +    SYMKEY = 'symkey' +    PUBKEY = 'pubkey' + + +class MacMethods(object): +    """ +    Representation of MAC methods used to authenticate document's contents. +    """ + +    HMAC = 'hmac' + + +# +# Crypto utilities for a SoledadDocument. +# + +ENC_JSON_KEY = '_enc_json' +ENC_SCHEME_KEY = '_enc_scheme' +ENC_METHOD_KEY = '_enc_method' +ENC_IV_KEY = '_enc_iv' +MAC_KEY = '_mac' +MAC_METHOD_KEY = '_mac_method' diff --git a/soledad/src/leap/soledad/document.py b/common/src/leap/soledad/common/document.py index cc24b53a..cc24b53a 100644 --- a/soledad/src/leap/soledad/document.py +++ b/common/src/leap/soledad/common/document.py diff --git a/soledad_server/src/leap/soledad_server/objectstore.py b/common/src/leap/soledad/common/objectstore.py index 8afac3ec..921cf075 100644 --- a/soledad_server/src/leap/soledad_server/objectstore.py +++ b/common/src/leap/soledad/common/objectstore.py @@ -26,11 +26,12 @@ extended to implement OpenStack or Amazon S3 storage, for example.  See U1DB documentation for more information on how to use databases.  """ + +from u1db import errors  from u1db.backends.inmemory import (      InMemoryDatabase,      InMemorySyncTarget,  ) -from u1db import errors  class ObjectStoreDatabase(InMemoryDatabase): diff --git a/soledad/src/leap/soledad/tests/__init__.py b/common/src/leap/soledad/common/tests/__init__.py index 84882e27..9f47d74a 100644 --- a/soledad/src/leap/soledad/tests/__init__.py +++ b/common/src/leap/soledad/common/tests/__init__.py @@ -9,10 +9,13 @@ import u1db  from mock import Mock -from leap.soledad import Soledad -from leap.soledad.document import SoledadDocument -from leap.soledad.target import decrypt_doc -from leap.soledad.target import ENC_SCHEME_KEY +from leap.soledad.common.document import SoledadDocument +from leap.soledad.client import Soledad +from leap.soledad.client.crypto import SoledadCrypto +from leap.soledad.client.target import ( +    decrypt_doc, +    ENC_SCHEME_KEY, +)  from leap.common.testing.basetest import BaseLeapTest diff --git a/soledad/src/leap/soledad/tests/couchdb.ini.template b/common/src/leap/soledad/common/tests/couchdb.ini.template index 7d0316f0..7d0316f0 100644 --- a/soledad/src/leap/soledad/tests/couchdb.ini.template +++ b/common/src/leap/soledad/common/tests/couchdb.ini.template diff --git a/soledad/src/leap/soledad/tests/test_couch.py b/common/src/leap/soledad/common/tests/test_couch.py index a84bb46c..2fb799b7 100644 --- a/soledad/src/leap/soledad/tests/test_couch.py +++ b/common/src/leap/soledad/common/tests/test_couch.py @@ -27,12 +27,12 @@ from base64 import b64decode  from leap.common.files import mkdir_p -from leap.soledad_server import couch -from leap.soledad.tests import u1db_tests as tests -from leap.soledad.tests.u1db_tests import test_backends -from leap.soledad.tests.u1db_tests import test_sync +from leap.soledad.common.document import SoledadDocument +from leap.soledad.common.tests import u1db_tests as tests +from leap.soledad.common.tests.u1db_tests import test_backends +from leap.soledad.common.tests.u1db_tests import test_sync +from leap.soledad.common import couch  import simplejson as json -from leap.soledad.document import SoledadDocument  #----------------------------------------------------------------------------- diff --git a/soledad/src/leap/soledad/tests/test_crypto.py b/common/src/leap/soledad/common/tests/test_crypto.py index eea67b45..0c7b8254 100644 --- a/soledad/src/leap/soledad/tests/test_crypto.py +++ b/common/src/leap/soledad/common/tests/test_crypto.py @@ -32,19 +32,18 @@ from leap.common.testing.basetest import BaseLeapTest  from Crypto import Random -from leap.common.testing.basetest import BaseLeapTest -from leap.soledad import ( +from leap.soledad.client import (      Soledad,      crypto,      target,  ) -from leap.soledad.document import SoledadDocument -from leap.soledad.tests import ( +from leap.soledad.common.document import SoledadDocument +from leap.soledad.common.tests import (      BaseSoledadTest,      KEY_FINGERPRINT,      PRIVATE_KEY,  ) -from leap.soledad.tests.u1db_tests import ( +from leap.soledad.common.tests.u1db_tests import (      simple_doc,      nested_doc,      TestCaseWithServer, diff --git a/soledad/src/leap/soledad/tests/test_server.py b/common/src/leap/soledad/common/tests/test_server.py index 24cd68dc..beb7e04d 100644 --- a/soledad/src/leap/soledad/tests/test_server.py +++ b/common/src/leap/soledad/common/tests/test_server.py @@ -26,27 +26,27 @@ import simplejson as json  import mock -from leap.soledad import Soledad -from leap.soledad_server import SoledadApp -from leap.soledad_server.auth import URLToAuthorization -from leap.soledad_server.couch import ( +from leap.common.testing.basetest import BaseLeapTest +from leap.soledad.common.couch import (      CouchServerState,      CouchDatabase,  ) -from leap.soledad import target - - -from leap.common.testing.basetest import BaseLeapTest -from leap.soledad.tests.u1db_tests import ( +from leap.soledad.common.tests.u1db_tests import (      TestCaseWithServer,      simple_doc,  ) -from leap.soledad.tests.test_couch import CouchDBTestCase -from leap.soledad.tests.test_target import ( +from leap.soledad.common.tests.test_couch import CouchDBTestCase +from leap.soledad.common.tests.test_target import (      make_token_soledad_app,      make_leap_document_for_test,      token_leap_sync_target,  ) +from leap.soledad.client import ( +    Soledad, +    target, +) +from leap.soledad.server import SoledadApp +from leap.soledad.server.auth import URLToAuthorization  class ServerAuthorizationTestCase(BaseLeapTest): diff --git a/soledad/src/leap/soledad/tests/test_soledad.py b/common/src/leap/soledad/common/tests/test_soledad.py index 531bb572..0b753647 100644 --- a/soledad/src/leap/soledad/tests/test_soledad.py +++ b/common/src/leap/soledad/common/tests/test_soledad.py @@ -27,16 +27,16 @@ from mock import Mock  from pysqlcipher.dbapi2 import DatabaseError  from leap.common.events import events_pb2 as proto -from leap.soledad.tests import ( +from leap.soledad.common.tests import (      BaseSoledadTest,      ADDRESS,  )  from leap import soledad -from leap.soledad import Soledad -from leap.soledad.document import SoledadDocument -from leap.soledad.crypto import SoledadCrypto -from leap.soledad.shared_db import SoledadSharedDatabase -from leap.soledad.target import SoledadSyncTarget +from leap.soledad.common.document import SoledadDocument +from leap.soledad.client import Soledad, PassphraseTooShort +from leap.soledad.client.crypto import SoledadCrypto +from leap.soledad.client.shared_db import SoledadSharedDatabase +from leap.soledad.client.target import SoledadSyncTarget  class AuxMethodsTestCase(BaseSoledadTest): @@ -58,7 +58,7 @@ class AuxMethodsTestCase(BaseSoledadTest):              sol._gen_secret()          sol._load_secrets()          sol._init_db() -        from leap.soledad.sqlcipher import SQLCipherDatabase +        from leap.soledad.client.sqlcipher import SQLCipherDatabase          self.assertIsInstance(sol._db, SQLCipherDatabase)      def test__init_config_defaults(self): @@ -142,7 +142,7 @@ class AuxMethodsTestCase(BaseSoledadTest):              passphrase='123')          # check that soledad complains about new passphrase length          self.assertRaises( -            soledad.PassphraseTooShort, +            PassphraseTooShort,              sol.change_passphrase, '54321')      def test_get_passphrase(self): @@ -203,7 +203,7 @@ class SoledadSignalingTestCase(BaseSoledadTest):      def setUp(self):          BaseSoledadTest.setUp(self)          # mock signaling -        soledad.signal = Mock() +        soledad.client.signal = Mock()      def tearDown(self):          pass @@ -217,54 +217,54 @@ class SoledadSignalingTestCase(BaseSoledadTest):          """          Test that a fresh soledad emits all bootstrap signals.          """ -        soledad.signal.reset_mock() +        soledad.client.signal.reset_mock()          # get a fresh instance so it emits all bootstrap signals          sol = self._soledad_instance(              secrets_path='alternative.json',              local_db_path='alternative.u1db')          # reverse call order so we can verify in the order the signals were          # expected -        soledad.signal.mock_calls.reverse() -        soledad.signal.call_args = \ -            soledad.signal.call_args_list[0] -        soledad.signal.call_args_list.reverse() +        soledad.client.signal.mock_calls.reverse() +        soledad.client.signal.call_args = \ +            soledad.client.signal.call_args_list[0] +        soledad.client.signal.call_args_list.reverse()          # assert signals -        soledad.signal.assert_called_with( +        soledad.client.signal.assert_called_with(              proto.SOLEDAD_DOWNLOADING_KEYS,              ADDRESS,          ) -        self._pop_mock_call(soledad.signal) -        soledad.signal.assert_called_with( +        self._pop_mock_call(soledad.client.signal) +        soledad.client.signal.assert_called_with(              proto.SOLEDAD_DONE_DOWNLOADING_KEYS,              ADDRESS,          ) -        self._pop_mock_call(soledad.signal) -        soledad.signal.assert_called_with( +        self._pop_mock_call(soledad.client.signal) +        soledad.client.signal.assert_called_with(              proto.SOLEDAD_CREATING_KEYS,              ADDRESS,          ) -        self._pop_mock_call(soledad.signal) -        soledad.signal.assert_called_with( +        self._pop_mock_call(soledad.client.signal) +        soledad.client.signal.assert_called_with(              proto.SOLEDAD_DONE_CREATING_KEYS,              ADDRESS,          ) -        self._pop_mock_call(soledad.signal) -        soledad.signal.assert_called_with( +        self._pop_mock_call(soledad.client.signal) +        soledad.client.signal.assert_called_with(              proto.SOLEDAD_DOWNLOADING_KEYS,              ADDRESS,          ) -        self._pop_mock_call(soledad.signal) -        soledad.signal.assert_called_with( +        self._pop_mock_call(soledad.client.signal) +        soledad.client.signal.assert_called_with(              proto.SOLEDAD_DONE_DOWNLOADING_KEYS,              ADDRESS,          ) -        self._pop_mock_call(soledad.signal) -        soledad.signal.assert_called_with( +        self._pop_mock_call(soledad.client.signal) +        soledad.client.signal.assert_called_with(              proto.SOLEDAD_UPLOADING_KEYS,              ADDRESS,          ) -        self._pop_mock_call(soledad.signal) -        soledad.signal.assert_called_with( +        self._pop_mock_call(soledad.client.signal) +        soledad.client.signal.assert_called_with(              proto.SOLEDAD_DONE_UPLOADING_KEYS,              ADDRESS,          ) @@ -273,32 +273,32 @@ class SoledadSignalingTestCase(BaseSoledadTest):          """          Test that an existent soledad emits some of the bootstrap signals.          """ -        soledad.signal.reset_mock() +        soledad.client.signal.reset_mock()          # get an existent instance so it emits only some of bootstrap signals          sol = self._soledad_instance()          # reverse call order so we can verify in the order the signals were          # expected -        soledad.signal.mock_calls.reverse() -        soledad.signal.call_args = \ -            soledad.signal.call_args_list[0] -        soledad.signal.call_args_list.reverse() +        soledad.client.signal.mock_calls.reverse() +        soledad.client.signal.call_args = \ +            soledad.client.signal.call_args_list[0] +        soledad.client.signal.call_args_list.reverse()          # assert signals -        soledad.signal.assert_called_with( +        soledad.client.signal.assert_called_with(              proto.SOLEDAD_DOWNLOADING_KEYS,              ADDRESS,          ) -        self._pop_mock_call(soledad.signal) -        soledad.signal.assert_called_with( +        self._pop_mock_call(soledad.client.signal) +        soledad.client.signal.assert_called_with(              proto.SOLEDAD_DONE_DOWNLOADING_KEYS,              ADDRESS,          ) -        self._pop_mock_call(soledad.signal) -        soledad.signal.assert_called_with( +        self._pop_mock_call(soledad.client.signal) +        soledad.client.signal.assert_called_with(              proto.SOLEDAD_UPLOADING_KEYS,              ADDRESS,          ) -        self._pop_mock_call(soledad.signal) -        soledad.signal.assert_called_with( +        self._pop_mock_call(soledad.client.signal) +        soledad.client.signal.assert_called_with(              proto.SOLEDAD_DONE_UPLOADING_KEYS,              ADDRESS,          ) @@ -307,7 +307,7 @@ class SoledadSignalingTestCase(BaseSoledadTest):          """          Test Soledad emits SOLEDAD_CREATING_KEYS signal.          """ -        soledad.signal.reset_mock() +        soledad.client.signal.reset_mock()          # get a fresh instance so it emits all bootstrap signals          sol = self._soledad_instance()          # mock the actual db sync so soledad does not try to connect to the @@ -316,7 +316,7 @@ class SoledadSignalingTestCase(BaseSoledadTest):          # do the sync          sol.sync()          # assert the signal has been emitted -        soledad.signal.assert_called_with( +        soledad.client.signal.assert_called_with(              proto.SOLEDAD_DONE_DATA_SYNC,              ADDRESS,          ) @@ -325,7 +325,7 @@ class SoledadSignalingTestCase(BaseSoledadTest):          """          Test Soledad emits SOLEDAD_CREATING_KEYS signal.          """ -        soledad.signal.reset_mock() +        soledad.client.signal.reset_mock()          sol = self._soledad_instance()          # mock the sync target          old_get_sync_info = SoledadSyncTarget.get_sync_info @@ -335,7 +335,7 @@ class SoledadSignalingTestCase(BaseSoledadTest):          # check for new data to sync          sol.need_sync('http://provider/userdb')          # assert the signal has been emitted -        soledad.signal.assert_called_with( +        soledad.client.signal.assert_called_with(              proto.SOLEDAD_NEW_DATA_TO_SYNC,              ADDRESS,          ) diff --git a/soledad/src/leap/soledad/tests/test_sqlcipher.py b/common/src/leap/soledad/common/tests/test_sqlcipher.py index 4c4384b8..66a673b6 100644 --- a/soledad/src/leap/soledad/tests/test_sqlcipher.py +++ b/common/src/leap/soledad/common/tests/test_sqlcipher.py @@ -43,27 +43,27 @@ from u1db.backends.sqlite_backend import SQLitePartialExpandDatabase  # soledad stuff. -from leap.soledad.document import SoledadDocument -from leap.soledad.sqlcipher import ( +from leap.soledad.common.document import SoledadDocument +from leap.soledad.client.sqlcipher import (      SQLCipherDatabase,      DatabaseIsNotEncrypted,      open as u1db_open,  ) -from leap.soledad.target import ( +from leap.soledad.common.crypto import (      EncryptionSchemes, -    decrypt_doc,      ENC_JSON_KEY,      ENC_SCHEME_KEY,  ) +from leap.soledad.client.target import decrypt_doc  # u1db tests stuff. -from leap.soledad.tests import u1db_tests as tests, BaseSoledadTest -from leap.soledad.tests.u1db_tests import test_sqlite_backend -from leap.soledad.tests.u1db_tests import test_backends -from leap.soledad.tests.u1db_tests import test_open -from leap.soledad.tests.u1db_tests import test_sync -from leap.soledad.target import SoledadSyncTarget +from leap.soledad.common.tests import u1db_tests as tests, BaseSoledadTest +from leap.soledad.common.tests.u1db_tests import test_sqlite_backend +from leap.soledad.common.tests.u1db_tests import test_backends +from leap.soledad.common.tests.u1db_tests import test_open +from leap.soledad.common.tests.u1db_tests import test_sync +from leap.soledad.client.target import SoledadSyncTarget  from leap.common.testing.basetest import BaseLeapTest  PASSWORD = '123456' @@ -438,7 +438,8 @@ def sync_via_synchronizer_and_leap(test, db_source, db_target,      if trace_hook:          test.skipTest("full trace hook unsupported over http")      path = test._http_at[db_target] -    target = SoledadSyncTarget.connect(test.getURL(path), test._soledad._crypto) +    target = SoledadSyncTarget.connect( +        test.getURL(path), test._soledad._crypto)      target.set_token_credentials('user-uuid', 'auth-token')      if trace_hook_shallow:          target._set_trace_hook_shallow(trace_hook_shallow) diff --git a/soledad/src/leap/soledad/tests/test_target.py b/common/src/leap/soledad/common/tests/test_target.py index ca2878a5..5a541745 100644 --- a/soledad/src/leap/soledad/tests/test_target.py +++ b/common/src/leap/soledad/common/tests/test_target.py @@ -34,25 +34,26 @@ from u1db.remote import (      http_target,  ) -from leap import soledad -from leap.soledad import ( +from leap.soledad import client +from leap.soledad.client import (      target,      auth, +    VerifiedHTTPSConnection,  ) -from leap.soledad.document import SoledadDocument -from leap.soledad_server import SoledadApp -from leap.soledad_server.auth import SoledadTokenAuthMiddleware +from leap.soledad.common.document import SoledadDocument +from leap.soledad.server import SoledadApp +from leap.soledad.server.auth import SoledadTokenAuthMiddleware -from leap.soledad.tests import u1db_tests as tests -from leap.soledad.tests import BaseSoledadTest -from leap.soledad.tests.u1db_tests import test_backends -from leap.soledad.tests.u1db_tests import test_http_database -from leap.soledad.tests.u1db_tests import test_http_client -from leap.soledad.tests.u1db_tests import test_document -from leap.soledad.tests.u1db_tests import test_remote_sync_target -from leap.soledad.tests.u1db_tests import test_https -from leap.soledad.tests.u1db_tests import test_sync +from leap.soledad.common.tests import u1db_tests as tests +from leap.soledad.common.tests import BaseSoledadTest +from leap.soledad.common.tests.u1db_tests import test_backends +from leap.soledad.common.tests.u1db_tests import test_http_database +from leap.soledad.common.tests.u1db_tests import test_http_client +from leap.soledad.common.tests.u1db_tests import test_document +from leap.soledad.common.tests.u1db_tests import test_remote_sync_target +from leap.soledad.common.tests.u1db_tests import test_https +from leap.soledad.common.tests.u1db_tests import test_sync  #----------------------------------------------------------------------------- @@ -77,7 +78,7 @@ def make_token_soledad_app(state):              return True          return False -    # we test for action authorization in leap.soledad.tests.test_server +    # we test for action authorization in leap.soledad.common.tests.test_server      def _verify_authorization(uuid, environ):          return True @@ -504,8 +505,9 @@ def token_leap_https_sync_target(test, host, path):      return st -class TestSoledadSyncTargetHttpsSupport(test_https.TestHttpSyncTargetHttpsSupport, -                                     BaseSoledadTest): +class TestSoledadSyncTargetHttpsSupport( +        test_https.TestHttpSyncTargetHttpsSupport, +        BaseSoledadTest):      scenarios = [          ('token_soledad_https', @@ -520,8 +522,8 @@ class TestSoledadSyncTargetHttpsSupport(test_https.TestHttpSyncTargetHttpsSuppor          # run smoothly with standard u1db.          test_https.TestHttpSyncTargetHttpsSupport.setUp(self)          # so here monkey patch again to test our functionality. -        http_client._VerifiedHTTPSConnection = soledad.VerifiedHTTPSConnection -        soledad.SOLEDAD_CERT = http_client.CA_CERTS +        http_client._VerifiedHTTPSConnection = VerifiedHTTPSConnection +        client.SOLEDAD_CERT = http_client.CA_CERTS      def test_working(self):          """ @@ -532,7 +534,7 @@ class TestSoledadSyncTargetHttpsSupport(test_https.TestHttpSyncTargetHttpsSuppor          """          self.startServer()          db = self.request_state._create_database('test') -        self.patch(soledad, 'SOLEDAD_CERT', self.cacert_pem) +        self.patch(client, 'SOLEDAD_CERT', self.cacert_pem)          remote_target = self.getSyncTarget('localhost', 'test')          remote_target.record_sync_info('other-id', 2, 'T-id')          self.assertEqual( @@ -548,7 +550,7 @@ class TestSoledadSyncTargetHttpsSupport(test_https.TestHttpSyncTargetHttpsSuppor          """          self.startServer()          self.request_state._create_database('test') -        self.patch(soledad, 'SOLEDAD_CERT', self.cacert_pem) +        self.patch(client, 'SOLEDAD_CERT', self.cacert_pem)          remote_target = self.getSyncTarget('127.0.0.1', 'test')          self.assertRaises(              http_client.CertificateError, remote_target.record_sync_info, diff --git a/soledad/src/leap/soledad/tests/u1db_tests/README b/common/src/leap/soledad/common/tests/u1db_tests/README index 605f01fa..605f01fa 100644 --- a/soledad/src/leap/soledad/tests/u1db_tests/README +++ b/common/src/leap/soledad/common/tests/u1db_tests/README diff --git a/soledad/src/leap/soledad/tests/u1db_tests/__init__.py b/common/src/leap/soledad/common/tests/u1db_tests/__init__.py index 3bc12487..3bc12487 100644 --- a/soledad/src/leap/soledad/tests/u1db_tests/__init__.py +++ b/common/src/leap/soledad/common/tests/u1db_tests/__init__.py diff --git a/soledad/src/leap/soledad/tests/u1db_tests/test_backends.py b/common/src/leap/soledad/common/tests/u1db_tests/test_backends.py index a53b01ba..d2a91d11 100644 --- a/soledad/src/leap/soledad/tests/u1db_tests/test_backends.py +++ b/common/src/leap/soledad/common/tests/u1db_tests/test_backends.py @@ -26,12 +26,12 @@ from u1db import (      vectorclock,  ) -from leap.soledad.tests import u1db_tests as tests +from leap.soledad.common.tests import u1db_tests as tests  simple_doc = tests.simple_doc  nested_doc = tests.nested_doc -from leap.soledad.tests.u1db_tests.test_remote_sync_target import ( +from leap.soledad.common.tests.u1db_tests.test_remote_sync_target import (      make_http_app,      make_oauth_http_app,  ) diff --git a/soledad/src/leap/soledad/tests/u1db_tests/test_document.py b/common/src/leap/soledad/common/tests/u1db_tests/test_document.py index e706e1a9..8b30ed51 100644 --- a/soledad/src/leap/soledad/tests/u1db_tests/test_document.py +++ b/common/src/leap/soledad/common/tests/u1db_tests/test_document.py @@ -17,7 +17,7 @@  from u1db import errors -from leap.soledad.tests import u1db_tests as tests +from leap.soledad.common.tests import u1db_tests as tests  class TestDocument(tests.TestCase): diff --git a/soledad/src/leap/soledad/tests/u1db_tests/test_http_app.py b/common/src/leap/soledad/common/tests/u1db_tests/test_http_app.py index e0729aa2..789006ba 100644 --- a/soledad/src/leap/soledad/tests/u1db_tests/test_http_app.py +++ b/common/src/leap/soledad/common/tests/u1db_tests/test_http_app.py @@ -30,7 +30,7 @@ from u1db import (      sync,  ) -from leap.soledad.tests import u1db_tests as tests +from leap.soledad.common.tests import u1db_tests as tests  from u1db.remote import (      http_app, diff --git a/soledad/src/leap/soledad/tests/u1db_tests/test_http_client.py b/common/src/leap/soledad/common/tests/u1db_tests/test_http_client.py index 42e98461..08e9714e 100644 --- a/soledad/src/leap/soledad/tests/u1db_tests/test_http_client.py +++ b/common/src/leap/soledad/common/tests/u1db_tests/test_http_client.py @@ -26,7 +26,7 @@ from u1db import (      errors,  ) -from leap.soledad.tests import u1db_tests as tests +from leap.soledad.common.tests import u1db_tests as tests  from u1db.remote import (      http_client, diff --git a/soledad/src/leap/soledad/tests/u1db_tests/test_http_database.py b/common/src/leap/soledad/common/tests/u1db_tests/test_http_database.py index f21e6da1..3f3c7bba 100644 --- a/soledad/src/leap/soledad/tests/u1db_tests/test_http_database.py +++ b/common/src/leap/soledad/common/tests/u1db_tests/test_http_database.py @@ -27,13 +27,13 @@ from u1db import (      Document,  ) -from leap.soledad.tests import u1db_tests as tests +from leap.soledad.common.tests import u1db_tests as tests  from u1db.remote import (      http_database,      http_target,  ) -from leap.soledad.tests.u1db_tests.test_remote_sync_target import ( +from leap.soledad.common.tests.u1db_tests.test_remote_sync_target import (      make_http_app,  ) diff --git a/soledad/src/leap/soledad/tests/u1db_tests/test_https.py b/common/src/leap/soledad/common/tests/u1db_tests/test_https.py index 62180f8c..c086fbc0 100644 --- a/soledad/src/leap/soledad/tests/u1db_tests/test_https.py +++ b/common/src/leap/soledad/common/tests/u1db_tests/test_https.py @@ -12,8 +12,8 @@ from u1db.remote import (  )  from leap import soledad -from leap.soledad.tests import u1db_tests as tests -from leap.soledad.tests.u1db_tests.test_remote_sync_target import ( +from leap.soledad.common.tests import u1db_tests as tests +from leap.soledad.common.tests.u1db_tests.test_remote_sync_target import (      make_oauth_http_app,  ) @@ -75,7 +75,7 @@ class TestHttpSyncTargetHttpsSupport(tests.TestCaseWithServer):          # order to maintain the compatibility with u1db default tests, we undo          # that replacement here.          http_client._VerifiedHTTPSConnection = \ -            soledad.old__VerifiedHTTPSConnection +            soledad.client.old__VerifiedHTTPSConnection          super(TestHttpSyncTargetHttpsSupport, self).setUp()      def getSyncTarget(self, host, path=None): diff --git a/soledad/src/leap/soledad/tests/u1db_tests/test_open.py b/common/src/leap/soledad/common/tests/u1db_tests/test_open.py index 0ff307e8..13425b4f 100644 --- a/soledad/src/leap/soledad/tests/u1db_tests/test_open.py +++ b/common/src/leap/soledad/common/tests/u1db_tests/test_open.py @@ -22,9 +22,9 @@ from u1db import (      errors,      open as u1db_open,  ) -from leap.soledad.tests import u1db_tests as tests +from leap.soledad.common.tests import u1db_tests as tests  from u1db.backends import sqlite_backend -from leap.soledad.tests.u1db_tests.test_backends import TestAlternativeDocument +from leap.soledad.common.tests.u1db_tests.test_backends import TestAlternativeDocument  class TestU1DBOpen(tests.TestCase): diff --git a/soledad/src/leap/soledad/tests/u1db_tests/test_remote_sync_target.py b/common/src/leap/soledad/common/tests/u1db_tests/test_remote_sync_target.py index 66d404d2..3793e0df 100644 --- a/soledad/src/leap/soledad/tests/u1db_tests/test_remote_sync_target.py +++ b/common/src/leap/soledad/common/tests/u1db_tests/test_remote_sync_target.py @@ -22,7 +22,7 @@ from u1db import (      errors,  ) -from leap.soledad.tests import u1db_tests as tests +from leap.soledad.common.tests import u1db_tests as tests  from u1db.remote import (      http_app, diff --git a/soledad/src/leap/soledad/tests/u1db_tests/test_sqlite_backend.py b/common/src/leap/soledad/common/tests/u1db_tests/test_sqlite_backend.py index 1380e4b1..a53ea6cc 100644 --- a/soledad/src/leap/soledad/tests/u1db_tests/test_sqlite_backend.py +++ b/common/src/leap/soledad/common/tests/u1db_tests/test_sqlite_backend.py @@ -27,10 +27,10 @@ from u1db import (      query_parser,  ) -from leap.soledad.tests import u1db_tests as tests +from leap.soledad.common.tests import u1db_tests as tests  from u1db.backends import sqlite_backend -from leap.soledad.tests.u1db_tests.test_backends import TestAlternativeDocument +from leap.soledad.common.tests.u1db_tests.test_backends import TestAlternativeDocument  simple_doc = '{"key": "value"}' diff --git a/soledad/src/leap/soledad/tests/u1db_tests/test_sync.py b/common/src/leap/soledad/common/tests/u1db_tests/test_sync.py index a87c1787..5346d540 100644 --- a/soledad/src/leap/soledad/tests/u1db_tests/test_sync.py +++ b/common/src/leap/soledad/common/tests/u1db_tests/test_sync.py @@ -26,7 +26,7 @@ from u1db import (      SyncTarget,  ) -from leap.soledad.tests import u1db_tests as tests +from leap.soledad.common.tests import u1db_tests as tests  from u1db.backends import (      inmemory, @@ -35,7 +35,7 @@ from u1db.remote import (      http_target,  ) -from leap.soledad.tests.u1db_tests.test_remote_sync_target import ( +from leap.soledad.common.tests.u1db_tests.test_remote_sync_target import (      make_http_app,      make_oauth_http_app,  ) diff --git a/soledad/src/leap/soledad/tests/u1db_tests/testing-certs/Makefile b/common/src/leap/soledad/common/tests/u1db_tests/testing-certs/Makefile index 2385e75b..2385e75b 100644 --- a/soledad/src/leap/soledad/tests/u1db_tests/testing-certs/Makefile +++ b/common/src/leap/soledad/common/tests/u1db_tests/testing-certs/Makefile diff --git a/soledad/src/leap/soledad/tests/u1db_tests/testing-certs/cacert.pem b/common/src/leap/soledad/common/tests/u1db_tests/testing-certs/cacert.pem index c019a730..c019a730 100644 --- a/soledad/src/leap/soledad/tests/u1db_tests/testing-certs/cacert.pem +++ b/common/src/leap/soledad/common/tests/u1db_tests/testing-certs/cacert.pem diff --git a/soledad/src/leap/soledad/tests/u1db_tests/testing-certs/testing.cert b/common/src/leap/soledad/common/tests/u1db_tests/testing-certs/testing.cert index 985684fb..985684fb 100644 --- a/soledad/src/leap/soledad/tests/u1db_tests/testing-certs/testing.cert +++ b/common/src/leap/soledad/common/tests/u1db_tests/testing-certs/testing.cert diff --git a/soledad/src/leap/soledad/tests/u1db_tests/testing-certs/testing.key b/common/src/leap/soledad/common/tests/u1db_tests/testing-certs/testing.key index d83d4920..d83d4920 100644 --- a/soledad/src/leap/soledad/tests/u1db_tests/testing-certs/testing.key +++ b/common/src/leap/soledad/common/tests/u1db_tests/testing-certs/testing.key diff --git a/server/changes/feature_3487-split-soledad-into-common-client-and-server b/server/changes/feature_3487-split-soledad-into-common-client-and-server new file mode 100644 index 00000000..2eab6b56 --- /dev/null +++ b/server/changes/feature_3487-split-soledad-into-common-client-and-server @@ -0,0 +1 @@ +  o Split soledad package into common, client and server. Closes #3487. diff --git a/soledad_server/pkg/soledad b/server/pkg/soledad index c233731e..c233731e 100644 --- a/soledad_server/pkg/soledad +++ b/server/pkg/soledad diff --git a/soledad_server/setup.py b/server/setup.py index 6229cf62..89354a57 100644 --- a/soledad_server/setup.py +++ b/server/setup.py @@ -31,10 +31,9 @@ install_requirements = [      'oauth',  # this is not strictly needed by us, but we need it                # until u1db adds it to its release as a dep.      'u1db', -    'six==1.1.0',      'routes',      'PyOpenSSL', -    'leap.soledad>=0.3.0', +    'leap.soledad.common>=0.3.0',  ] @@ -44,6 +43,7 @@ else:      # XXX this should go only for linux/mac      data_files = [("/etc/init.d/", ["pkg/soledad"])] +  trove_classifiers = (      "Development Status :: 3 - Alpha",      "Intended Audience :: Developers", @@ -58,8 +58,9 @@ trove_classifiers = (      "Topic :: Software Development :: Libraries :: Python Modules"  ) +  setup( -    name='leap.soledad_server', +    name='leap.soledad.server',      version='0.3.0',      url='https://leap.se/',      license='GPLv3+', @@ -71,7 +72,7 @@ setup(          "securely shared among devices. It provides, to other parts of the "          "LEAP client, an API for data storage and sync."      ), -    namespace_packages=["leap"], +    namespace_packages=["leap", "leap.soledad"],      packages=find_packages('src'),      package_dir={'': 'src'},      install_requires=install_requirements, diff --git a/server/src/leap/__init__.py b/server/src/leap/__init__.py new file mode 100644 index 00000000..f48ad105 --- /dev/null +++ b/server/src/leap/__init__.py @@ -0,0 +1,6 @@ +# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages +try: +    __import__('pkg_resources').declare_namespace(__name__) +except ImportError: +    from pkgutil import extend_path +    __path__ = extend_path(__path__, __name__) diff --git a/server/src/leap/soledad/__init__.py b/server/src/leap/soledad/__init__.py new file mode 100644 index 00000000..f48ad105 --- /dev/null +++ b/server/src/leap/soledad/__init__.py @@ -0,0 +1,6 @@ +# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages +try: +    __import__('pkg_resources').declare_namespace(__name__) +except ImportError: +    from pkgutil import extend_path +    __path__ = extend_path(__path__, __name__) diff --git a/soledad_server/src/leap/soledad_server/__init__.py b/server/src/leap/soledad/server/__init__.py index 18a0546e..4ed8efc9 100644 --- a/soledad_server/src/leap/soledad_server/__init__.py +++ b/server/src/leap/soledad/server/__init__.py @@ -43,8 +43,8 @@ if version.base() == "12.0.0":      sys.modules['OpenSSL.tsafe'] = old_tsafe -from leap.soledad_server.auth import SoledadTokenAuthMiddleware -from leap.soledad_server.couch import CouchServerState +from leap.soledad.server.auth import SoledadTokenAuthMiddleware +from leap.soledad.common.couch import CouchServerState  #----------------------------------------------------------------------------- @@ -122,4 +122,4 @@ state = CouchServerState(conf['couch_url'])  # WSGI application that may be used by `twistd -web`  application = SoledadTokenAuthMiddleware(SoledadApp(state)) -resource = WSGIResource(reactor, reactor.getThreadPool(), application)
\ No newline at end of file +resource = WSGIResource(reactor, reactor.getThreadPool(), application) diff --git a/soledad_server/src/leap/soledad_server/auth.py b/server/src/leap/soledad/server/auth.py index 3bcfcf04..3bcfcf04 100644 --- a/soledad_server/src/leap/soledad_server/auth.py +++ b/server/src/leap/soledad/server/auth.py diff --git a/soledad/src/leap/soledad/backends/__init__.py b/soledad/src/leap/soledad/backends/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/soledad/src/leap/soledad/backends/__init__.py +++ /dev/null diff --git a/soledad/src/leap/soledad/backends/leap_backend.py b/soledad/src/leap/soledad/backends/leap_backend.py deleted file mode 100644 index a8d76b67..00000000 --- a/soledad/src/leap/soledad/backends/leap_backend.py +++ /dev/null @@ -1,73 +0,0 @@ -# -*- coding: utf-8 -*- -# leap_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/>. - - -""" -This file exists to provide backwards compatibility with code that uses -Soledad before the refactor that removed the leap_backend module. -""" - - -import logging -import warnings - - -from leap.soledad import document -from leap.soledad import target - - -logger = logging.getLogger(name=__name__) - - -def warn(oldclass, newclass): -    """ -    Warns about deprecation of C{oldclass}, which must be substituted by -    C{newclass}. - -    @param oldclass: The class that is deprecated. -    @type oldclass: type -    @param newclass: The class that should be used instead. -    @type newclass: type -    """ -    message = \ -        "%s is deprecated and will be removed soon. Please use %s instead." \ -        % (str(oldclass), str(newclass)) -    print message -    logger.warning(message) -    warnings.warn(message, DeprecationWarning, stacklevel=2) - - -class LeapDocument(document.SoledadDocument): -    """ -    This class exists to provide backwards compatibility with code that still -    uses C{leap.soledad.backends.leap_backend.LeapDocument}. -    """ - -    def __init__(self, *args, **kwargs): -        warn(self.__class__, document.SoledadDocument) -        document.SoledadDocument.__init__(self, *args, **kwargs) - - -class EncryptionSchemes(target.EncryptionSchemes): -    """ -    This class exists to provide backwards compatibility with code that still -    uses C{leap.soledad.backends.leap_backend.EncryptionSchemes}. -    """ - -    def __init__(self, *args, **kwargs): -        warn(self.__class__, target.EncryptionSchemes) -        target.EncryptionSchemes.__init__(self, *args, **kwargs) | 
