diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/leap/soledad/__init__.py | 167 | ||||
| -rw-r--r-- | src/leap/soledad/config.py | 97 | ||||
| -rw-r--r-- | src/leap/soledad/tests/__init__.py | 1 | ||||
| -rw-r--r-- | src/leap/soledad/tests/test_soledad.py | 76 | 
4 files changed, 104 insertions, 237 deletions
diff --git a/src/leap/soledad/__init__.py b/src/leap/soledad/__init__.py index 76b5656e..94ebec1a 100644 --- a/src/leap/soledad/__init__.py +++ b/src/leap/soledad/__init__.py @@ -36,12 +36,12 @@ except ImportError:      import json  # noqa +from xdg import BaseDirectory  from hashlib import sha256  from leap.common import events  from leap.common.check import leap_assert -from leap.soledad.config import SoledadConfig  from leap.soledad.backends import sqlcipher  from leap.soledad.backends.leap_backend import (      LeapDocument, @@ -77,7 +77,7 @@ class NotADirectory(Exception):      """ -class NoSharedDbUrl(Exception): +class NoServerUrl(Exception):      """      Tried to get access to shared recovery database but there's no URL for it.      """ @@ -131,9 +131,16 @@ class Soledad(object):      Key used to access symmetric keys in recovery documents.      """ -    def __init__(self, address, passphrase, config_path=None, -                 secret_path=None, local_db_path=None, -                 shared_db_url=None, auth_token=None, bootstrap=True): +    DEFAULT_PREFIX = os.path.join( +        BaseDirectory.xdg_config_home, +        'leap', 'soledad') +    """ +    Prefix for default values for path. +    """ + +    def __init__(self, address, passphrase, secret_path=None, +                 local_db_path=None, server_url=None, auth_token=None, +                 bootstrap=True):          """          Initialize configuration, cryptographic keys and dbs. @@ -142,16 +149,15 @@ class Soledad(object):          @param passphrase: The passphrase for locking and unlocking encryption              secrets for disk storage.          @type passphrase: str -        @param config_path: Path for configuration file. -        @type config_path: str          @param secret_path: Path for storing encrypted key used for              symmetric encryption.          @type secret_path: str          @param local_db_path: Path for local encrypted storage db.          @type local_db_path: str -        @param shared_db_url: URL for shared Soledad DB for key storage and -            unauth retrieval. -        @type shared_db_url: str +        @param server_url: URL for Soledad server. This is used either to sync +            with the user's remote db and to interact with the shared recovery +            database. +        @type server_url: str          @param auth_token: Authorization token for accessing remote databases.          @type auth_token: str          @param bootstrap: True/False, should bootstrap this instance? Mostly @@ -162,15 +168,33 @@ class Soledad(object):          self._address = address          self._passphrase = passphrase          self._set_token(auth_token) -        self._init_config( -            config_path=config_path, -            secret_path=secret_path, -            local_db_path=local_db_path, -            shared_db_url=shared_db_url, -        ) +        self._init_config(secret_path, local_db_path, server_url)          if bootstrap:              self._bootstrap() +    def _init_config(self, secret_path, local_db_path, server_url): +        """ +        Initialize configuration using default values for missing params. +        """ +        # initialize secret_path +        self._secret_path = secret_path +        if self._secret_path is None: +            self._secret_path = os.path.join( +                self.DEFAULT_PREFIX, 'secret.gpg') +        # initialize local_db_path +        self._local_db_path = local_db_path +        if self._local_db_path is None: +            self._local_db_path = os.path.join( +                self.DEFAULT_PREFIX, 'soledad.u1db') +        # initialize server_url +        self._server_url = server_url +        if self._server_url is None: +            raise NoServerUrl() + +    # +    # initialization methods +    # +      def _bootstrap(self):          """          Bootstrap local Soledad instance. @@ -225,53 +249,13 @@ class Soledad(object):          # Stage 3 - Local database initialization          self._init_db() -    def _shared_db(self): -        """ -        Return an instance of the shared recovery database object. -        """ -        if self._config.get_shared_db_url(): -            return SoledadSharedDatabase.open_database( -                self._config.get_shared_db_url(), -                False,  # TODO: eliminate need to create db here. -                creds=self._creds) -        else: -            raise NoSharedDbUrl() - -    def _init_config(self, config_path, secret_path, local_db_path, -                     shared_db_url): -        """ -        Initialize configuration using SoledadConfig. - -        Soledad configuration makes use of BaseLeapConfig to load values from -        a file or from default configuration. Parameters passed as arguments -        for this method will supersede file and default values. - -        @param kwargs: a dictionary with configuration parameter values passed -            when instantiating this Soledad instance. -        @type kwargs: dict -        """ -        self._config = SoledadConfig() -        if config_path is not None: -            self._config.load(path=config_path) -        else: -            self._config.load(data='') -        # overwrite config with passed parameters -        if secret_path is not None: -            self._config._config_checker.config['secret_path'] = secret_path -        if local_db_path is not None: -            self._config._config_checker.config['local_db_path'] = \ -                local_db_path -        if shared_db_url is not None: -            self._config._config_checker.config['shared_db_url'] = \ -                shared_db_url -      def _init_dirs(self):          """          Create work directories.          """          paths = map(              lambda x: os.path.dirname(x), -            [self._config.get_local_db_path(), self._config.get_secret_path()]) +            [self.local_db_path, self.secret_path])          for path in paths:              if not os.path.isfile(path):                  if not os.path.isdir(path): @@ -302,7 +286,7 @@ class Soledad(object):          # TODO: verify if secret for sqlcipher should be the same as the          # one for symmetric encryption.          self._db = sqlcipher.open( -            self._config.get_local_db_path(), +            self.local_db_path,              self._symkey,              create=True,              document_factory=LeapDocument, @@ -314,9 +298,9 @@ class Soledad(object):          """          self._db.close() -    #------------------------------------------------------------------------- -    # Management of secret for symmetric encryption -    #------------------------------------------------------------------------- +    # +    # Management of secret for symmetric encryption. +    #      def _has_symkey(self):          """ @@ -328,14 +312,14 @@ class Soledad(object):          @rtype: bool          """          # does the file exist in disk? -        if not os.path.isfile(self._config.get_secret_path()): +        if not os.path.isfile(self.secret_path):              return False          # is it symmetrically encrypted? -        with open(self._config.get_secret_path(), 'r') as f: +        with open(self.secret_path, 'r') as f:              content = f.read()          if not self._crypto.is_encrypted_sym(content):              raise DocumentNotEncrypted( -                "File %s is not encrypted!" % self._config.get_secret_path()) +                "File %s is not encrypted!" % self.secret_path)          # can we decrypt it?          plaintext = self._crypto.decrypt_sym(              content, passphrase=self._passphrase) @@ -348,7 +332,7 @@ class Soledad(object):          if not self._has_symkey():              raise KeyDoesNotExist("Tried to load key for symmetric "                                    "encryption but it does not exist on disk.") -        with open(self._config.get_secret_path()) as f: +        with open(self.secret_path) as f:              self._symkey = \                  self._crypto.decrypt_sym(                      f.read(), passphrase=self._passphrase) @@ -380,12 +364,12 @@ class Soledad(object):      def _store_symkey(self):          ciphertext = self._crypto.encrypt_sym(              self._symkey, self._passphrase) -        with open(self._config.get_secret_path(), 'w') as f: +        with open(self.secret_path, 'w') as f:              f.write(ciphertext) -    #------------------------------------------------------------------------- +    #      # General crypto utility methods. -    #------------------------------------------------------------------------- +    #      def _has_keys(self):          """ @@ -419,6 +403,15 @@ class Soledad(object):          """          return sha256('address-%s' % self._address).hexdigest() +    def _shared_db(self): +        """ +        Return an instance of the shared recovery database object. +        """ +        return SoledadSharedDatabase.open_database( +            self.server_url, +            False,  # TODO: eliminate need to create db here. +            creds=self._creds) +      def _fetch_keys_from_shared_db(self):          """          Retrieve the document with encrypted key material from the shared @@ -451,7 +444,7 @@ class Soledad(object):          if doc:              remote_symkey = self._crypto.decrypt_sym(                  doc.content[self.SYMKEY_KEY], -                passphrase=self._address_hash()) +                passphrase=self._passphrase)              leap_assert(                  remote_symkey == self._symkey,                  'Local and remote symmetric secrets differ!') @@ -468,9 +461,9 @@ class Soledad(object):              events.signal(                  events.events_pb2.SOLEDAD_DONE_UPLOADING_KEYS, self._address) -    #------------------------------------------------------------------------- -    # Document storage, retrieval and sync -    #------------------------------------------------------------------------- +    # +    # Document storage, retrieval and sync. +    #      # TODO: refactor the following methods to somewhere out of here      # (SoledadLocalDatabase, maybe?) @@ -708,7 +701,7 @@ class Soledad(object):          """          return self._db.resolve_doc(doc, conflicted_doc_revs) -    def sync(self, url): +    def sync(self):          """          Synchronize the local encrypted replica with a remote replica. @@ -719,7 +712,7 @@ class Soledad(object):              performed.          @rtype: str          """ -        local_gen = self._db.sync(url, creds=self._creds, autocreate=True) +        local_gen = self._db.sync(self.server_url, creds=self._creds, autocreate=True)          events.signal(events.events_pb2.SOLEDAD_DONE_DATA_SYNC, self._address)          return local_gen @@ -772,9 +765,9 @@ class Soledad(object):      token = property(_get_token, _set_token, doc='The authentication Token.') -    #------------------------------------------------------------------------- -    # Recovery document export and import -    #------------------------------------------------------------------------- +    # +    # Recovery document export and import methods. +    #      def export_recovery_document(self, passphrase=None):          """ @@ -845,5 +838,23 @@ class Soledad(object):      address = property(_get_address, doc='The user address.') +    def _get_secret_path(self): +        return self._secret_path + +    secret_path = property( +        _get_secret_path, +        doc='The path for the file containing the encrypted symmetric secret.') + +    def _get_local_db_path(self): +        return self._local_db_path + +    local_db_path = property( +        _get_local_db_path, +        doc='The path for the local database replica.') + +    def _get_server_url(self): +        return self._server_url -__all__ = ['backends', 'util', 'server', 'shared_db'] +    server_url = property( +        _get_server_url, +        doc='The URL of the Soledad server.') diff --git a/src/leap/soledad/config.py b/src/leap/soledad/config.py deleted file mode 100644 index 733ad9e7..00000000 --- a/src/leap/soledad/config.py +++ /dev/null @@ -1,97 +0,0 @@ -# -*- coding: utf-8 -*- -# config.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/>. - - -""" -Management of configuration sources for Soledad. -""" - -import os -import logging - - -from xdg import BaseDirectory -from leap.common.config.baseconfig import BaseConfig - - -logger = logging.getLogger(name=__name__) - - -PREFIX = os.path.join( -    BaseDirectory.xdg_config_home, -    'leap', 'soledad') - - -soledad_config_spec = { -    'description': 'sample soledad config', -    'type': 'object', -    'properties': { -        'secret_path': { -            'type': unicode, -            'default': PREFIX + '/secret.gpg', -            'required': True, -        }, -        'local_db_path': { -            #'type': unicode, -            'default': PREFIX + '/soledad.u1db', -            'required': True, -        }, -        'shared_db_url': { -            'type': unicode, -            'default': 'http://provider/soledad/shared', -            'required': True,  # should this be True? -        }, -    } -} - - -class SoledadConfig(BaseConfig): - -    def _get_spec(self): -        """ -        Returns the spec object for the specific configuration -        """ -        return soledad_config_spec - -    def get_secret_path(self): -        return self._safe_get_value("secret_path") - -    def get_local_db_path(self): -        return self._safe_get_value("local_db_path") - -    def get_shared_db_url(self): -        return self._safe_get_value("shared_db_url") - - -if __name__ == "__main__": -    logger = logging.getLogger(name='leap') -    logger.setLevel(logging.DEBUG) -    console = logging.StreamHandler() -    console.setLevel(logging.DEBUG) -    formatter = logging.Formatter( -        '%(asctime)s ' -        '- %(name)s - %(levelname)s - %(message)s') -    console.setFormatter(formatter) -    logger.addHandler(console) - -    soledadconfig = SoledadConfig() - -    try: -        soledadconfig.get_local_db_path() -    except Exception as e: -        assert isinstance(e, AssertionError), "Expected an assert" -        print "Safe value getting is working" diff --git a/src/leap/soledad/tests/__init__.py b/src/leap/soledad/tests/__init__.py index a30193d3..149228e5 100644 --- a/src/leap/soledad/tests/__init__.py +++ b/src/leap/soledad/tests/__init__.py @@ -54,6 +54,7 @@ class BaseSoledadTest(BaseLeapTest):              '123',              secret_path=self.tempdir+prefix+secret_path,              local_db_path=self.tempdir+prefix+local_db_path, +            server_url='',  # Soledad will fail if not given an url.              bootstrap=bootstrap) diff --git a/src/leap/soledad/tests/test_soledad.py b/src/leap/soledad/tests/test_soledad.py index 1ddfb6a0..1ce9adb7 100644 --- a/src/leap/soledad/tests/test_soledad.py +++ b/src/leap/soledad/tests/test_soledad.py @@ -44,8 +44,8 @@ class AuxMethodsTestCase(BaseSoledadTest):      def test__init_dirs(self):          sol = self._soledad_instance(prefix='/_init_dirs')          sol._init_dirs() -        local_db_dir = os.path.dirname(sol._config.get_local_db_path()) -        secret_path = os.path.dirname(sol._config.get_secret_path()) +        local_db_dir = os.path.dirname(sol.local_db_path) +        secret_path = os.path.dirname(sol.secret_path)          self.assertTrue(os.path.isdir(local_db_dir))          self.assertTrue(os.path.isdir(secret_path)) @@ -61,81 +61,33 @@ class AuxMethodsTestCase(BaseSoledadTest):          from leap.soledad.backends.sqlcipher import SQLCipherDatabase          self.assertIsInstance(sol._db, SQLCipherDatabase) -    def test__init_config_default(self): -        """ -        Test if configuration defaults point to the correct place. -        """ -        sol = Soledad('leap@leap.se', passphrase='123', bootstrap=False) -        self.assertTrue(bool(re.match( -            '.*/\.config/leap/soledad/secret.gpg', -            sol._config.get_secret_path()))) -        self.assertTrue(bool(re.match( -            '.*/\.config/leap/soledad/soledad.u1db', -            sol._config.get_local_db_path()))) -        self.assertEqual( -            'http://provider/soledad/shared', -            sol._config.get_shared_db_url()) -      def test__init_config_defaults(self):          """          Test if configuration defaults point to the correct place.          """ -        # we use regexp match here because HOME environment variable is -        # changed by the BaseLeapTest class but BaseConfig does not capture -        # that change. -        sol = Soledad('leap@leap.se', passphrase='123', bootstrap=False) -        self.assertTrue(bool(re.match( -            '.*/\.config/leap/soledad/secret.gpg', -            sol._config.get_secret_path()))) -        self.assertTrue(bool(re.match( -            '.*/\.config/leap/soledad/soledad.u1db', -            sol._config.get_local_db_path()))) -        self.assertEqual( -            'http://provider/soledad/shared', -            sol._config.get_shared_db_url()) - -    def test__init_config_from_file(self): -        """ -        Test if configuration is correctly read from file. -        """ -        # we use regexp match here because HOME environment variable is -        # changed by the BaseLeapTest class but BaseConfig does not capture -        # that change. -        config_values = { -            "secret_path": "value_1", -            "local_db_path": "value_2", -            "shared_db_url": "value_3" -        } -        tmpfile = tempfile.mktemp(dir=self.tempdir) -        f = open(tmpfile, 'w') -        f.write(json.dumps(config_values)) -        f.close() -        sol = Soledad( -            'leap@leap.se', -            passphrase='123', -            bootstrap=False, -            config_path=tmpfile) -        self.assertEqual('value_1', sol._config.get_secret_path()) -        self.assertEqual('value_2', sol._config.get_local_db_path()) -        self.assertEqual('value_3', sol._config.get_shared_db_url()) +        sol = Soledad('leap@leap.se', passphrase='123', bootstrap=False, +                      server_url='')  # otherwise Soledad will fail. +        self.assertEquals( +            os.path.join(sol.DEFAULT_PREFIX, 'secret.gpg'), +            sol.secret_path) +        self.assertEquals( +            os.path.join(sol.DEFAULT_PREFIX, 'soledad.u1db'), +            sol.local_db_path)      def test__init_config_from_params(self):          """          Test if configuration is correctly read from file.          """ -        # we use regexp match here because HOME environment variable is -        # changed by the BaseLeapTest class but BaseConfig does not capture -        # that change.          sol = Soledad(              'leap@leap.se',              passphrase='123',              bootstrap=False,              secret_path='value_3',              local_db_path='value_2', -            shared_db_url='value_1') -        self.assertEqual('value_3', sol._config.get_secret_path()) -        self.assertEqual('value_2', sol._config.get_local_db_path()) -        self.assertEqual('value_1', sol._config.get_shared_db_url()) +            server_url='value_1') +        self.assertEqual('value_3', sol.secret_path) +        self.assertEqual('value_2', sol.local_db_path) +        self.assertEqual('value_1', sol.server_url)  class SoledadSharedDBTestCase(BaseSoledadTest):  | 
