summaryrefslogtreecommitdiff
path: root/common/src
diff options
context:
space:
mode:
authordrebs <drebs@leap.se>2013-10-14 10:54:24 -0300
committerdrebs <drebs@leap.se>2013-10-28 19:18:30 -0200
commit1244f691b084b12463f88e5e0ba068432c17f621 (patch)
tree2cbd68e51bf7a6db4de38bc00067b4e2e74d081c /common/src
parentf6873fa53593a5fa04e733fc8f4cfbeafc4fb1ee (diff)
Add shared db locking.
* Improve bootstrap sequence: - stages are more organized. - there are less useless requests to server. * Improve shared db access: - instantiate the shared db only once. - also results in less requests to server. * Handle unicode passphrases. * Move some common functions and global variables to common. * Improve security of recovery document: - access to the recovery document now depends on the user password. * Improve documentation.
Diffstat (limited to 'common/src')
-rw-r--r--common/src/leap/soledad/common/__init__.py16
-rw-r--r--common/src/leap/soledad/common/couch.py19
-rw-r--r--common/src/leap/soledad/common/errors.py70
-rw-r--r--common/src/leap/soledad/common/tests/__init__.py10
-rw-r--r--common/src/leap/soledad/common/tests/test_server.py189
-rw-r--r--common/src/leap/soledad/common/tests/test_soledad.py94
6 files changed, 345 insertions, 53 deletions
diff --git a/common/src/leap/soledad/common/__init__.py b/common/src/leap/soledad/common/__init__.py
index 26467740..23d28e76 100644
--- a/common/src/leap/soledad/common/__init__.py
+++ b/common/src/leap/soledad/common/__init__.py
@@ -21,8 +21,21 @@ Soledad routines common to client and server.
"""
+from hashlib import sha256
+
+
#
-# Assert functions
+# Global constants
+#
+
+
+SHARED_DB_NAME = 'shared'
+SHARED_DB_LOCK_DOC_ID_PREFIX = 'lock-'
+USER_DB_PREFIX = 'user-'
+
+
+#
+# Global functions
#
# we want to use leap.common.check.leap_assert in case it is available,
@@ -63,6 +76,7 @@ except ImportError:
"Expected type %r instead of %r" %
(expectedType, type(var)))
+
from ._version import get_versions
__version__ = get_versions()['version']
del get_versions
diff --git a/common/src/leap/soledad/common/couch.py b/common/src/leap/soledad/common/couch.py
index 187d3035..1396f4d7 100644
--- a/common/src/leap/soledad/common/couch.py
+++ b/common/src/leap/soledad/common/couch.py
@@ -33,6 +33,7 @@ from couchdb.client import Server, Document as CouchDocument
from couchdb.http import ResourceNotFound, Unauthorized
+from leap.soledad.common import USER_DB_PREFIX
from leap.soledad.common.objectstore import (
ObjectStoreDatabase,
ObjectStoreSyncTarget,
@@ -61,7 +62,7 @@ def persistent_class(cls):
dump_method_name, store):
"""
Create a persistent method to replace C{old_method_name}.
-
+
The new method will load C{key} using C{load_method_name} and stores
it using C{dump_method_name} depending on the value of C{store}.
"""
@@ -522,8 +523,7 @@ class CouchServerState(ServerState):
Inteface of the WSGI server with the CouchDB backend.
"""
- def __init__(self, couch_url, shared_db_name, tokens_db_name,
- user_db_prefix):
+ def __init__(self, couch_url, shared_db_name, tokens_db_name):
"""
Initialize the couch server state.
@@ -533,13 +533,10 @@ class CouchServerState(ServerState):
@type shared_db_name: str
@param tokens_db_name: The name of the tokens database.
@type tokens_db_name: str
- @param user_db_prefix: The prefix for user database names.
- @type user_db_prefix: str
"""
self._couch_url = couch_url
self._shared_db_name = shared_db_name
self._tokens_db_name = tokens_db_name
- self._user_db_prefix = user_db_prefix
try:
self._check_couch_permissions()
except NotEnoughCouchPermissions:
@@ -553,8 +550,8 @@ class CouchServerState(ServerState):
def _check_couch_permissions(self):
"""
- Assert that Soledad Server has enough permissions on the underlying couch
- database.
+ Assert that Soledad Server has enough permissions on the underlying
+ couch database.
Soledad Server has to be able to do the following in the couch server:
@@ -563,8 +560,8 @@ class CouchServerState(ServerState):
* Read from 'tokens' db.
This function tries to perform the actions above using the "low level"
- couch library to ensure that Soledad Server can do everything it needs on
- the underlying couch database.
+ couch library to ensure that Soledad Server can do everything it needs
+ on the underlying couch database.
@param couch_url: The URL of the couch database.
@type couch_url: str
@@ -593,7 +590,7 @@ class CouchServerState(ServerState):
_open_couch_db(self._shared_db_name))
# test read/write auth for user-<something> db
_create_delete_test_doc(
- _open_couch_db('%stest-db' % self._user_db_prefix))
+ _open_couch_db('%stest-db' % USER_DB_PREFIX))
# test read auth for tokens db
tokensdb = _open_couch_db(self._tokens_db_name)
tokensdb.info()
diff --git a/common/src/leap/soledad/common/errors.py b/common/src/leap/soledad/common/errors.py
new file mode 100644
index 00000000..45433627
--- /dev/null
+++ b/common/src/leap/soledad/common/errors.py
@@ -0,0 +1,70 @@
+# -*- coding: utf-8 -*-
+# errors.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 errors.
+"""
+
+
+from u1db import errors
+from u1db.remote import http_errors
+
+
+#
+# LockResource: a lock based on a document in the shared database.
+#
+
+class InvalidTokenError(errors.U1DBError):
+ """
+ Exception raised when trying to unlock shared database with invalid token.
+ """
+
+ wire_description = "unlock unauthorized"
+ status = 401
+
+
+class NotLockedError(errors.U1DBError):
+ """
+ Exception raised when trying to unlock shared database when it is not
+ locked.
+ """
+
+ wire_description = "lock not found"
+ status = 404
+
+
+class AlreadyLockedError(errors.U1DBError):
+ """
+ Exception raised when trying to lock shared database but it is already
+ locked.
+ """
+
+ wire_description = "lock is locked"
+ status = 403
+
+# update u1db "wire description to status" and "wire description to exception"
+# maps.
+for e in [InvalidTokenError, NotLockedError, AlreadyLockedError]:
+ http_errors.wire_description_to_status.update({
+ (e.wire_description, e.status)})
+ errors.wire_description_to_exc.update({
+ (e.wire_description, e)})
+
+# u1db error statuses also have to be updated
+http_errors.ERROR_STATUSES = set(
+ http_errors.wire_description_to_status.values())
diff --git a/common/src/leap/soledad/common/tests/__init__.py b/common/src/leap/soledad/common/tests/__init__.py
index 9f47d74a..88f98272 100644
--- a/common/src/leap/soledad/common/tests/__init__.py
+++ b/common/src/leap/soledad/common/tests/__init__.py
@@ -60,11 +60,12 @@ class BaseSoledadTest(BaseLeapTest):
if os.path.isfile(f):
os.unlink(f)
- def _soledad_instance(self, user=ADDRESS, passphrase='123',
+ def _soledad_instance(self, user=ADDRESS, passphrase=u'123',
prefix='',
secrets_path=Soledad.STORAGE_SECRETS_FILE_NAME,
local_db_path='soledad.u1db', server_url='',
- cert_file=None, secret_id=None):
+ cert_file=None, secret_id=None,
+ shared_db_class=None):
def _put_doc_side_effect(doc):
self._doc_put = doc
@@ -73,10 +74,15 @@ class BaseSoledadTest(BaseLeapTest):
get_doc = Mock(return_value=None)
put_doc = Mock(side_effect=_put_doc_side_effect)
+ lock = Mock(return_value=('atoken', 300))
+ unlock = Mock(return_value=True)
def __call__(self):
return self
+ if shared_db_class is not None:
+ MockSharedDB = shared_db_class
+
Soledad._shared_db = MockSharedDB()
return Soledad(
user,
diff --git a/common/src/leap/soledad/common/tests/test_server.py b/common/src/leap/soledad/common/tests/test_server.py
index 1ea4d615..83df192b 100644
--- a/common/src/leap/soledad/common/tests/test_server.py
+++ b/common/src/leap/soledad/common/tests/test_server.py
@@ -24,6 +24,7 @@ import os
import tempfile
import simplejson as json
import mock
+import time
from leap.common.testing.basetest import BaseLeapTest
@@ -45,7 +46,7 @@ from leap.soledad.client import (
Soledad,
target,
)
-from leap.soledad.server import SoledadApp
+from leap.soledad.server import SoledadApp, LockResource
from leap.soledad.server.auth import URLToAuthorization
@@ -86,9 +87,8 @@ class ServerAuthorizationTestCase(BaseLeapTest):
/user-db/sync-from/{source} | GET, PUT, POST
"""
uuid = 'myuuid'
- authmap = URLToAuthorization(
- uuid, SoledadApp.SHARED_DB_NAME, SoledadApp.USER_DB_PREFIX)
- dbname = authmap._uuid_dbname(uuid)
+ authmap = URLToAuthorization(uuid,)
+ dbname = authmap._user_db_name
# test global auth
self.assertTrue(
authmap.is_authorized(self._make_environ('/', 'GET')))
@@ -202,8 +202,7 @@ class ServerAuthorizationTestCase(BaseLeapTest):
Test if authorization fails for a wrong dbname.
"""
uuid = 'myuuid'
- authmap = URLToAuthorization(
- uuid, SoledadApp.SHARED_DB_NAME, SoledadApp.USER_DB_PREFIX)
+ authmap = URLToAuthorization(uuid)
dbname = 'somedb'
# test wrong-db database resource auth
self.assertFalse(
@@ -273,7 +272,7 @@ class EncryptedSyncTestCase(
sync_target = token_leap_sync_target
- def _soledad_instance(self, user='user-uuid', passphrase='123',
+ def _soledad_instance(self, user='user-uuid', passphrase=u'123',
prefix='',
secrets_path=Soledad.STORAGE_SECRETS_FILE_NAME,
local_db_path='soledad.u1db', server_url='',
@@ -293,6 +292,8 @@ class EncryptedSyncTestCase(
get_doc = mock.Mock(return_value=None)
put_doc = mock.Mock(side_effect=_put_doc_side_effect)
+ lock = mock.Mock(return_value=('atoken', 300))
+ unlock = mock.Mock()
def __call__(self):
return self
@@ -310,8 +311,8 @@ class EncryptedSyncTestCase(
secret_id=secret_id)
def make_app(self):
- self.request_state = CouchServerState(
- self._couch_url, 'shared', 'tokens', 'user-')
+ self.request_state = CouchServerState(self._couch_url, 'shared',
+ 'tokens')
return self.make_app_with_state(self.request_state)
def setUp(self):
@@ -375,3 +376,173 @@ class EncryptedSyncTestCase(
doc2 = doclist[0]
# assert incoming doc is equal to the first sent doc
self.assertEqual(doc1, doc2)
+
+ def test_encrypted_sym_sync_with_unicode_passphrase(self):
+ """
+ Test the complete syncing chain between two soledad dbs using a
+ Soledad server backed by a couch database, using an unicode
+ passphrase.
+ """
+ self.startServer()
+ # instantiate soledad and create a document
+ sol1 = self._soledad_instance(
+ # token is verified in test_target.make_token_soledad_app
+ auth_token='auth-token',
+ passphrase=u'ãáàäéàëíìïóòöõúùüñç',
+ )
+ _, doclist = sol1.get_all_docs()
+ self.assertEqual([], doclist)
+ doc1 = sol1.create_doc(json.loads(simple_doc))
+ # sync with server
+ sol1._server_url = self.getURL()
+ sol1.sync()
+ # assert doc was sent to couch db
+ db = CouchDatabase(
+ self._couch_url,
+ # the name of the user database is "user-<uuid>".
+ 'user-user-uuid',
+ )
+ _, doclist = db.get_all_docs()
+ self.assertEqual(1, len(doclist))
+ couchdoc = doclist[0]
+ # assert document structure in couch server
+ self.assertEqual(doc1.doc_id, couchdoc.doc_id)
+ self.assertEqual(doc1.rev, couchdoc.rev)
+ self.assertEqual(6, len(couchdoc.content))
+ self.assertTrue(target.ENC_JSON_KEY in couchdoc.content)
+ self.assertTrue(target.ENC_SCHEME_KEY in couchdoc.content)
+ self.assertTrue(target.ENC_METHOD_KEY in couchdoc.content)
+ self.assertTrue(target.ENC_IV_KEY in couchdoc.content)
+ self.assertTrue(target.MAC_KEY in couchdoc.content)
+ self.assertTrue(target.MAC_METHOD_KEY in couchdoc.content)
+ # instantiate soledad with empty db, but with same secrets path
+ sol2 = self._soledad_instance(
+ prefix='x',
+ auth_token='auth-token',
+ passphrase=u'ãáàäéàëíìïóòöõúùüñç',
+ )
+ _, doclist = sol2.get_all_docs()
+ self.assertEqual([], doclist)
+ sol2._secrets_path = sol1.secrets_path
+ sol2._load_secrets()
+ sol2._set_secret_id(sol1._secret_id)
+ # sync the new instance
+ sol2._server_url = self.getURL()
+ sol2.sync()
+ _, doclist = sol2.get_all_docs()
+ self.assertEqual(1, len(doclist))
+ doc2 = doclist[0]
+ # assert incoming doc is equal to the first sent doc
+ self.assertEqual(doc1, doc2)
+
+
+class LockResourceTestCase(
+ CouchDBTestCase, TestCaseWithServer):
+ """
+ Tests for use of PUT and DELETE on lock resource.
+ """
+
+ @staticmethod
+ def make_app_with_state(state):
+ return make_token_soledad_app(state)
+
+ make_document_for_test = make_leap_document_for_test
+
+ sync_target = token_leap_sync_target
+
+ def setUp(self):
+ TestCaseWithServer.setUp(self)
+ CouchDBTestCase.setUp(self)
+ self.tempdir = tempfile.mkdtemp(prefix="leap_tests-")
+ self._couch_url = 'http://localhost:' + str(self.wrapper.port)
+ self._state = CouchServerState(
+ self._couch_url, 'shared', 'tokens')
+
+ def tearDown(self):
+ CouchDBTestCase.tearDown(self)
+ TestCaseWithServer.tearDown(self)
+
+ def test__try_obtain_filesystem_lock(self):
+ responder = mock.Mock()
+ lr = LockResource('uuid', self._state, responder)
+ self.assertFalse(lr._lock.locked)
+ self.assertTrue(lr._try_obtain_filesystem_lock())
+ self.assertTrue(lr._lock.locked)
+ lr._try_release_filesystem_lock()
+
+ def test__try_release_filesystem_lock(self):
+ responder = mock.Mock()
+ lr = LockResource('uuid', self._state, responder)
+ lr._try_obtain_filesystem_lock()
+ self.assertTrue(lr._lock.locked)
+ lr._try_release_filesystem_lock()
+ self.assertFalse(lr._lock.locked)
+
+ def test_put(self):
+ responder = mock.Mock()
+ lr = LockResource('uuid', self._state, responder)
+ # lock!
+ lr.put({}, None)
+ # assert lock document was correctly written
+ lock_doc = lr._shared_db.get_doc('lock-uuid')
+ self.assertIsNotNone(lock_doc)
+ self.assertTrue(LockResource.TIMESTAMP_KEY in lock_doc.content)
+ self.assertTrue(LockResource.LOCK_TOKEN_KEY in lock_doc.content)
+ timestamp = lock_doc.content[LockResource.TIMESTAMP_KEY]
+ token = lock_doc.content[LockResource.LOCK_TOKEN_KEY]
+ self.assertTrue(timestamp < time.time())
+ self.assertTrue(time.time() < timestamp + LockResource.TIMEOUT)
+ # assert response to user
+ responder.send_response_json.assert_called_with(
+ 201, token=token,
+ timeout=LockResource.TIMEOUT)
+
+ def test_delete(self):
+ responder = mock.Mock()
+ lr = LockResource('uuid', self._state, responder)
+ # lock!
+ lr.put({}, None)
+ lock_doc = lr._shared_db.get_doc('lock-uuid')
+ token = lock_doc.content[LockResource.LOCK_TOKEN_KEY]
+ # unlock!
+ lr.delete({'token': token}, None)
+ self.assertFalse(lr._lock.locked)
+ self.assertIsNone(lr._shared_db.get_doc('lock-uuid'))
+ responder.send_response_json.assert_called_with(200)
+
+ def test_put_while_locked_fails(self):
+ responder = mock.Mock()
+ lr = LockResource('uuid', self._state, responder)
+ # lock!
+ lr.put({}, None)
+ # try to lock again!
+ lr.put({}, None)
+ self.assertEqual(
+ len(responder.send_response_json.call_args), 2)
+ self.assertEqual(
+ responder.send_response_json.call_args[0], (403,))
+ self.assertEqual(
+ len(responder.send_response_json.call_args[1]), 2)
+ self.assertTrue(
+ 'remaining' in responder.send_response_json.call_args[1])
+ self.assertTrue(
+ responder.send_response_json.call_args[1]['remaining'] > 0)
+
+ def test_unlock_unexisting_lock_fails(self):
+ responder = mock.Mock()
+ lr = LockResource('uuid', self._state, responder)
+ # unlock!
+ lr.delete({'token': 'anything'}, None)
+ responder.send_response_json.assert_called_with(
+ 404, error='lock not found')
+
+ def test_unlock_with_wrong_token_fails(self):
+ responder = mock.Mock()
+ lr = LockResource('uuid', self._state, responder)
+ # lock!
+ lr.put({}, None)
+ # unlock!
+ lr.delete({'token': 'wrongtoken'}, None)
+ self.assertIsNotNone(lr._shared_db.get_doc('lock-uuid'))
+ responder.send_response_json.assert_called_with(
+ 401, error='unlock unauthorized')
diff --git a/common/src/leap/soledad/common/tests/test_soledad.py b/common/src/leap/soledad/common/tests/test_soledad.py
index 0b753647..8970a437 100644
--- a/common/src/leap/soledad/common/tests/test_soledad.py
+++ b/common/src/leap/soledad/common/tests/test_soledad.py
@@ -90,7 +90,7 @@ class AuxMethodsTestCase(BaseSoledadTest):
"""
sol = self._soledad_instance(
'leap@leap.se',
- passphrase='123',
+ passphrase=u'123',
secrets_path='value_3',
local_db_path='value_2',
server_url='value_1',
@@ -109,25 +109,25 @@ class AuxMethodsTestCase(BaseSoledadTest):
"""
sol = self._soledad_instance(
'leap@leap.se',
- passphrase='123',
+ passphrase=u'123',
prefix=self.rand_prefix,
)
doc = sol.create_doc({'simple': 'doc'})
doc_id = doc.doc_id
# change the passphrase
- sol.change_passphrase('654321')
+ sol.change_passphrase(u'654321')
self.assertRaises(
DatabaseError,
self._soledad_instance, 'leap@leap.se',
- passphrase='123',
+ passphrase=u'123',
prefix=self.rand_prefix)
# use new passphrase and retrieve doc
sol2 = self._soledad_instance(
'leap@leap.se',
- passphrase='654321',
+ passphrase=u'654321',
prefix=self.rand_prefix)
doc2 = sol2.get_doc(doc_id)
self.assertEqual(doc, doc2)
@@ -139,11 +139,11 @@ class AuxMethodsTestCase(BaseSoledadTest):
"""
sol = self._soledad_instance(
'leap@leap.se',
- passphrase='123')
+ passphrase=u'123')
# check that soledad complains about new passphrase length
self.assertRaises(
PassphraseTooShort,
- sol.change_passphrase, '54321')
+ sol.change_passphrase, u'54321')
def test_get_passphrase(self):
"""
@@ -161,13 +161,14 @@ class SoledadSharedDBTestCase(BaseSoledadTest):
def setUp(self):
BaseSoledadTest.setUp(self)
self._shared_db = SoledadSharedDatabase(
- 'https://provider/', SoledadDocument, None)
+ 'https://provider/', ADDRESS, document_factory=SoledadDocument,
+ creds=None)
def test__get_secrets_from_shared_db(self):
"""
Ensure the shared db is queried with the correct doc_id.
"""
- doc_id = self._soledad._uuid_hash()
+ doc_id = self._soledad._shared_db_doc_id()
self._soledad._get_secrets_from_shared_db()
self.assertTrue(
self._soledad._shared_db().get_doc.assert_called_with(
@@ -178,7 +179,7 @@ class SoledadSharedDBTestCase(BaseSoledadTest):
"""
Ensure recovery document is put into shared recover db.
"""
- doc_id = self._soledad._uuid_hash()
+ doc_id = self._soledad._shared_db_doc_id()
self._soledad._put_secrets_in_shared_db()
self.assertTrue(
self._soledad._shared_db().get_doc.assert_called_with(
@@ -201,9 +202,10 @@ class SoledadSignalingTestCase(BaseSoledadTest):
EVENTS_SERVER_PORT = 8090
def setUp(self):
- BaseSoledadTest.setUp(self)
# mock signaling
soledad.client.signal = Mock()
+ # run parent's setUp
+ BaseSoledadTest.setUp(self)
def tearDown(self):
pass
@@ -213,22 +215,28 @@ class SoledadSignalingTestCase(BaseSoledadTest):
mocked.mock_calls.pop()
mocked.call_args = mocked.call_args_list[-1]
- def test_stage2_bootstrap_signals(self):
+ def test_stage3_bootstrap_signals(self):
"""
Test that a fresh soledad emits all bootstrap signals.
+
+ Signals are:
+ - downloading keys / done downloading keys.
+ - creating keys / done creating keys.
+ - downloading keys / done downloading keys.
+ - uploading keys / done uploading keys.
"""
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')
+ secrets_path='alternative_stage3.json',
+ local_db_path='alternative_stage3.u1db')
# reverse call order so we can verify in the order the signals were
# expected
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
+ # downloading keys signals
soledad.client.signal.assert_called_with(
proto.SOLEDAD_DOWNLOADING_KEYS,
ADDRESS,
@@ -238,6 +246,7 @@ class SoledadSignalingTestCase(BaseSoledadTest):
proto.SOLEDAD_DONE_DOWNLOADING_KEYS,
ADDRESS,
)
+ # creating keys signals
self._pop_mock_call(soledad.client.signal)
soledad.client.signal.assert_called_with(
proto.SOLEDAD_CREATING_KEYS,
@@ -248,6 +257,7 @@ class SoledadSignalingTestCase(BaseSoledadTest):
proto.SOLEDAD_DONE_CREATING_KEYS,
ADDRESS,
)
+ # downloading once more (inside _put_keys_in_shared_db)
self._pop_mock_call(soledad.client.signal)
soledad.client.signal.assert_called_with(
proto.SOLEDAD_DOWNLOADING_KEYS,
@@ -258,6 +268,7 @@ class SoledadSignalingTestCase(BaseSoledadTest):
proto.SOLEDAD_DONE_DOWNLOADING_KEYS,
ADDRESS,
)
+ # uploading keys signals
self._pop_mock_call(soledad.client.signal)
soledad.client.signal.assert_called_with(
proto.SOLEDAD_UPLOADING_KEYS,
@@ -268,21 +279,45 @@ class SoledadSignalingTestCase(BaseSoledadTest):
proto.SOLEDAD_DONE_UPLOADING_KEYS,
ADDRESS,
)
+ # assert db was locked and unlocked
+ sol._shared_db.lock.assert_called_with()
+ sol._shared_db.unlock.assert_called_with('atoken')
- def test_stage1_bootstrap_signals(self):
+ def test_stage2_bootstrap_signals(self):
"""
- Test that an existent soledad emits some of the bootstrap signals.
+ Test that if there are keys in server, soledad will download them and
+ emit corresponding signals.
"""
- soledad.client.signal.reset_mock()
- # get an existent instance so it emits only some of bootstrap signals
+ # get existing instance so we have access to keys
sol = self._soledad_instance()
+ # create a document with secrets
+ doc = SoledadDocument(doc_id=sol._shared_db_doc_id())
+ doc.content = sol.export_recovery_document(include_uuid=False)
+
+ class Stage2MockSharedDB(object):
+
+ get_doc = Mock(return_value=doc)
+ put_doc = Mock()
+ lock = Mock(return_value=('atoken', 300))
+ unlock = Mock()
+
+ def __call__(self):
+ return self
+
+ # reset mock
+ soledad.client.signal.reset_mock()
+ # get a fresh instance so it emits all bootstrap signals
+ sol = self._soledad_instance(
+ secrets_path='alternative_stage2.json',
+ local_db_path='alternative_stage2.u1db',
+ shared_db_class=Stage2MockSharedDB)
# reverse call order so we can verify in the order the signals were
# expected
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
+ # assert download keys signals
soledad.client.signal.assert_called_with(
proto.SOLEDAD_DOWNLOADING_KEYS,
ADDRESS,
@@ -292,16 +327,15 @@ class SoledadSignalingTestCase(BaseSoledadTest):
proto.SOLEDAD_DONE_DOWNLOADING_KEYS,
ADDRESS,
)
- self._pop_mock_call(soledad.client.signal)
- soledad.client.signal.assert_called_with(
- proto.SOLEDAD_UPLOADING_KEYS,
- ADDRESS,
- )
- self._pop_mock_call(soledad.client.signal)
- soledad.client.signal.assert_called_with(
- proto.SOLEDAD_DONE_UPLOADING_KEYS,
- ADDRESS,
- )
+
+ def test_stage1_bootstrap_signals(self):
+ """
+ Test that if soledad already has a local secret, it emits no signals.
+ """
+ soledad.client.signal.reset_mock()
+ # get an existent instance so it emits only some of bootstrap signals
+ sol = self._soledad_instance()
+ self.assertEqual([], soledad.client.signal.mock_calls)
def test_sync_signals(self):
"""