From b433c1ed736f5d4c19da4cdb21108a02459ca7fd Mon Sep 17 00:00:00 2001 From: drebs Date: Sat, 25 Feb 2017 08:53:38 -0300 Subject: [refactor] pass soledad object to client secrets api In order to be able to change passphrase, token and offline status of soledad from the bitmask client api, the secrets api also has to be able to use up-to-date values when encrypting/decrypting secrets and uploading/downloading them to the server. This commit makes public some soledad attributes that were previously "private" (i.e. used to start with "_" and were not meant to be accessed from outside), and passes the whole soledad object to the client secrets api. This makes the code cleaner and also allows for always getting newest values of soledad attributes. --- .../src/leap/soledad/client/_secrets/__init__.py | 25 +++------- client/src/leap/soledad/client/_secrets/crypto.py | 7 +-- client/src/leap/soledad/client/_secrets/storage.py | 40 +++++++-------- client/src/leap/soledad/client/_secrets/util.py | 4 +- client/src/leap/soledad/client/api.py | 57 ++++++---------------- testing/tests/client/test_aux_methods.py | 12 ++--- testing/tests/client/test_deprecated_crypto.py | 2 +- testing/tests/client/test_secrets.py | 10 ++-- testing/tests/server/test_server.py | 2 +- 9 files changed, 60 insertions(+), 99 deletions(-) diff --git a/client/src/leap/soledad/client/_secrets/__init__.py b/client/src/leap/soledad/client/_secrets/__init__.py index 78cfae5e..43541e16 100644 --- a/client/src/leap/soledad/client/_secrets/__init__.py +++ b/client/src/leap/soledad/client/_secrets/__init__.py @@ -43,16 +43,11 @@ class Secrets(EmitMixin): 'local_secret': 448, # local_secret to derive a local_key for storage } - def __init__(self, uuid, passphrase, url, local_path, get_token, userid, - shared_db=None): - self._uuid = uuid - self._passphrase = passphrase - self._userid = userid + def __init__(self, soledad): + self._soledad = soledad self._secrets = {} - self.crypto = SecretsCrypto(self.get_passphrase) - self.storage = SecretsStorage( - uuid, self.get_passphrase, url, local_path, get_token, userid, - shared_db=shared_db) + self.crypto = SecretsCrypto(soledad) + self.storage = SecretsStorage(soledad) self._bootstrap() # @@ -83,6 +78,8 @@ class Secrets(EmitMixin): if encrypted['version'] < self.crypto.VERSION or force_storage: # TODO: what should we do if it's the first run and remote save # fails? + # TODO: we have to actually update the encrypted version before + # saving, we are currently not doing it. self.storage.save_local(encrypted) self.storage.save_remote(encrypted) @@ -112,15 +109,7 @@ class Secrets(EmitMixin): data = {'secret': encrypted, 'version': 2} return data - def get_passphrase(self): - return self._passphrase.encode('utf-8') - - @property - def passphrase(self): - return self.get_passphrase() - - def change_passphrase(self, new_passphrase): - self._passphrase = new_passphrase + def store_secrets(self): encrypted = self.crypto.encrypt(self._secrets) self.storage.save_local(encrypted) self.storage.save_remote(encrypted) diff --git a/client/src/leap/soledad/client/_secrets/crypto.py b/client/src/leap/soledad/client/_secrets/crypto.py index 02d7dc02..fa7aaca0 100644 --- a/client/src/leap/soledad/client/_secrets/crypto.py +++ b/client/src/leap/soledad/client/_secrets/crypto.py @@ -34,11 +34,12 @@ class SecretsCrypto(object): VERSION = 2 - def __init__(self, get_pass): - self._get_pass = get_pass + def __init__(self, soledad): + self._soledad = soledad def _get_key(self, salt): - key = scrypt.hash(self._get_pass(), salt, buflen=32) + passphrase = self._soledad.passphrase.encode('utf8') + key = scrypt.hash(passphrase, salt, buflen=32) return key # diff --git a/client/src/leap/soledad/client/_secrets/storage.py b/client/src/leap/soledad/client/_secrets/storage.py index 5fde8988..bb74dba3 100644 --- a/client/src/leap/soledad/client/_secrets/storage.py +++ b/client/src/leap/soledad/client/_secrets/storage.py @@ -33,29 +33,26 @@ logger = getLogger(__name__) class SecretsStorage(EmitMixin): - def __init__(self, uuid, get_pass, url, local_path, get_token, userid, - shared_db=None): - self._uuid = uuid - self._get_pass = get_pass - self._local_path = local_path - self._get_token = get_token - self._userid = userid - - self._shared_db = shared_db or self._init_shared_db(url, self._creds) + def __init__(self, soledad): + self._soledad = soledad + self._shared_db = self._soledad.shared_db or self._init_shared_db() self.__remote_doc = None @property def _creds(self): - return {'token': {'uuid': self._uuid, 'token': self._get_token()}} + uuid = self._soledad.uuid + token = self._soledad.token + return {'token': {'uuid': uuid, 'token': token}} # # local storage # def load_local(self): - logger.info("trying to load secrets from disk: %s" % self._local_path) + path = self._soledad.secrets_path + logger.info("trying to load secrets from disk: %s" % path) try: - with open(self._local_path, 'r') as f: + with open(path, 'r') as f: encrypted = json.loads(f.read()) logger.info("secrets loaded successfully from disk") return encrypted @@ -64,23 +61,26 @@ class SecretsStorage(EmitMixin): return None def save_local(self, encrypted): + path = self._soledad.secrets_path json_data = json.dumps(encrypted) - with open(self._local_path, 'w') as f: + with open(path, 'w') as f: f.write(json_data) # # remote storage # - def _init_shared_db(self, url, creds): - url = urlparse.urljoin(url, SHARED_DB_NAME) - db = SoledadSharedDatabase.open_database( - url, self._uuid, creds=creds) - self._shared_db = db + def _init_shared_db(self): + url = urlparse.urljoin(self._soledad.server_url, SHARED_DB_NAME) + uuid = self._soledad.uuid + creds = self._creds + db = SoledadSharedDatabase.open_database(url, uuid, creds) + return db def _remote_doc_id(self): - passphrase = self._get_pass() - text = '%s%s' % (passphrase, self._uuid) + passphrase = self._soledad.passphrase.encode('utf8') + uuid = self._soledad.uuid + text = '%s%s' % (passphrase, uuid) digest = sha256(text).hexdigest() return digest diff --git a/client/src/leap/soledad/client/_secrets/util.py b/client/src/leap/soledad/client/_secrets/util.py index 0dcdd3af..75418518 100644 --- a/client/src/leap/soledad/client/_secrets/util.py +++ b/client/src/leap/soledad/client/_secrets/util.py @@ -27,7 +27,9 @@ class EmitMixin(object): @property def _user_data(self): - return {'uuid': self._uuid, 'userid': self._userid} + uuid = self._soledad.uuid + userid = self._soledad.userid + return {'uuid': uuid, 'userid': userid} def emit(verb): diff --git a/client/src/leap/soledad/client/api.py b/client/src/leap/soledad/client/api.py index 16569ec2..4be38cf1 100644 --- a/client/src/leap/soledad/client/api.py +++ b/client/src/leap/soledad/client/api.py @@ -177,27 +177,25 @@ class Soledad(object): some reason. """ # store config params - self._uuid = uuid - self._passphrase = passphrase + self.uuid = uuid + self.passphrase = passphrase + self.secrets_path = secrets_path self._local_db_path = local_db_path - self._server_url = server_url - self._secrets_path = None + self.server_url = server_url + self.shared_db = shared_db + self.token = auth_token + self.offline = offline + self._dbsyncer = None - self._offline = offline # configure SSL certificate global SOLEDAD_CERT SOLEDAD_CERT = cert_file - self.set_token(auth_token) - self._init_config_with_defaults() self._init_working_dirs() - self._secrets_path = secrets_path - - self._init_secrets(shared_db=shared_db) - + self._secrets = Secrets(self) self._crypto = SoledadCrypto(self._secrets.remote_secret) try: @@ -214,14 +212,6 @@ class Soledad(object): self._dbpool.close() raise - def _get_offline(self): - return self._offline - - def _set_offline(self, offline): - self._offline = offline - - offline = property(_get_offline, _set_offline) - # # initialization/destruction methods # @@ -230,7 +220,7 @@ class Soledad(object): """ Initialize configuration using default values for missing params. """ - soledad_assert_type(self._passphrase, unicode) + soledad_assert_type(self.passphrase, unicode) def initialize(attr, val): return ((getattr(self, attr, None) is None) and @@ -241,7 +231,7 @@ class Soledad(object): initialize("_local_db_path", os.path.join( self.default_prefix, self.local_db_file_name)) # initialize server_url - soledad_assert(self._server_url is not None, + soledad_assert(self.server_url is not None, 'Missing URL for Soledad server.') def _init_working_dirs(self): @@ -255,14 +245,6 @@ class Soledad(object): for path in paths: create_path_if_not_exists(path) - def _init_secrets(self, shared_db=None): - """ - Initialize Soledad secrets. - """ - self._secrets = Secrets( - self._uuid, self._passphrase, self._server_url, self._secrets_path, - self.get_token, self.userid, shared_db=shared_db) - def _init_u1db_sqlcipher_backend(self): """ Initialize the U1DB SQLCipher database for local storage. @@ -646,10 +628,6 @@ class Soledad(object): def local_db_path(self): return self._local_db_path - @property - def uuid(self): - return self._uuid - @property def userid(self): return self.uuid @@ -687,7 +665,7 @@ class Soledad(object): generation before the synchronization was performed. :rtype: twisted.internet.defer.Deferred """ - sync_url = urlparse.urljoin(self._server_url, 'user-%s' % self.uuid) + sync_url = urlparse.urljoin(self.server_url, 'user-%s' % self.uuid) if not self._dbsyncer: return creds = {'token': {'uuid': self.uuid, 'token': self.token}} @@ -748,14 +726,6 @@ class Soledad(object): """ return self.sync_lock.locked - def set_token(self, token): - self._token = token - - def get_token(self): - return self._token - - token = property(get_token, set_token, doc='The authentication Token.') - # # ISecretsStorage # @@ -779,7 +749,8 @@ class Soledad(object): :raise NoStorageSecret: Raised if there's no storage secret available. """ - self._secrets.change_passphrase(new_passphrase) + self.passphrase = new_passphrase + self._secrets.store_secrets() # # Raw SQLCIPHER Queries diff --git a/testing/tests/client/test_aux_methods.py b/testing/tests/client/test_aux_methods.py index a08f7d36..729aa28a 100644 --- a/testing/tests/client/test_aux_methods.py +++ b/testing/tests/client/test_aux_methods.py @@ -33,7 +33,7 @@ class AuxMethodsTestCase(BaseSoledadTest): def test__init_dirs(self): sol = self._soledad_instance(prefix='_init_dirs') local_db_dir = os.path.dirname(sol.local_db_path) - secrets_path = os.path.dirname(sol.secrets.storage._local_path) + secrets_path = os.path.dirname(sol.secrets_path) self.assertTrue(os.path.isdir(local_db_dir)) self.assertTrue(os.path.isdir(secrets_path)) @@ -63,8 +63,8 @@ class AuxMethodsTestCase(BaseSoledadTest): # instantiate without initializing so we just test # _init_config_with_defaults() sol = SoledadMock() - sol._passphrase = u'' - sol._server_url = '' + sol.passphrase = u'' + sol.server_url = '' sol._init_config_with_defaults() # assert value of local_db_path self.assertEquals( @@ -84,11 +84,11 @@ class AuxMethodsTestCase(BaseSoledadTest): cert_file=None) self.assertEqual( os.path.join(self.tempdir, 'value_3'), - sol.secrets.storage._local_path) + sol.secrets_path) self.assertEqual( os.path.join(self.tempdir, 'value_2'), sol.local_db_path) - self.assertEqual('value_1', sol._server_url) + self.assertEqual('value_1', sol.server_url) sol.close() @inlineCallbacks @@ -128,5 +128,5 @@ class AuxMethodsTestCase(BaseSoledadTest): Assert passphrase getter works fine. """ sol = self._soledad_instance() - self.assertEqual('123', sol._passphrase) + self.assertEqual('123', sol.passphrase) sol.close() diff --git a/testing/tests/client/test_deprecated_crypto.py b/testing/tests/client/test_deprecated_crypto.py index 8c711c22..1af1a130 100644 --- a/testing/tests/client/test_deprecated_crypto.py +++ b/testing/tests/client/test_deprecated_crypto.py @@ -51,7 +51,7 @@ class DeprecatedCryptoTest(SoledadWithCouchServerMixin, TestCaseWithServer): self._soledad_instance(user=user, server_url=server_url)) self.make_app() - remote = self.request_state._create_database(replica_uid=client._uuid) + remote = self.request_state._create_database(replica_uid=client.uuid) remote = CouchDatabase.open_database( urljoin(self.couch_url, 'user-' + user), create=True) diff --git a/testing/tests/client/test_secrets.py b/testing/tests/client/test_secrets.py index bbeb1fc2..18ff458b 100644 --- a/testing/tests/client/test_secrets.py +++ b/testing/tests/client/test_secrets.py @@ -121,12 +121,10 @@ class SecretsCryptoTestCase(unittest.TestCase): } def setUp(self): - def _get_pass(): - return '123' - self._crypto = SecretsCrypto(_get_pass) - - def test__get_pass(self): - self.assertEqual(self._crypto._get_pass(), '123') + class Soledad(object): + passphrase = '123' + soledad = Soledad() + self._crypto = SecretsCrypto(soledad) def test__get_key(self): salt = 'abc' diff --git a/testing/tests/server/test_server.py b/testing/tests/server/test_server.py index 647ef5a8..4a5ec43f 100644 --- a/testing/tests/server/test_server.py +++ b/testing/tests/server/test_server.py @@ -135,7 +135,7 @@ class EncryptedSyncTestCase( user=user, prefix='x', auth_token='auth-token', - secrets_path=sol1._secrets_path, + secrets_path=sol1.secrets_path, passphrase=passphrase) # ensure remote db exists before syncing -- cgit v1.2.3