From ff85c2a41fe933d9959fb84a0df2a13a6e199cec Mon Sep 17 00:00:00 2001 From: drebs Date: Mon, 5 Dec 2016 09:10:47 -0200 Subject: [refactor] improve secrets generation and storage code --- testing/tests/client/test_aux_methods.py | 27 +-- testing/tests/client/test_crypto.py | 227 +++++++++++++++---------- testing/tests/client/test_deprecated_crypto.py | 6 +- testing/tests/client/test_shared_db.py | 38 ++--- testing/tests/client/test_signals.py | 98 +++++------ testing/tests/sync/test_sync_target.py | 2 +- 6 files changed, 203 insertions(+), 195 deletions(-) (limited to 'testing/tests') diff --git a/testing/tests/client/test_aux_methods.py b/testing/tests/client/test_aux_methods.py index 9b4a175f..a08f7d36 100644 --- a/testing/tests/client/test_aux_methods.py +++ b/testing/tests/client/test_aux_methods.py @@ -19,12 +19,11 @@ Tests for general Soledad functionality. """ import os -from twisted.internet import defer +from pytest import inlineCallbacks from leap.soledad.client import Soledad from leap.soledad.client.adbapi import U1DBConnectionPool -from leap.soledad.client.secrets import PassphraseTooShort -from leap.soledad.client.secrets import SecretsException +from leap.soledad.client._secrets.util import SecretsError from test_soledad.util import BaseSoledadTest @@ -34,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.secrets_path) + secrets_path = os.path.dirname(sol.secrets.storage._local_path) self.assertTrue(os.path.isdir(local_db_dir)) self.assertTrue(os.path.isdir(secrets_path)) @@ -85,14 +84,14 @@ class AuxMethodsTestCase(BaseSoledadTest): cert_file=None) self.assertEqual( os.path.join(self.tempdir, 'value_3'), - sol.secrets.secrets_path) + sol.secrets.storage._local_path) self.assertEqual( os.path.join(self.tempdir, 'value_2'), sol.local_db_path) self.assertEqual('value_1', sol._server_url) sol.close() - @defer.inlineCallbacks + @inlineCallbacks def test_change_passphrase(self): """ Test if passphrase can be changed. @@ -108,7 +107,7 @@ class AuxMethodsTestCase(BaseSoledadTest): sol.change_passphrase(u'654321') sol.close() - with self.assertRaises(SecretsException): + with self.assertRaises(SecretsError): self._soledad_instance( 'leap@leap.se', passphrase=u'123', @@ -124,20 +123,6 @@ class AuxMethodsTestCase(BaseSoledadTest): sol2.close() - def test_change_passphrase_with_short_passphrase_raises(self): - """ - Test if attempt to change passphrase passing a short passphrase - raises. - """ - sol = self._soledad_instance( - 'leap@leap.se', - passphrase=u'123') - # check that soledad complains about new passphrase length - self.assertRaises( - PassphraseTooShort, - sol.change_passphrase, u'54321') - sol.close() - def test_get_passphrase(self): """ Assert passphrase getter works fine. diff --git a/testing/tests/client/test_crypto.py b/testing/tests/client/test_crypto.py index 49a61438..379475cd 100644 --- a/testing/tests/client/test_crypto.py +++ b/testing/tests/client/test_crypto.py @@ -19,9 +19,9 @@ Tests for cryptographic related stuff. """ import binascii import base64 -import hashlib import json import os +import scrypt from io import BytesIO @@ -34,6 +34,7 @@ from cryptography.exceptions import InvalidTag from leap.soledad.common.document import SoledadDocument from test_soledad.util import BaseSoledadTest from leap.soledad.client import _crypto +from leap.soledad.client._secrets import SecretsCrypto from twisted.trial import unittest from twisted.internet import defer @@ -186,99 +187,145 @@ class BlobTestCase(unittest.TestCase): yield crypto.decrypt_doc(doc2) -class RecoveryDocumentTestCase(BaseSoledadTest): - - def test_export_recovery_document_raw(self): - rd = self._soledad.secrets._export_recovery_document() - secret_id = rd[self._soledad.secrets.STORAGE_SECRETS_KEY].items()[0][0] - # assert exported secret is the same - secret = self._soledad.secrets._decrypt_storage_secret_version_1( - rd[self._soledad.secrets.STORAGE_SECRETS_KEY][secret_id]) - self.assertEqual(secret_id, self._soledad.secrets._secret_id) - self.assertEqual(secret, self._soledad.secrets._secrets[secret_id]) - # assert recovery document structure - encrypted_secret = rd[ - self._soledad.secrets.STORAGE_SECRETS_KEY][secret_id] - self.assertTrue(self._soledad.secrets.CIPHER_KEY in encrypted_secret) - self.assertEquals( - _crypto.ENC_METHOD.aes_256_gcm, - encrypted_secret[self._soledad.secrets.CIPHER_KEY]) - self.assertTrue(self._soledad.secrets.LENGTH_KEY in encrypted_secret) - self.assertTrue(self._soledad.secrets.SECRET_KEY in encrypted_secret) - - def test_import_recovery_document(self, cipher='aes256'): - rd = self._soledad.secrets._export_recovery_document(cipher) - s = self._soledad_instance() - s.secrets._import_recovery_document(rd) - s.secrets.set_secret_id(self._soledad.secrets._secret_id) - self.assertEqual(self._soledad.storage_secret, - s.storage_secret, - 'Failed settinng secret for symmetric encryption.') - s.close() - - def test_import_GCM_recovery_document(self): - cipher = self._soledad.secrets.CIPHER_AES256_GCM - self.test_import_recovery_document(cipher) - - def test_import_legacy_CTR_recovery_document(self): - cipher = self._soledad.secrets.CIPHER_AES256 - self.test_import_recovery_document(cipher) +class SecretsCryptoTestCase(unittest.TestCase): + + SECRETS = {'remote': 'a' * 512, 'salt': 'b' * 64, 'local': 'c' * 448} + ENCRYPTED_V2 = { + 'cipher': 'aes_256_gcm', + 'length': 1417, + 'kdf_salt': '3DCkfecls0GcX2RadA04FAC2cqkI+vpGwwCLwffdRI6vpO5SPxaw/eM0/' + 'z3GUADm3If3YCQBldKXNdqHQLsU1Q==\n', + 'iv': 'rRwCDw5Rbp5+J3QwjQ46Hw==', + 'secrets': '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' + '\n', + 'version': 2, + 'kdf': 'scrypt', + 'kdf_length': 32 + } + + ENCRYPTED_V1 = { + 'version': 1, + 'active_secret': 'secret_id', + 'storage_secrets': { + 'secret_id': { + 'kdf': 'scrypt', + 'secret': 'u31ObvxNU8jB0HgMj3TVwQ==:JQwlYq6sAQmHYS3x2CJzObT9h' + 'j1iiHthvrMh887qedNCcOfJyCA3jpRkc0vjd2Qk/2HSJ+JxM2F' + 'MrPzzx5O34EHlgF2scen34guZRRIf42WpnMy+PrL4cnMlZLgCh' + 'H1Jz6wcIMEpU9LQ8OaCShk1/yJ6qcVHOV4DDt3mTF7ttiqI5cp' + 'msaVtxxYCcpxFiWSeSCEgr0h4/Ih1qHuM6vk+CQjf/zg1f/7HR' + 'imIyNYXit9Fw3YTkxBen1wG3f5L7OAODRTuqnWpkQFOmclx050' + 'k0frKRcX6UWhIOWpW2mqJXnvzDtQQVGzqIdSgGTGtUDGQ7Onnc' + 'NkUlSnuVC7PkDNNRuwit3pCB9YWBWyPAQgs0kLqoV4YcuSctz6' + 'SAf76ozdcK5/SrOzutOfyPag4V3AYKMv6rCKALJ10OnFJ61FL9' + 'kd6JZam7WOlEUXyO7Gdgvz+eKiQMTZXbtO2kAKqel513MedPXC' + 'dzajUe1U2JaGg86UdiDWoPYOiWxnAPwfNJk+1QuNy5NZ7PaMtF' + 'IKT3/Xema2U8mufS0FbvJyK2flP1VUWcCzHKTSqX6+kU7UpoWa' + 'hYa7PlO40El+putTQLBmNaEeaWFngO+XB4TReICHSiCdcAb3pw' + 'sabjtxt+OpK4vbj3yBSfpiZTpVbEjt9U/tUpVp/T2M66lMi3ZC' + 'oHLlhu45Zo0aEq3UmQ/WBXu6EkO2eLYz2br9YQwRbSJ6z5CHmu' + 'hjKBQlpvGNfZYObx5lY4o6Ab4f/N8gyukskjmAFAf7Fr8cEog/' + 'oxmbagoCtUGRYJp2paooqH8L6xXp0Y8+23g7WJaAIr1i4V4aKS' + 'r9x7iUK6prcZTtMJZEHCswkLN/+DU6/FX3YZcOjseC+Qv3P+9v' + 'zIDp/92KJzqVqITGwrsc6ZsglMW37qxs6albtw3lMWSHlkcLbj' + 'Xf/iHPeKnb2WNLdkFNQ1J5OaTJR+E1CrXN+pm1JtB6XaUbaLGV' + 'CGUo13lAPVDtXcPbo64kMrQtQu4m9m8X8t8tfuJmINfwBnrKzk' + 'O6pl+LwimFaFEArV6wcaMxmwi0lM7mt4U1u9OIQjghQ/dEmOyV' + 'dZBnvyG7T/oRuLdUyZ/QGXZMlPQ3lAZ0ONn1Mk4bmKToW8ToE8' + 'ylld3rLlWDjjoQP8mP05Izg3mguLHXUhikUL8MD5NdYyeZJ1XZ' + '0OZ5S9uncurYj2ABWJoVaq/tFCdCEo9bbjWsePei26GZjaM3Fx' + 'RkAICXe/bt6/uLgaPZtO+sdARDuU3DRKMIdgM9NBaIn0kC7Wk4' + 'bnYShZ/rbhVt2/ds5XinnDBZsxSR3s553DixJ9v6w9Db++9Stw' + '4DgePd9lLy+6WuVBlKmcNflx9zg7US0AOarX2UNiQ==', + 'kdf_length': 32, + 'kdf_salt': 'MYH68QH48nRFMWH44piFWqBnKtU8KCz6Ajh24otrvzJlqPgB' + 'v6bvFJjRvjRp/0/v1j2nt40RZ6H5hfoKmore0g==\n', + 'length': 1024, + 'cipher': 'aes256', + } + } + } + + 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') + + def test__get_key(self): + salt = 'abc' + expected = scrypt.hash('123', salt, buflen=32) + key = self._crypto._get_key(salt) + self.assertEqual(expected, key) + + def test_encrypt(self): + info = self._crypto.encrypt(self.SECRETS) + self.assertEqual(8, len(info)) + for key, value in [ + ('kdf', 'scrypt'), + ('kdf_salt', None), + ('kdf_length', None), + ('cipher', 'aes_256_gcm'), + ('length', None), + ('iv', None), + ('secrets', None), + ('version', 2)]: + self.assertTrue(key in info) + if value: + self.assertEqual(info[key], value) + + def test__decrypt_v2(self): + encrypted = self.ENCRYPTED_V2 + decrypted = self._crypto.decrypt(encrypted) + self.assertEqual(decrypted, self.SECRETS) + + def test__decrypt_v1(self): + encrypted = self.ENCRYPTED_V1 + decrypted = self._crypto.decrypt(encrypted) + self.assertEqual(decrypted, self.SECRETS) class SoledadSecretsTestCase(BaseSoledadTest): - def test_new_soledad_instance_generates_one_secret(self): - self.assertTrue( - self._soledad.storage_secret is not None, - "Expected secret to be something different than None") - number_of_secrets = len(self._soledad.secrets._secrets) - self.assertTrue( - number_of_secrets == 1, - "Expected exactly 1 secret, got %d instead." % number_of_secrets) - - def test_generated_secret_is_of_correct_type(self): - expected_type = str - self.assertIsInstance( - self._soledad.storage_secret, expected_type, - "Expected secret to be of type %s" % expected_type) - - def test_generated_secret_has_correct_lengt(self): - expected_length = self._soledad.secrets.GEN_SECRET_LENGTH - actual_length = len(self._soledad.storage_secret) - self.assertTrue( - expected_length == actual_length, - "Expected secret with length %d, got %d instead." - % (expected_length, actual_length)) - - def test_generated_secret_id_is_sha256_hash_of_secret(self): - generated = self._soledad.secrets.secret_id - expected = hashlib.sha256(self._soledad.storage_secret).hexdigest() - self.assertTrue( - generated == expected, - "Expeceted generated secret id to be sha256 hash, got something " - "else instead.") - - def test_generate_new_secret_generates_different_secret_id(self): - # generate new secret - secret_id_1 = self._soledad.secrets.secret_id - secret_id_2 = self._soledad.secrets._gen_secret() - self.assertTrue( - len(self._soledad.secrets._secrets) == 2, - "Expected exactly 2 secrets.") - self.assertTrue( - secret_id_1 != secret_id_2, - "Expected IDs of secrets to be distinct.") - self.assertTrue( - secret_id_1 in self._soledad.secrets._secrets, - "Expected to find ID of first secret in Soledad Secrets.") - self.assertTrue( - secret_id_2 in self._soledad.secrets._secrets, - "Expected to find ID of second secret in Soledad Secrets.") - - def test__has_secret(self): - self.assertTrue( - self._soledad._secrets._has_secret(), - "Should have a secret at this point") + def test_generated_secrets_have_correct_length(self): + expected = self._soledad.secrets.lengths + for name, length in expected.iteritems(): + secret = getattr(self._soledad.secrets, name) + self.assertEqual(length, len(secret)) class SoledadCryptoAESTestCase(BaseSoledadTest): diff --git a/testing/tests/client/test_deprecated_crypto.py b/testing/tests/client/test_deprecated_crypto.py index 8ee3735c..8c711c22 100644 --- a/testing/tests/client/test_deprecated_crypto.py +++ b/testing/tests/client/test_deprecated_crypto.py @@ -1,5 +1,7 @@ import json -from twisted.internet import defer + +from pytest import inlineCallbacks + from uuid import uuid4 from urlparse import urljoin @@ -39,7 +41,7 @@ class DeprecatedCryptoTest(SoledadWithCouchServerMixin, TestCaseWithServer): def make_app_with_state(state): return make_token_soledad_app(state) - @defer.inlineCallbacks + @inlineCallbacks def test_touch_updates_remote_representation(self): self.startTwistedServer() user = 'user-' + uuid4().hex diff --git a/testing/tests/client/test_shared_db.py b/testing/tests/client/test_shared_db.py index aac766c2..b045e524 100644 --- a/testing/tests/client/test_shared_db.py +++ b/testing/tests/client/test_shared_db.py @@ -2,7 +2,6 @@ from leap.soledad.common.document import SoledadDocument from leap.soledad.client.shared_db import SoledadSharedDatabase from test_soledad.util import BaseSoledadTest -from test_soledad.util import ADDRESS class SoledadSharedDBTestCase(BaseSoledadTest): @@ -14,37 +13,28 @@ class SoledadSharedDBTestCase(BaseSoledadTest): def setUp(self): BaseSoledadTest.setUp(self) self._shared_db = SoledadSharedDatabase( - 'https://provider/', ADDRESS, document_factory=SoledadDocument, + 'https://provider/', document_factory=SoledadDocument, creds=None) def tearDown(self): BaseSoledadTest.tearDown(self) - def test__get_secrets_from_shared_db(self): + def test__get_remote_doc(self): """ Ensure the shared db is queried with the correct doc_id. """ - doc_id = self._soledad.secrets._shared_db_doc_id() - self._soledad.secrets._get_secrets_from_shared_db() - self.assertTrue( - self._soledad.shared_db.get_doc.assert_called_with( - doc_id) is None, - 'Wrong doc_id when fetching recovery document.') - - def test__put_secrets_in_shared_db(self): + doc_id = self._soledad.secrets.storage._remote_doc_id() + self._soledad.secrets.storage._get_remote_doc() + self._soledad.secrets.storage._shared_db.get_doc.assert_called_with( + doc_id) + + def test_save_remote(self): """ Ensure recovery document is put into shared recover db. """ - doc_id = self._soledad.secrets._shared_db_doc_id() - self._soledad.secrets._put_secrets_in_shared_db() - self.assertTrue( - self._soledad.shared_db.get_doc.assert_called_with( - doc_id) is None, - 'Wrong doc_id when fetching recovery document.') - self.assertTrue( - self._soledad.shared_db.put_doc.assert_called_with( - self._doc_put) is None, - 'Wrong document when putting recovery document.') - self.assertTrue( - self._doc_put.doc_id == doc_id, - 'Wrong doc_id when putting recovery document.') + doc_id = self._soledad.secrets.storage._remote_doc_id() + storage = self._soledad.secrets.storage + storage.save_remote({'content': 'blah'}) + storage._shared_db.get_doc.assert_called_with(doc_id) + storage._shared_db.put_doc.assert_called_with(self._doc_put) + self.assertTrue(self._doc_put.doc_id == doc_id) diff --git a/testing/tests/client/test_signals.py b/testing/tests/client/test_signals.py index 4e9ebfd0..c7609a74 100644 --- a/testing/tests/client/test_signals.py +++ b/testing/tests/client/test_signals.py @@ -20,7 +20,7 @@ class SoledadSignalingTestCase(BaseSoledadTest): def setUp(self): # mock signaling soledad.client.signal = Mock() - soledad.client.secrets.events.emit_async = Mock() + soledad.client._secrets.util.events.emit_async = Mock() # run parent's setUp BaseSoledadTest.setUp(self) @@ -42,55 +42,36 @@ class SoledadSignalingTestCase(BaseSoledadTest): - downloading keys / done downloading keys. - uploading keys / done uploading keys. """ - soledad.client.secrets.events.emit_async.reset_mock() + soledad.client._secrets.util.events.emit_async.reset_mock() # get a fresh instance so it emits all bootstrap signals sol = self._soledad_instance( 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.secrets.events.emit_async.mock_calls.reverse() - soledad.client.secrets.events.emit_async.call_args = \ - soledad.client.secrets.events.emit_async.call_args_list[0] - soledad.client.secrets.events.emit_async.call_args_list.reverse() + soledad.client._secrets.util.events.emit_async.mock_calls.reverse() + soledad.client._secrets.util.events.emit_async.call_args = \ + soledad.client._secrets.util.events.emit_async.call_args_list[0] + soledad.client._secrets.util.events.emit_async.call_args_list.reverse() user_data = {'userid': ADDRESS, 'uuid': ADDRESS} - # downloading keys signals - soledad.client.secrets.events.emit_async.assert_called_with( - catalog.SOLEDAD_DOWNLOADING_KEYS, user_data - ) - self._pop_mock_call(soledad.client.secrets.events.emit_async) - soledad.client.secrets.events.emit_async.assert_called_with( - catalog.SOLEDAD_DONE_DOWNLOADING_KEYS, user_data - ) - # creating keys signals - self._pop_mock_call(soledad.client.secrets.events.emit_async) - soledad.client.secrets.events.emit_async.assert_called_with( - catalog.SOLEDAD_CREATING_KEYS, user_data - ) - self._pop_mock_call(soledad.client.secrets.events.emit_async) - soledad.client.secrets.events.emit_async.assert_called_with( - catalog.SOLEDAD_DONE_CREATING_KEYS, user_data - ) - # downloading once more (inside _put_keys_in_shared_db) - self._pop_mock_call(soledad.client.secrets.events.emit_async) - soledad.client.secrets.events.emit_async.assert_called_with( - catalog.SOLEDAD_DOWNLOADING_KEYS, user_data - ) - self._pop_mock_call(soledad.client.secrets.events.emit_async) - soledad.client.secrets.events.emit_async.assert_called_with( - catalog.SOLEDAD_DONE_DOWNLOADING_KEYS, user_data - ) - # uploading keys signals - self._pop_mock_call(soledad.client.secrets.events.emit_async) - soledad.client.secrets.events.emit_async.assert_called_with( - catalog.SOLEDAD_UPLOADING_KEYS, user_data - ) - self._pop_mock_call(soledad.client.secrets.events.emit_async) - soledad.client.secrets.events.emit_async.assert_called_with( - catalog.SOLEDAD_DONE_UPLOADING_KEYS, user_data - ) + def _assert(*args, **kwargs): + mocked = soledad.client._secrets.util.events.emit_async + mocked.assert_called_with(*args) + pop = kwargs.get('pop') + if pop or pop is None: + self._pop_mock_call(mocked) + + _assert(catalog.SOLEDAD_DOWNLOADING_KEYS, user_data) + _assert(catalog.SOLEDAD_DONE_DOWNLOADING_KEYS, user_data) + _assert(catalog.SOLEDAD_CREATING_KEYS, user_data) + _assert(catalog.SOLEDAD_DONE_CREATING_KEYS, user_data) + _assert(catalog.SOLEDAD_UPLOADING_KEYS, user_data) + _assert(catalog.SOLEDAD_DOWNLOADING_KEYS, user_data) + _assert(catalog.SOLEDAD_DONE_DOWNLOADING_KEYS, user_data) + _assert(catalog.SOLEDAD_DONE_UPLOADING_KEYS, user_data, pop=False) + sol.close() def test_stage2_bootstrap_signals(self): @@ -101,11 +82,11 @@ class SoledadSignalingTestCase(BaseSoledadTest): # get existing instance so we have access to keys sol = self._soledad_instance() # create a document with secrets - doc = SoledadDocument(doc_id=sol.secrets._shared_db_doc_id()) - doc.content = sol.secrets._export_recovery_document() + doc = SoledadDocument(doc_id=sol.secrets.storage._remote_doc_id()) + doc.content = sol.secrets.crypto.encrypt(sol.secrets._secrets) sol.close() # reset mock - soledad.client.secrets.events.emit_async.reset_mock() + soledad.client._secrets.util.events.emit_async.reset_mock() # get a fresh instance so it emits all bootstrap signals shared_db = self.get_default_shared_mock(get_doc_return_value=doc) sol = self._soledad_instance( @@ -114,20 +95,23 @@ class SoledadSignalingTestCase(BaseSoledadTest): shared_db_class=shared_db) # reverse call order so we can verify in the order the signals were # expected - soledad.client.secrets.events.emit_async.mock_calls.reverse() - soledad.client.secrets.events.emit_async.call_args = \ - soledad.client.secrets.events.emit_async.call_args_list[0] - soledad.client.secrets.events.emit_async.call_args_list.reverse() + mocked = soledad.client._secrets.util.events.emit_async + mocked.mock_calls.reverse() + mocked.call_args = mocked.call_args_list[0] + mocked.call_args_list.reverse() + + def _assert(*args, **kwargs): + mocked = soledad.client._secrets.util.events.emit_async + mocked.assert_called_with(*args) + pop = kwargs.get('pop') + if pop or pop is None: + self._pop_mock_call(mocked) + # assert download keys signals - soledad.client.secrets.events.emit_async.assert_called_with( - catalog.SOLEDAD_DOWNLOADING_KEYS, - {'userid': ADDRESS, 'uuid': ADDRESS} - ) - self._pop_mock_call(soledad.client.secrets.events.emit_async) - soledad.client.secrets.events.emit_async.assert_called_with( - catalog.SOLEDAD_DONE_DOWNLOADING_KEYS, - {'userid': ADDRESS, 'uuid': ADDRESS}, - ) + user_data = {'userid': ADDRESS, 'uuid': ADDRESS} + _assert(catalog.SOLEDAD_DOWNLOADING_KEYS, user_data) + _assert(catalog.SOLEDAD_DONE_DOWNLOADING_KEYS, user_data, pop=False) + sol.close() def test_stage1_bootstrap_signals(self): diff --git a/testing/tests/sync/test_sync_target.py b/testing/tests/sync/test_sync_target.py index 6ce9a5c5..302a16b8 100644 --- a/testing/tests/sync/test_sync_target.py +++ b/testing/tests/sync/test_sync_target.py @@ -838,7 +838,7 @@ class TestSoledadDbSync( # already created on some setUp method. import binascii tohex = binascii.b2a_hex - key = tohex(self._soledad.secrets.get_local_storage_key()) + key = tohex(self._soledad.secrets.local) dbpath = self._soledad._local_db_path self.opts = SQLCipherOptions( -- cgit v1.2.3 From 8d9782c689daa14aca495d7b6b2598b2743c4e7c Mon Sep 17 00:00:00 2001 From: drebs Date: Sat, 24 Dec 2016 14:05:15 -0200 Subject: [bug] use derived key for local storage --- testing/tests/client/test_crypto.py | 82 +++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 39 deletions(-) (limited to 'testing/tests') diff --git a/testing/tests/client/test_crypto.py b/testing/tests/client/test_crypto.py index 379475cd..d161052a 100644 --- a/testing/tests/client/test_crypto.py +++ b/testing/tests/client/test_crypto.py @@ -189,47 +189,51 @@ class BlobTestCase(unittest.TestCase): class SecretsCryptoTestCase(unittest.TestCase): - SECRETS = {'remote': 'a' * 512, 'salt': 'b' * 64, 'local': 'c' * 448} + SECRETS = { + 'remote_secret': 'a' * 512, + 'local_salt': 'b' * 64, + 'local_secret': 'c' * 448 + } ENCRYPTED_V2 = { 'cipher': 'aes_256_gcm', - 'length': 1417, - 'kdf_salt': '3DCkfecls0GcX2RadA04FAC2cqkI+vpGwwCLwffdRI6vpO5SPxaw/eM0/' - 'z3GUADm3If3YCQBldKXNdqHQLsU1Q==\n', - 'iv': 'rRwCDw5Rbp5+J3QwjQ46Hw==', - 'secrets': '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' - '\n', + 'length': 1437, + 'kdf_salt': 'TSgNLeAGFeITeSgNzmYZHh+mzmkZPOqao7CAV/tx3KZCLwsrT0HmWtVK3' + 'TyWHWNgVdeamMZYRuvZavE2sp0DGw==\n', + 'iv': 'TKZQKIlRgdnXFhJf08qswg==', + 'secrets': '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' + 'XYf+cEX6gp92L9rTo0FCz3Hw==\n', 'version': 2, 'kdf': 'scrypt', 'kdf_length': 32 -- cgit v1.2.3 From fffaf266ef401cc7b081085929f42f6ca8af73b6 Mon Sep 17 00:00:00 2001 From: drebs Date: Sat, 24 Dec 2016 14:17:46 -0200 Subject: [test] fix test after secrets refactor --- testing/tests/sync/test_sync_target.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'testing/tests') diff --git a/testing/tests/sync/test_sync_target.py b/testing/tests/sync/test_sync_target.py index 302a16b8..34702362 100644 --- a/testing/tests/sync/test_sync_target.py +++ b/testing/tests/sync/test_sync_target.py @@ -838,7 +838,7 @@ class TestSoledadDbSync( # already created on some setUp method. import binascii tohex = binascii.b2a_hex - key = tohex(self._soledad.secrets.local) + key = tohex(self._soledad.secrets.local_key) dbpath = self._soledad._local_db_path self.opts = SQLCipherOptions( -- cgit v1.2.3 From 71ab022aa12d5754d5845eae757b2f3531b4e50a Mon Sep 17 00:00:00 2001 From: drebs Date: Sat, 21 Jan 2017 20:13:27 -0200 Subject: [test] move client secrets tests to its own file --- testing/tests/client/test_crypto.py | 138 ------------------------------ testing/tests/client/test_secrets.py | 161 +++++++++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+), 138 deletions(-) create mode 100644 testing/tests/client/test_secrets.py (limited to 'testing/tests') diff --git a/testing/tests/client/test_crypto.py b/testing/tests/client/test_crypto.py index d161052a..5411a4e8 100644 --- a/testing/tests/client/test_crypto.py +++ b/testing/tests/client/test_crypto.py @@ -21,7 +21,6 @@ import binascii import base64 import json import os -import scrypt from io import BytesIO @@ -34,7 +33,6 @@ from cryptography.exceptions import InvalidTag from leap.soledad.common.document import SoledadDocument from test_soledad.util import BaseSoledadTest from leap.soledad.client import _crypto -from leap.soledad.client._secrets import SecretsCrypto from twisted.trial import unittest from twisted.internet import defer @@ -187,142 +185,6 @@ class BlobTestCase(unittest.TestCase): yield crypto.decrypt_doc(doc2) -class SecretsCryptoTestCase(unittest.TestCase): - - SECRETS = { - 'remote_secret': 'a' * 512, - 'local_salt': 'b' * 64, - 'local_secret': 'c' * 448 - } - ENCRYPTED_V2 = { - 'cipher': 'aes_256_gcm', - 'length': 1437, - 'kdf_salt': 'TSgNLeAGFeITeSgNzmYZHh+mzmkZPOqao7CAV/tx3KZCLwsrT0HmWtVK3' - 'TyWHWNgVdeamMZYRuvZavE2sp0DGw==\n', - 'iv': 'TKZQKIlRgdnXFhJf08qswg==', - 'secrets': 'ZNZRi72VDtwZqyuU+uf3yzZt23vCtMS3Ki2bnZyeHUOSGVweJeDadF4oqE' - 'BW87NN00j9E49BzyzLr9SNgwZjPp0wlUm7kt+s8EUfJUdH8nxaQ+9iqGXM' - 'cCHmBM8L8DRN2m3BrPGx7m+QGlN9sbrRpl7fqc46RWcYuTEpm4upjdtI7O' - 'jDd0JG3C0rUzIuKJn9w4rEpX3tLEKXVdZfLvRXS5roR0cauazsDO69E13q' - 'a01vDuY+UJ+buLQ3FluPnnk8QE7ztPVUmRJJ76yAIhjVX9owiwlp9GnUJY' - 'sETRCqdRSTwUcHIkzVR0zAvtxTX7eGTitzf4gCYEC4T9v5N/jHxEfPdx28' - 'MM4KShWN2nFxNFQLQUpMN2OrM7UyUw+DQ3ydqBeBPKPHRN5s05kIK7P/Ra' - 'aLNcrJWa7DopLbgLlei0Jd7S4sjv1ufaRY7v0qJaVkhh/VaCylTSVw1rv5' - 'YzSWcHHcLuC0R8xLadz6T+EpsVYxgPYCS7w5xoE82zwNQzw/EBxLIcyLPl' - 'ipKnr2dttrmm3KXUOT1IdbSbI5elF6yQTAusdqiXuypey+MDqHYWEYWkCn' - 'e9/uGM9FjklDLE0RtPEDxhq64tw6u2Xu7RzDzyQDI8EIoTdU+4zEMTnelZ' - 'fKEwdG58EDxTXfUk6IDcRUupz3YuToSMhIOkqgXnbWl/nrK0O9v4JMhQjI' - 'r+oPICYfFr14kvJXBsfntILTJCxzbqTQcNba3jc8rGqCZ6gM0u4PndwTG2' - 'UiCqPU2HMnWvVGQOXeLdQY+EqqXQiRDi0DrDmkVwFf+27dPXxmZ43C48W3' - 'lMhTKXl0rdBFnOD5jiMh0X6q/KYXonyEtMZMsjT7dFePcCy4wQRhuut+ac' - '/TJWyrr+/IB45E+LZbhV7xCy1dYsbdb52jTRJFpaQ83sj6Iv6SYdiqqXzL' - 'F5JGMyuovTjwAoIIQzpLv36xY2wGGAH1V8c7QmDR2qumXrHD9R68WjBoSY' - '7IFM0TFAGZNun56y/zQ4r8yOMSAId+j4kuRH0fENEi0FJ+SpmSdHfpvBhE' - 'MdGh927E9enEYWmUQMmkxXIw6E+O3cmOWt2hsMbUAikDCpQOnVP2BD55HT' - '6FfbW7ITVwPINHYexmy2Xcm8H5zzGFSp+uYIyPBYDKA+VJ+QQI8bud9K+T' - 'NBybUv9u6LbB6BsLpwLoxMPJu0WsN2HpmLYgrg2ML1huMF1OtaGRuUr2PL' - 'NBaZaL6VOztYrVtQG1+tNyRxn8XQTtx0l6n+EihGVe9Sk5XF6DJA9ZN7uO' - 'svTUFJ5qG3Erf4AmbUJWoOR/NvapBtifidM7gPZZ6NqBs6v72rU1pGy+p7' - 'o84KrmB2MNf3yJ0BvKxPvFmltF3Dc7LB5TN8ycbmFM6hgrLvvhPxiHEnG/' - '8Qcrg0nUXOipFGNgZEU7t7Mz6RJ189Z2Kx1HVGrkAzEgqwZYqijAPlsgzO' - 'bg6DwzwC7stolQWGCDQUtJVlE8FZ/Up8zFYYZKn52WzjmSN4/hHhEvdkck' - 'Nez/JVev6fMcVrgdrTZ+uCwxjN/4xPdgog2HV470ea1bvIkSNOOrhm194M' - '40GmvmBhiSSMjdRQCQtM0t9bUuSQLPDzEiCA9QaLyygtlz9uRR/dXgkEg0' - 'J4YNpZvhE0wbyp4GHytbPaAmrcd7im9+buTuMwhXpZl0stmfkJxVHJSZ8Y' - 'IHakHs3W1fdYyI3wxGpel/9eYO3ISukolwrHXESP65wVNKfBwbqVJzQmts' - 'pyDBOI6DcLKZfE1EVg0+uwQ/5PKZbn0TwlXO1YE3NL3mAply3zQR9hyBrY' - '6f1jkHVD3irIlWkSiPJsP8sW+nrK8c/Ha8F+dua6DTZmg594OIaQj8mPiY' - 'GcIusiARWocR5/MmSjupGOgFx4HtmckTJtAta3XP4elOx04teH/P9Cgr1x' - 'XYf+cEX6gp92L9rTo0FCz3Hw==\n', - 'version': 2, - 'kdf': 'scrypt', - 'kdf_length': 32 - } - - ENCRYPTED_V1 = { - 'version': 1, - 'active_secret': 'secret_id', - 'storage_secrets': { - 'secret_id': { - 'kdf': 'scrypt', - 'secret': 'u31ObvxNU8jB0HgMj3TVwQ==:JQwlYq6sAQmHYS3x2CJzObT9h' - 'j1iiHthvrMh887qedNCcOfJyCA3jpRkc0vjd2Qk/2HSJ+JxM2F' - 'MrPzzx5O34EHlgF2scen34guZRRIf42WpnMy+PrL4cnMlZLgCh' - 'H1Jz6wcIMEpU9LQ8OaCShk1/yJ6qcVHOV4DDt3mTF7ttiqI5cp' - 'msaVtxxYCcpxFiWSeSCEgr0h4/Ih1qHuM6vk+CQjf/zg1f/7HR' - 'imIyNYXit9Fw3YTkxBen1wG3f5L7OAODRTuqnWpkQFOmclx050' - 'k0frKRcX6UWhIOWpW2mqJXnvzDtQQVGzqIdSgGTGtUDGQ7Onnc' - 'NkUlSnuVC7PkDNNRuwit3pCB9YWBWyPAQgs0kLqoV4YcuSctz6' - 'SAf76ozdcK5/SrOzutOfyPag4V3AYKMv6rCKALJ10OnFJ61FL9' - 'kd6JZam7WOlEUXyO7Gdgvz+eKiQMTZXbtO2kAKqel513MedPXC' - 'dzajUe1U2JaGg86UdiDWoPYOiWxnAPwfNJk+1QuNy5NZ7PaMtF' - 'IKT3/Xema2U8mufS0FbvJyK2flP1VUWcCzHKTSqX6+kU7UpoWa' - 'hYa7PlO40El+putTQLBmNaEeaWFngO+XB4TReICHSiCdcAb3pw' - 'sabjtxt+OpK4vbj3yBSfpiZTpVbEjt9U/tUpVp/T2M66lMi3ZC' - 'oHLlhu45Zo0aEq3UmQ/WBXu6EkO2eLYz2br9YQwRbSJ6z5CHmu' - 'hjKBQlpvGNfZYObx5lY4o6Ab4f/N8gyukskjmAFAf7Fr8cEog/' - 'oxmbagoCtUGRYJp2paooqH8L6xXp0Y8+23g7WJaAIr1i4V4aKS' - 'r9x7iUK6prcZTtMJZEHCswkLN/+DU6/FX3YZcOjseC+Qv3P+9v' - 'zIDp/92KJzqVqITGwrsc6ZsglMW37qxs6albtw3lMWSHlkcLbj' - 'Xf/iHPeKnb2WNLdkFNQ1J5OaTJR+E1CrXN+pm1JtB6XaUbaLGV' - 'CGUo13lAPVDtXcPbo64kMrQtQu4m9m8X8t8tfuJmINfwBnrKzk' - 'O6pl+LwimFaFEArV6wcaMxmwi0lM7mt4U1u9OIQjghQ/dEmOyV' - 'dZBnvyG7T/oRuLdUyZ/QGXZMlPQ3lAZ0ONn1Mk4bmKToW8ToE8' - 'ylld3rLlWDjjoQP8mP05Izg3mguLHXUhikUL8MD5NdYyeZJ1XZ' - '0OZ5S9uncurYj2ABWJoVaq/tFCdCEo9bbjWsePei26GZjaM3Fx' - 'RkAICXe/bt6/uLgaPZtO+sdARDuU3DRKMIdgM9NBaIn0kC7Wk4' - 'bnYShZ/rbhVt2/ds5XinnDBZsxSR3s553DixJ9v6w9Db++9Stw' - '4DgePd9lLy+6WuVBlKmcNflx9zg7US0AOarX2UNiQ==', - 'kdf_length': 32, - 'kdf_salt': 'MYH68QH48nRFMWH44piFWqBnKtU8KCz6Ajh24otrvzJlqPgB' - 'v6bvFJjRvjRp/0/v1j2nt40RZ6H5hfoKmore0g==\n', - 'length': 1024, - 'cipher': 'aes256', - } - } - } - - 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') - - def test__get_key(self): - salt = 'abc' - expected = scrypt.hash('123', salt, buflen=32) - key = self._crypto._get_key(salt) - self.assertEqual(expected, key) - - def test_encrypt(self): - info = self._crypto.encrypt(self.SECRETS) - self.assertEqual(8, len(info)) - for key, value in [ - ('kdf', 'scrypt'), - ('kdf_salt', None), - ('kdf_length', None), - ('cipher', 'aes_256_gcm'), - ('length', None), - ('iv', None), - ('secrets', None), - ('version', 2)]: - self.assertTrue(key in info) - if value: - self.assertEqual(info[key], value) - - def test__decrypt_v2(self): - encrypted = self.ENCRYPTED_V2 - decrypted = self._crypto.decrypt(encrypted) - self.assertEqual(decrypted, self.SECRETS) - - def test__decrypt_v1(self): - encrypted = self.ENCRYPTED_V1 - decrypted = self._crypto.decrypt(encrypted) - self.assertEqual(decrypted, self.SECRETS) - - class SoledadSecretsTestCase(BaseSoledadTest): def test_generated_secrets_have_correct_length(self): diff --git a/testing/tests/client/test_secrets.py b/testing/tests/client/test_secrets.py new file mode 100644 index 00000000..bbeb1fc2 --- /dev/null +++ b/testing/tests/client/test_secrets.py @@ -0,0 +1,161 @@ +# -*- CODing: utf-8 -*- +# test_secrets.py +# Copyright (C) 2017 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 . +""" +Tests for secrets encryption and decryption. +""" +import scrypt + +from twisted.trial import unittest + +from leap.soledad.client._crypto import ENC_METHOD +from leap.soledad.client._secrets import SecretsCrypto + + +class SecretsCryptoTestCase(unittest.TestCase): + + SECRETS = { + 'remote_secret': 'a' * 512, + 'local_salt': 'b' * 64, + 'local_secret': 'c' * 448 + } + ENCRYPTED_V2 = { + 'cipher': 2, + 'length': 1437, + 'kdf_salt': 'TSgNLeAGFeITeSgNzmYZHh+mzmkZPOqao7CAV/tx3KZCLwsrT0HmWtVK3' + 'TyWHWNgVdeamMZYRuvZavE2sp0DGw==\n', + 'iv': 'TKZQKIlRgdnXFhJf08qswg==', + 'secrets': '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' + 'XYf+cEX6gp92L9rTo0FCz3Hw==\n', + 'version': 2, + 'kdf': 'scrypt', + 'kdf_length': 32 + } + + ENCRYPTED_V1 = { + 'version': 1, + 'active_secret': 'secret_id', + 'storage_secrets': { + 'secret_id': { + 'kdf': 'scrypt', + 'secret': 'u31ObvxNU8jB0HgMj3TVwQ==:JQwlYq6sAQmHYS3x2CJzObT9h' + 'j1iiHthvrMh887qedNCcOfJyCA3jpRkc0vjd2Qk/2HSJ+JxM2F' + 'MrPzzx5O34EHlgF2scen34guZRRIf42WpnMy+PrL4cnMlZLgCh' + 'H1Jz6wcIMEpU9LQ8OaCShk1/yJ6qcVHOV4DDt3mTF7ttiqI5cp' + 'msaVtxxYCcpxFiWSeSCEgr0h4/Ih1qHuM6vk+CQjf/zg1f/7HR' + 'imIyNYXit9Fw3YTkxBen1wG3f5L7OAODRTuqnWpkQFOmclx050' + 'k0frKRcX6UWhIOWpW2mqJXnvzDtQQVGzqIdSgGTGtUDGQ7Onnc' + 'NkUlSnuVC7PkDNNRuwit3pCB9YWBWyPAQgs0kLqoV4YcuSctz6' + 'SAf76ozdcK5/SrOzutOfyPag4V3AYKMv6rCKALJ10OnFJ61FL9' + 'kd6JZam7WOlEUXyO7Gdgvz+eKiQMTZXbtO2kAKqel513MedPXC' + 'dzajUe1U2JaGg86UdiDWoPYOiWxnAPwfNJk+1QuNy5NZ7PaMtF' + 'IKT3/Xema2U8mufS0FbvJyK2flP1VUWcCzHKTSqX6+kU7UpoWa' + 'hYa7PlO40El+putTQLBmNaEeaWFngO+XB4TReICHSiCdcAb3pw' + 'sabjtxt+OpK4vbj3yBSfpiZTpVbEjt9U/tUpVp/T2M66lMi3ZC' + 'oHLlhu45Zo0aEq3UmQ/WBXu6EkO2eLYz2br9YQwRbSJ6z5CHmu' + 'hjKBQlpvGNfZYObx5lY4o6Ab4f/N8gyukskjmAFAf7Fr8cEog/' + 'oxmbagoCtUGRYJp2paooqH8L6xXp0Y8+23g7WJaAIr1i4V4aKS' + 'r9x7iUK6prcZTtMJZEHCswkLN/+DU6/FX3YZcOjseC+Qv3P+9v' + 'zIDp/92KJzqVqITGwrsc6ZsglMW37qxs6albtw3lMWSHlkcLbj' + 'Xf/iHPeKnb2WNLdkFNQ1J5OaTJR+E1CrXN+pm1JtB6XaUbaLGV' + 'CGUo13lAPVDtXcPbo64kMrQtQu4m9m8X8t8tfuJmINfwBnrKzk' + 'O6pl+LwimFaFEArV6wcaMxmwi0lM7mt4U1u9OIQjghQ/dEmOyV' + 'dZBnvyG7T/oRuLdUyZ/QGXZMlPQ3lAZ0ONn1Mk4bmKToW8ToE8' + 'ylld3rLlWDjjoQP8mP05Izg3mguLHXUhikUL8MD5NdYyeZJ1XZ' + '0OZ5S9uncurYj2ABWJoVaq/tFCdCEo9bbjWsePei26GZjaM3Fx' + 'RkAICXe/bt6/uLgaPZtO+sdARDuU3DRKMIdgM9NBaIn0kC7Wk4' + 'bnYShZ/rbhVt2/ds5XinnDBZsxSR3s553DixJ9v6w9Db++9Stw' + '4DgePd9lLy+6WuVBlKmcNflx9zg7US0AOarX2UNiQ==', + 'kdf_length': 32, + 'kdf_salt': 'MYH68QH48nRFMWH44piFWqBnKtU8KCz6Ajh24otrvzJlqPgB' + 'v6bvFJjRvjRp/0/v1j2nt40RZ6H5hfoKmore0g==\n', + 'length': 1024, + 'cipher': 'aes256', + } + } + } + + 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') + + def test__get_key(self): + salt = 'abc' + expected = scrypt.hash('123', salt, buflen=32) + key = self._crypto._get_key(salt) + self.assertEqual(expected, key) + + def test_encrypt(self): + info = self._crypto.encrypt(self.SECRETS) + self.assertEqual(8, len(info)) + for key, value in [ + ('kdf', 'scrypt'), + ('kdf_salt', None), + ('kdf_length', None), + ('cipher', ENC_METHOD.aes_256_gcm), + ('length', None), + ('iv', None), + ('secrets', None), + ('version', 2)]: + self.assertTrue(key in info) + if value: + self.assertEqual(info[key], value) + + def test__decrypt_v2(self): + encrypted = self.ENCRYPTED_V2 + decrypted = self._crypto.decrypt(encrypted) + self.assertEqual(decrypted, self.SECRETS) + + def test__decrypt_v1(self): + encrypted = self.ENCRYPTED_V1 + decrypted = self._crypto.decrypt(encrypted) + self.assertEqual(decrypted, self.SECRETS) -- cgit v1.2.3 From a39af0e003ba95c9b7ab554aa4a4c5ce316a43c7 Mon Sep 17 00:00:00 2001 From: drebs Date: Sun, 18 Dec 2016 12:56:21 -0200 Subject: [bug] disallow all requests to "user-{uuid}/" --- testing/tests/server/test_server.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'testing/tests') diff --git a/testing/tests/server/test_server.py b/testing/tests/server/test_server.py index 6710caaf..cae2e75c 100644 --- a/testing/tests/server/test_server.py +++ b/testing/tests/server/test_server.py @@ -110,7 +110,7 @@ class ServerAuthorizationTestCase(BaseSoledadTest): /shared-db/docs | - /shared-db/doc/{id} | GET, PUT, DELETE /shared-db/sync-from/{source} | - - /user-db | GET, PUT, DELETE + /user-db | - /user-db/docs | - /user-db/doc/{id} | - /user-db/sync-from/{source} | GET, PUT, POST @@ -174,13 +174,13 @@ class ServerAuthorizationTestCase(BaseSoledadTest): authmap.is_authorized( self._make_environ('/shared/sync-from/x', 'POST'))) # test user-db database resource auth - self.assertTrue( + self.assertFalse( authmap.is_authorized( self._make_environ('/%s' % dbname, 'GET'))) - self.assertTrue( + self.assertFalse( authmap.is_authorized( self._make_environ('/%s' % dbname, 'PUT'))) - self.assertTrue( + self.assertFalse( authmap.is_authorized( self._make_environ('/%s' % dbname, 'DELETE'))) self.assertFalse( -- cgit v1.2.3 From e73d36621052a69aae327200c063ac1689bcf9e0 Mon Sep 17 00:00:00 2001 From: drebs Date: Sun, 18 Dec 2016 14:21:54 -0200 Subject: [feat] reuse the url mapper instead of creating it for every request --- testing/tests/server/test_server.py | 251 ++++++++++++++++-------------------- 1 file changed, 114 insertions(+), 137 deletions(-) (limited to 'testing/tests') diff --git a/testing/tests/server/test_server.py b/testing/tests/server/test_server.py index cae2e75c..09242736 100644 --- a/testing/tests/server/test_server.py +++ b/testing/tests/server/test_server.py @@ -45,7 +45,7 @@ from leap.soledad.client import _crypto from leap.soledad.client import Soledad from leap.soledad.server.config import load_configuration from leap.soledad.server.config import CONFIG_DEFAULTS -from leap.soledad.server.auth import URLToAuthorization +from leap.soledad.server.auth import URLMapper from leap.soledad.server.auth import SoledadTokenAuthMiddleware @@ -116,175 +116,152 @@ class ServerAuthorizationTestCase(BaseSoledadTest): /user-db/sync-from/{source} | GET, PUT, POST """ uuid = uuid4().hex - authmap = URLToAuthorization(uuid,) - dbname = authmap._user_db_name + urlmap = URLMapper() + dbname = 'user-%s' % uuid + # test global auth - self.assertTrue( - authmap.is_authorized(self._make_environ('/', 'GET'))) + match = urlmap.match(self._make_environ('/', 'GET')) + # test shared-db database resource auth - self.assertTrue( - authmap.is_authorized( - self._make_environ('/shared', 'GET'))) - self.assertFalse( - authmap.is_authorized( + match = urlmap.match( + self._make_environ('/shared', 'GET')) + self.assertIsNotNone(match) + + self.assertIsNone( + urlmap.match( self._make_environ('/shared', 'PUT'))) - self.assertFalse( - authmap.is_authorized( + + self.assertIsNone( + urlmap.match( self._make_environ('/shared', 'DELETE'))) - self.assertFalse( - authmap.is_authorized( + + self.assertIsNone( + urlmap.match( self._make_environ('/shared', 'POST'))) + # test shared-db docs resource auth - self.assertFalse( - authmap.is_authorized( + self.assertIsNone( + urlmap.match( self._make_environ('/shared/docs', 'GET'))) - self.assertFalse( - authmap.is_authorized( + + self.assertIsNone( + urlmap.match( self._make_environ('/shared/docs', 'PUT'))) - self.assertFalse( - authmap.is_authorized( + + self.assertIsNone( + urlmap.match( self._make_environ('/shared/docs', 'DELETE'))) - self.assertFalse( - authmap.is_authorized( + + self.assertIsNone( + urlmap.match( self._make_environ('/shared/docs', 'POST'))) + # test shared-db doc resource auth - self.assertTrue( - authmap.is_authorized( - self._make_environ('/shared/doc/x', 'GET'))) - self.assertTrue( - authmap.is_authorized( - self._make_environ('/shared/doc/x', 'PUT'))) - self.assertTrue( - authmap.is_authorized( - self._make_environ('/shared/doc/x', 'DELETE'))) - self.assertFalse( - authmap.is_authorized( + match = urlmap.match( + self._make_environ('/shared/doc/x', 'GET')) + self.assertIsNotNone(match) + self.assertEqual('x', match.get('id')) + + match = urlmap.match( + self._make_environ('/shared/doc/x', 'PUT')) + self.assertIsNotNone(match) + self.assertEqual('x', match.get('id')) + + match = urlmap.match( + self._make_environ('/shared/doc/x', 'DELETE')) + self.assertEqual('x', match.get('id')) + + self.assertIsNone( + urlmap.match( self._make_environ('/shared/doc/x', 'POST'))) + # test shared-db sync resource auth - self.assertFalse( - authmap.is_authorized( + self.assertIsNone( + urlmap.match( self._make_environ('/shared/sync-from/x', 'GET'))) - self.assertFalse( - authmap.is_authorized( + + self.assertIsNone( + urlmap.match( self._make_environ('/shared/sync-from/x', 'PUT'))) - self.assertFalse( - authmap.is_authorized( + + self.assertIsNone( + urlmap.match( self._make_environ('/shared/sync-from/x', 'DELETE'))) - self.assertFalse( - authmap.is_authorized( + + self.assertIsNone( + urlmap.match( self._make_environ('/shared/sync-from/x', 'POST'))) + # test user-db database resource auth - self.assertFalse( - authmap.is_authorized( + self.assertIsNone( + urlmap.match( self._make_environ('/%s' % dbname, 'GET'))) - self.assertFalse( - authmap.is_authorized( + + self.assertIsNone( + urlmap.match( self._make_environ('/%s' % dbname, 'PUT'))) - self.assertFalse( - authmap.is_authorized( + + self.assertIsNone( + urlmap.match( self._make_environ('/%s' % dbname, 'DELETE'))) - self.assertFalse( - authmap.is_authorized( + + self.assertIsNone( + urlmap.match( self._make_environ('/%s' % dbname, 'POST'))) + # test user-db docs resource auth - self.assertFalse( - authmap.is_authorized( + self.assertIsNone( + urlmap.match( self._make_environ('/%s/docs' % dbname, 'GET'))) - self.assertFalse( - authmap.is_authorized( + + self.assertIsNone( + urlmap.match( self._make_environ('/%s/docs' % dbname, 'PUT'))) - self.assertFalse( - authmap.is_authorized( + + self.assertIsNone( + urlmap.match( self._make_environ('/%s/docs' % dbname, 'DELETE'))) - self.assertFalse( - authmap.is_authorized( + + self.assertIsNone( + urlmap.match( self._make_environ('/%s/docs' % dbname, 'POST'))) + # test user-db doc resource auth - self.assertFalse( - authmap.is_authorized( + self.assertIsNone( + urlmap.match( self._make_environ('/%s/doc/x' % dbname, 'GET'))) - self.assertFalse( - authmap.is_authorized( + + self.assertIsNone( + urlmap.match( self._make_environ('/%s/doc/x' % dbname, 'PUT'))) - self.assertFalse( - authmap.is_authorized( + + self.assertIsNone( + urlmap.match( self._make_environ('/%s/doc/x' % dbname, 'DELETE'))) - self.assertFalse( - authmap.is_authorized( + + self.assertIsNone( + urlmap.match( self._make_environ('/%s/doc/x' % dbname, 'POST'))) + # test user-db sync resource auth - self.assertTrue( - authmap.is_authorized( - self._make_environ('/%s/sync-from/x' % dbname, 'GET'))) - self.assertTrue( - authmap.is_authorized( - self._make_environ('/%s/sync-from/x' % dbname, 'PUT'))) - self.assertFalse( - authmap.is_authorized( - self._make_environ('/%s/sync-from/x' % dbname, 'DELETE'))) - self.assertTrue( - authmap.is_authorized( - self._make_environ('/%s/sync-from/x' % dbname, 'POST'))) - - def test_verify_action_with_wrong_dbnames(self): - """ - Test if authorization fails for a wrong dbname. - """ - uuid = uuid4().hex - authmap = URLToAuthorization(uuid) - dbname = 'somedb' - # test wrong-db database resource auth - self.assertFalse( - authmap.is_authorized( - self._make_environ('/%s' % dbname, 'GET'))) - self.assertFalse( - authmap.is_authorized( - self._make_environ('/%s' % dbname, 'PUT'))) - self.assertFalse( - authmap.is_authorized( - self._make_environ('/%s' % dbname, 'DELETE'))) - self.assertFalse( - authmap.is_authorized( - self._make_environ('/%s' % dbname, 'POST'))) - # test wrong-db docs resource auth - self.assertFalse( - authmap.is_authorized( - self._make_environ('/%s/docs' % dbname, 'GET'))) - self.assertFalse( - authmap.is_authorized( - self._make_environ('/%s/docs' % dbname, 'PUT'))) - self.assertFalse( - authmap.is_authorized( - self._make_environ('/%s/docs' % dbname, 'DELETE'))) - self.assertFalse( - authmap.is_authorized( - self._make_environ('/%s/docs' % dbname, 'POST'))) - # test wrong-db doc resource auth - self.assertFalse( - authmap.is_authorized( - self._make_environ('/%s/doc/x' % dbname, 'GET'))) - self.assertFalse( - authmap.is_authorized( - self._make_environ('/%s/doc/x' % dbname, 'PUT'))) - self.assertFalse( - authmap.is_authorized( - self._make_environ('/%s/doc/x' % dbname, 'DELETE'))) - self.assertFalse( - authmap.is_authorized( - self._make_environ('/%s/doc/x' % dbname, 'POST'))) - # test wrong-db sync resource auth - self.assertFalse( - authmap.is_authorized( - self._make_environ('/%s/sync-from/x' % dbname, 'GET'))) - self.assertFalse( - authmap.is_authorized( - self._make_environ('/%s/sync-from/x' % dbname, 'PUT'))) - self.assertFalse( - authmap.is_authorized( - self._make_environ('/%s/sync-from/x' % dbname, 'DELETE'))) - self.assertFalse( - authmap.is_authorized( - self._make_environ('/%s/sync-from/x' % dbname, 'POST'))) + match = urlmap.match( + self._make_environ('/%s/sync-from/x' % dbname, 'GET')) + self.assertEqual(uuid, match.get('uuid')) + self.assertEqual('x', match.get('source_replica_uid')) + + match = urlmap.match( + self._make_environ('/%s/sync-from/x' % dbname, 'PUT')) + self.assertEqual(uuid, match.get('uuid')) + self.assertEqual('x', match.get('source_replica_uid')) + + match = urlmap.match( + self._make_environ('/%s/sync-from/x' % dbname, 'DELETE')) + self.assertIsNone(match) + + match = urlmap.match( + self._make_environ('/%s/sync-from/x' % dbname, 'POST')) + self.assertEqual(uuid, match.get('uuid')) + self.assertEqual('x', match.get('source_replica_uid')) @pytest.mark.usefixtures("method_tmpdir") -- cgit v1.2.3 From 260805b9967184841c4499f94713a9a48c49a813 Mon Sep 17 00:00:00 2001 From: drebs Date: Sun, 18 Dec 2016 16:36:39 -0200 Subject: [feat] use twisted web http auth and creds --- testing/tests/client/test_http_client.py | 3 +- testing/tests/server/test_server.py | 164 +++++++------------------------ 2 files changed, 38 insertions(+), 129 deletions(-) (limited to 'testing/tests') diff --git a/testing/tests/client/test_http_client.py b/testing/tests/client/test_http_client.py index a107930a..691c7576 100644 --- a/testing/tests/client/test_http_client.py +++ b/testing/tests/client/test_http_client.py @@ -24,7 +24,6 @@ from testscenarios import TestWithScenarios from leap.soledad.client import auth from leap.soledad.common.l2db.remote import http_client from test_soledad.u1db_tests import test_http_client -from leap.soledad.server.auth import SoledadTokenAuthMiddleware # ----------------------------------------------------------------------------- @@ -67,7 +66,7 @@ class TestSoledadClientBase( return res # mime solead application here. if '/token' in environ['PATH_INFO']: - auth = environ.get(SoledadTokenAuthMiddleware.HTTP_AUTH_KEY) + auth = environ.get('HTTP_AUTHORIZATION') if not auth: start_response("401 Unauthorized", [('Content-Type', 'application/json')]) diff --git a/testing/tests/server/test_server.py b/testing/tests/server/test_server.py index 09242736..12f6fb20 100644 --- a/testing/tests/server/test_server.py +++ b/testing/tests/server/test_server.py @@ -18,11 +18,9 @@ Tests for server-related functionality. """ import binascii -import mock import os import pytest -from hashlib import sha512 from pkg_resources import resource_filename from urlparse import urljoin from uuid import uuid4 @@ -46,36 +44,6 @@ from leap.soledad.client import Soledad from leap.soledad.server.config import load_configuration from leap.soledad.server.config import CONFIG_DEFAULTS from leap.soledad.server.auth import URLMapper -from leap.soledad.server.auth import SoledadTokenAuthMiddleware - - -class ServerAuthenticationMiddlewareTestCase(CouchDBTestCase): - - def setUp(self): - super(ServerAuthenticationMiddlewareTestCase, self).setUp() - app = mock.Mock() - self._state = CouchServerState(self.couch_url) - app.state = self._state - self.auth_middleware = SoledadTokenAuthMiddleware(app) - self._authorize('valid-uuid', 'valid-token') - - def _authorize(self, uuid, token): - token_doc = {} - token_doc['_id'] = sha512(token).hexdigest() - token_doc[self._state.TOKENS_USER_ID_KEY] = uuid - token_doc[self._state.TOKENS_TYPE_KEY] = \ - self._state.TOKENS_TYPE_DEF - dbname = self._state._tokens_dbname() - db = self.couch_server.create(dbname) - db.save(token_doc) - self.addCleanup(self.delete_db, db.name) - - def test_authorized_user(self): - is_authorized = self.auth_middleware._verify_authentication_data - self.assertTrue(is_authorized('valid-uuid', 'valid-token')) - self.assertFalse(is_authorized('valid-uuid', 'invalid-token')) - self.assertFalse(is_authorized('invalid-uuid', 'valid-token')) - self.assertFalse(is_authorized('eve', 'invalid-token')) class ServerAuthorizationTestCase(BaseSoledadTest): @@ -90,12 +58,6 @@ class ServerAuthorizationTestCase(BaseSoledadTest): def tearDown(self): pass - def _make_environ(self, path_info, request_method): - return { - 'PATH_INFO': path_info, - 'REQUEST_METHOD': request_method, - } - def test_verify_action_with_correct_dbnames(self): """ Test encrypting and decrypting documents. @@ -120,146 +82,94 @@ class ServerAuthorizationTestCase(BaseSoledadTest): dbname = 'user-%s' % uuid # test global auth - match = urlmap.match(self._make_environ('/', 'GET')) + match = urlmap.match('/', 'GET') + self.assertIsNotNone(match) # test shared-db database resource auth - match = urlmap.match( - self._make_environ('/shared', 'GET')) + match = urlmap.match('/shared', 'GET') self.assertIsNotNone(match) - self.assertIsNone( - urlmap.match( - self._make_environ('/shared', 'PUT'))) + match = urlmap.match('/shared', 'PUT') + self.assertIsNone(match) - self.assertIsNone( - urlmap.match( - self._make_environ('/shared', 'DELETE'))) + match = urlmap.match('/shared', 'DELETE') + self.assertIsNone(match) - self.assertIsNone( - urlmap.match( - self._make_environ('/shared', 'POST'))) + match = urlmap.match('/shared', 'POST') + self.assertIsNone(match) # test shared-db docs resource auth - self.assertIsNone( - urlmap.match( - self._make_environ('/shared/docs', 'GET'))) + self.assertIsNone(urlmap.match('/shared/docs', 'GET')) - self.assertIsNone( - urlmap.match( - self._make_environ('/shared/docs', 'PUT'))) + self.assertIsNone(urlmap.match('/shared/docs', 'PUT')) - self.assertIsNone( - urlmap.match( - self._make_environ('/shared/docs', 'DELETE'))) + self.assertIsNone(urlmap.match('/shared/docs', 'DELETE')) - self.assertIsNone( - urlmap.match( - self._make_environ('/shared/docs', 'POST'))) + self.assertIsNone(urlmap.match('/shared/docs', 'POST')) # test shared-db doc resource auth - match = urlmap.match( - self._make_environ('/shared/doc/x', 'GET')) + match = urlmap.match('/shared/doc/x', 'GET') self.assertIsNotNone(match) self.assertEqual('x', match.get('id')) - match = urlmap.match( - self._make_environ('/shared/doc/x', 'PUT')) + match = urlmap.match('/shared/doc/x', 'PUT') self.assertIsNotNone(match) self.assertEqual('x', match.get('id')) - match = urlmap.match( - self._make_environ('/shared/doc/x', 'DELETE')) + match = urlmap.match('/shared/doc/x', 'DELETE') self.assertEqual('x', match.get('id')) - self.assertIsNone( - urlmap.match( - self._make_environ('/shared/doc/x', 'POST'))) + self.assertIsNone(urlmap.match('/shared/doc/x', 'POST')) # test shared-db sync resource auth - self.assertIsNone( - urlmap.match( - self._make_environ('/shared/sync-from/x', 'GET'))) + self.assertIsNone(urlmap.match('/shared/sync-from/x', 'GET')) - self.assertIsNone( - urlmap.match( - self._make_environ('/shared/sync-from/x', 'PUT'))) + self.assertIsNone(urlmap.match('/shared/sync-from/x', 'PUT')) - self.assertIsNone( - urlmap.match( - self._make_environ('/shared/sync-from/x', 'DELETE'))) + self.assertIsNone(urlmap.match('/shared/sync-from/x', 'DELETE')) - self.assertIsNone( - urlmap.match( - self._make_environ('/shared/sync-from/x', 'POST'))) + self.assertIsNone(urlmap.match('/shared/sync-from/x', 'POST')) # test user-db database resource auth - self.assertIsNone( - urlmap.match( - self._make_environ('/%s' % dbname, 'GET'))) + self.assertIsNone(urlmap.match('/%s' % dbname, 'GET')) - self.assertIsNone( - urlmap.match( - self._make_environ('/%s' % dbname, 'PUT'))) + self.assertIsNone(urlmap.match('/%s' % dbname, 'PUT')) - self.assertIsNone( - urlmap.match( - self._make_environ('/%s' % dbname, 'DELETE'))) + self.assertIsNone(urlmap.match('/%s' % dbname, 'DELETE')) - self.assertIsNone( - urlmap.match( - self._make_environ('/%s' % dbname, 'POST'))) + self.assertIsNone(urlmap.match('/%s' % dbname, 'POST')) # test user-db docs resource auth - self.assertIsNone( - urlmap.match( - self._make_environ('/%s/docs' % dbname, 'GET'))) + self.assertIsNone(urlmap.match('/%s/docs' % dbname, 'GET')) - self.assertIsNone( - urlmap.match( - self._make_environ('/%s/docs' % dbname, 'PUT'))) + self.assertIsNone(urlmap.match('/%s/docs' % dbname, 'PUT')) - self.assertIsNone( - urlmap.match( - self._make_environ('/%s/docs' % dbname, 'DELETE'))) + self.assertIsNone(urlmap.match('/%s/docs' % dbname, 'DELETE')) - self.assertIsNone( - urlmap.match( - self._make_environ('/%s/docs' % dbname, 'POST'))) + self.assertIsNone(urlmap.match('/%s/docs' % dbname, 'POST')) # test user-db doc resource auth - self.assertIsNone( - urlmap.match( - self._make_environ('/%s/doc/x' % dbname, 'GET'))) + self.assertIsNone(urlmap.match('/%s/doc/x' % dbname, 'GET')) - self.assertIsNone( - urlmap.match( - self._make_environ('/%s/doc/x' % dbname, 'PUT'))) + self.assertIsNone(urlmap.match('/%s/doc/x' % dbname, 'PUT')) - self.assertIsNone( - urlmap.match( - self._make_environ('/%s/doc/x' % dbname, 'DELETE'))) + self.assertIsNone(urlmap.match('/%s/doc/x' % dbname, 'DELETE')) - self.assertIsNone( - urlmap.match( - self._make_environ('/%s/doc/x' % dbname, 'POST'))) + self.assertIsNone(urlmap.match('/%s/doc/x' % dbname, 'POST')) # test user-db sync resource auth - match = urlmap.match( - self._make_environ('/%s/sync-from/x' % dbname, 'GET')) + match = urlmap.match('/%s/sync-from/x' % dbname, 'GET') self.assertEqual(uuid, match.get('uuid')) self.assertEqual('x', match.get('source_replica_uid')) - match = urlmap.match( - self._make_environ('/%s/sync-from/x' % dbname, 'PUT')) + match = urlmap.match('/%s/sync-from/x' % dbname, 'PUT') self.assertEqual(uuid, match.get('uuid')) self.assertEqual('x', match.get('source_replica_uid')) - match = urlmap.match( - self._make_environ('/%s/sync-from/x' % dbname, 'DELETE')) + match = urlmap.match('/%s/sync-from/x' % dbname, 'DELETE') self.assertIsNone(match) - match = urlmap.match( - self._make_environ('/%s/sync-from/x' % dbname, 'POST')) + match = urlmap.match('/%s/sync-from/x' % dbname, 'POST') self.assertEqual(uuid, match.get('uuid')) self.assertEqual('x', match.get('source_replica_uid')) -- cgit v1.2.3 From 6043f7966b64d6922987bca9137a524fb06a3379 Mon Sep 17 00:00:00 2001 From: drebs Date: Mon, 19 Dec 2016 09:43:03 -0200 Subject: [refactor] separate url mapper, avoid hanging tests Because the wsgi resource has its own threadpool, tests might get confused when shutting down and the reactor may get clogged waiting for the threadpool to be stopped. By refactoring the URLMapper to its own module, server tests can avoid loading the resource module, where the wsgi threadpool resides, so the threapool will not be started. --- testing/tests/server/test_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'testing/tests') diff --git a/testing/tests/server/test_server.py b/testing/tests/server/test_server.py index 12f6fb20..39d8e8c3 100644 --- a/testing/tests/server/test_server.py +++ b/testing/tests/server/test_server.py @@ -43,7 +43,7 @@ from leap.soledad.client import _crypto from leap.soledad.client import Soledad from leap.soledad.server.config import load_configuration from leap.soledad.server.config import CONFIG_DEFAULTS -from leap.soledad.server.auth import URLMapper +from leap.soledad.server.url_mapper import URLMapper class ServerAuthorizationTestCase(BaseSoledadTest): -- cgit v1.2.3 From c3c8afb68330bdfb8fe70efc7d055b55ca9c1c3a Mon Sep 17 00:00:00 2001 From: drebs Date: Thu, 22 Dec 2016 07:32:29 -0200 Subject: [refactor] remove leftover code from previous wsgi auth --- testing/tests/client/test_http_client.py | 107 ------------------------------- 1 file changed, 107 deletions(-) delete mode 100644 testing/tests/client/test_http_client.py (limited to 'testing/tests') diff --git a/testing/tests/client/test_http_client.py b/testing/tests/client/test_http_client.py deleted file mode 100644 index 691c7576..00000000 --- a/testing/tests/client/test_http_client.py +++ /dev/null @@ -1,107 +0,0 @@ -# -*- coding: utf-8 -*- -# test_http_client.py -# Copyright (C) 2013-2016 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 . -""" -Test Leap backend bits: sync target -""" -import json - -from testscenarios import TestWithScenarios - -from leap.soledad.client import auth -from leap.soledad.common.l2db.remote import http_client -from test_soledad.u1db_tests import test_http_client - - -# ----------------------------------------------------------------------------- -# The following tests come from `u1db.tests.test_http_client`. -# ----------------------------------------------------------------------------- - -class TestSoledadClientBase( - TestWithScenarios, - test_http_client.TestHTTPClientBase): - - """ - This class should be used to test Token auth. - """ - - def getClient(self, **kwds): - cli = self.getClientWithToken(**kwds) - if 'creds' not in kwds: - cli.set_token_credentials('user-uuid', 'auth-token') - return cli - - def getClientWithToken(self, **kwds): - self.startServer() - - class _HTTPClientWithToken( - http_client.HTTPClientBase, auth.TokenBasedAuth): - - def set_token_credentials(self, uuid, token): - auth.TokenBasedAuth.set_token_credentials(self, uuid, token) - - def _sign_request(self, method, url_query, params): - return auth.TokenBasedAuth._sign_request( - self, method, url_query, params) - - return _HTTPClientWithToken(self.getURL('dbase'), **kwds) - - def app(self, environ, start_response): - res = test_http_client.TestHTTPClientBase.app( - self, environ, start_response) - if res is not None: - return res - # mime solead application here. - if '/token' in environ['PATH_INFO']: - auth = environ.get('HTTP_AUTHORIZATION') - if not auth: - start_response("401 Unauthorized", - [('Content-Type', 'application/json')]) - return [ - json.dumps( - {"error": "unauthorized", - "message": "no token found in environment"}) - ] - scheme, encoded = auth.split(None, 1) - if scheme.lower() != 'token': - start_response("401 Unauthorized", - [('Content-Type', 'application/json')]) - return [json.dumps({"error": "unauthorized", - "message": "unknown scheme: %s" % scheme})] - uuid, token = encoded.decode('base64').split(':', 1) - if uuid != 'user-uuid' and token != 'auth-token': - return Exception("Incorrect address or token.") - start_response("200 OK", [('Content-Type', 'application/json')]) - return [json.dumps([environ['PATH_INFO'], uuid, token])] - - def test_token(self): - """ - Test if token is sent correctly. - """ - cli = self.getClientWithToken() - cli.set_token_credentials('user-uuid', 'auth-token') - res, headers = cli._request('GET', ['doc', 'token']) - self.assertEqual( - ['/dbase/doc/token', 'user-uuid', 'auth-token'], json.loads(res)) - - def test_token_ctr_creds(self): - cli = self.getClientWithToken(creds={'token': { - 'uuid': 'user-uuid', - 'token': 'auth-token', - }}) - res, headers = cli._request('GET', ['doc', 'token']) - self.assertEqual( - ['/dbase/doc/token', 'user-uuid', 'auth-token'], json.loads(res)) -- cgit v1.2.3 From 5058cae83227d4ba1b6390aa52a63b22a1acb11d Mon Sep 17 00:00:00 2001 From: drebs Date: Thu, 22 Dec 2016 07:56:01 -0200 Subject: [test] split url mapper test in many smaller tests --- testing/tests/server/test_server.py | 180 ++++++++++++++++-------------------- 1 file changed, 79 insertions(+), 101 deletions(-) (limited to 'testing/tests') diff --git a/testing/tests/server/test_server.py b/testing/tests/server/test_server.py index 39d8e8c3..da69c423 100644 --- a/testing/tests/server/test_server.py +++ b/testing/tests/server/test_server.py @@ -36,7 +36,6 @@ from test_soledad.util import ( make_token_soledad_app, make_soledad_document_for_test, soledad_sync_target, - BaseSoledadTest, ) from leap.soledad.client import _crypto @@ -46,133 +45,112 @@ from leap.soledad.server.config import CONFIG_DEFAULTS from leap.soledad.server.url_mapper import URLMapper -class ServerAuthorizationTestCase(BaseSoledadTest): - +class URLMapperTestCase(unittest.TestCase): """ - Tests related to Soledad server authorization. + Test if the URLMapper behaves as expected. + + The following table lists the authorized actions among all possible + u1db remote actions: + + URL path | Authorized actions + -------------------------------------------------- + / | GET + /shared-db | GET + /shared-db/docs | - + /shared-db/doc/{id} | - + /shared-db/sync-from/{source} | - + /user-db | - + /user-db/docs | - + /user-db/doc/{id} | - + /user-db/sync-from/{source} | GET, PUT, POST """ def setUp(self): - pass - - def tearDown(self): - pass - - def test_verify_action_with_correct_dbnames(self): - """ - Test encrypting and decrypting documents. - - The following table lists the authorized actions among all possible - u1db remote actions: - - URL path | Authorized actions - -------------------------------------------------- - / | GET - /shared-db | GET - /shared-db/docs | - - /shared-db/doc/{id} | GET, PUT, DELETE - /shared-db/sync-from/{source} | - - /user-db | - - /user-db/docs | - - /user-db/doc/{id} | - - /user-db/sync-from/{source} | GET, PUT, POST - """ - uuid = uuid4().hex - urlmap = URLMapper() - dbname = 'user-%s' % uuid - - # test global auth - match = urlmap.match('/', 'GET') - self.assertIsNotNone(match) + self._uuid = uuid4().hex + self._urlmap = URLMapper() + self._dbname = 'user-%s' % self._uuid - # test shared-db database resource auth - match = urlmap.match('/shared', 'GET') + def test_root_authorized(self): + match = self._urlmap.match('/', 'GET') self.assertIsNotNone(match) - match = urlmap.match('/shared', 'PUT') - self.assertIsNone(match) + def test_shared_authorized(self): + self.assertIsNotNone(self._urlmap.match('/shared', 'GET')) - match = urlmap.match('/shared', 'DELETE') - self.assertIsNone(match) + def test_shared_unauthorized(self): + self.assertIsNone(self._urlmap.match('/shared', 'PUT')) + self.assertIsNone(self._urlmap.match('/shared', 'DELETE')) + self.assertIsNone(self._urlmap.match('/shared', 'POST')) - match = urlmap.match('/shared', 'POST') - self.assertIsNone(match) + def test_shared_docs_unauthorized(self): + self.assertIsNone(self._urlmap.match('/shared/docs', 'GET')) + self.assertIsNone(self._urlmap.match('/shared/docs', 'PUT')) + self.assertIsNone(self._urlmap.match('/shared/docs', 'DELETE')) + self.assertIsNone(self._urlmap.match('/shared/docs', 'POST')) - # test shared-db docs resource auth - self.assertIsNone(urlmap.match('/shared/docs', 'GET')) - - self.assertIsNone(urlmap.match('/shared/docs', 'PUT')) - - self.assertIsNone(urlmap.match('/shared/docs', 'DELETE')) - - self.assertIsNone(urlmap.match('/shared/docs', 'POST')) - - # test shared-db doc resource auth - match = urlmap.match('/shared/doc/x', 'GET') + def test_shared_doc_authorized(self): + match = self._urlmap.match('/shared/doc/x', 'GET') self.assertIsNotNone(match) self.assertEqual('x', match.get('id')) - match = urlmap.match('/shared/doc/x', 'PUT') + match = self._urlmap.match('/shared/doc/x', 'PUT') self.assertIsNotNone(match) self.assertEqual('x', match.get('id')) - match = urlmap.match('/shared/doc/x', 'DELETE') + match = self._urlmap.match('/shared/doc/x', 'DELETE') + self.assertIsNotNone(match) self.assertEqual('x', match.get('id')) - self.assertIsNone(urlmap.match('/shared/doc/x', 'POST')) - - # test shared-db sync resource auth - self.assertIsNone(urlmap.match('/shared/sync-from/x', 'GET')) - - self.assertIsNone(urlmap.match('/shared/sync-from/x', 'PUT')) - - self.assertIsNone(urlmap.match('/shared/sync-from/x', 'DELETE')) - - self.assertIsNone(urlmap.match('/shared/sync-from/x', 'POST')) - - # test user-db database resource auth - self.assertIsNone(urlmap.match('/%s' % dbname, 'GET')) - - self.assertIsNone(urlmap.match('/%s' % dbname, 'PUT')) - - self.assertIsNone(urlmap.match('/%s' % dbname, 'DELETE')) - - self.assertIsNone(urlmap.match('/%s' % dbname, 'POST')) - - # test user-db docs resource auth - self.assertIsNone(urlmap.match('/%s/docs' % dbname, 'GET')) - - self.assertIsNone(urlmap.match('/%s/docs' % dbname, 'PUT')) - - self.assertIsNone(urlmap.match('/%s/docs' % dbname, 'DELETE')) - - self.assertIsNone(urlmap.match('/%s/docs' % dbname, 'POST')) - - # test user-db doc resource auth - self.assertIsNone(urlmap.match('/%s/doc/x' % dbname, 'GET')) - - self.assertIsNone(urlmap.match('/%s/doc/x' % dbname, 'PUT')) - - self.assertIsNone(urlmap.match('/%s/doc/x' % dbname, 'DELETE')) - - self.assertIsNone(urlmap.match('/%s/doc/x' % dbname, 'POST')) - - # test user-db sync resource auth - match = urlmap.match('/%s/sync-from/x' % dbname, 'GET') + def test_shared_doc_unauthorized(self): + self.assertIsNone(self._urlmap.match('/shared/doc/x', 'POST')) + + def test_shared_sync_unauthorized(self): + self.assertIsNone(self._urlmap.match('/shared/sync-from/x', 'GET')) + self.assertIsNone(self._urlmap.match('/shared/sync-from/x', 'PUT')) + self.assertIsNone(self._urlmap.match('/shared/sync-from/x', 'DELETE')) + self.assertIsNone(self._urlmap.match('/shared/sync-from/x', 'POST')) + + def test_user_db_unauthorized(self): + dbname = self._dbname + self.assertIsNone(self._urlmap.match('/%s' % dbname, 'GET')) + self.assertIsNone(self._urlmap.match('/%s' % dbname, 'PUT')) + self.assertIsNone(self._urlmap.match('/%s' % dbname, 'DELETE')) + self.assertIsNone(self._urlmap.match('/%s' % dbname, 'POST')) + + def test_user_db_docs_unauthorized(self): + dbname = self._dbname + self.assertIsNone(self._urlmap.match('/%s/docs' % dbname, 'GET')) + self.assertIsNone(self._urlmap.match('/%s/docs' % dbname, 'PUT')) + self.assertIsNone(self._urlmap.match('/%s/docs' % dbname, 'DELETE')) + self.assertIsNone(self._urlmap.match('/%s/docs' % dbname, 'POST')) + + def test_user_db_doc_unauthorized(self): + dbname = self._dbname + self.assertIsNone(self._urlmap.match('/%s/doc/x' % dbname, 'GET')) + self.assertIsNone(self._urlmap.match('/%s/doc/x' % dbname, 'PUT')) + self.assertIsNone(self._urlmap.match('/%s/doc/x' % dbname, 'DELETE')) + self.assertIsNone(self._urlmap.match('/%s/doc/x' % dbname, 'POST')) + + def test_user_db_sync_authorized(self): + uuid = self._uuid + dbname = self._dbname + match = self._urlmap.match('/%s/sync-from/x' % dbname, 'GET') self.assertEqual(uuid, match.get('uuid')) self.assertEqual('x', match.get('source_replica_uid')) - match = urlmap.match('/%s/sync-from/x' % dbname, 'PUT') + match = self._urlmap.match('/%s/sync-from/x' % dbname, 'PUT') self.assertEqual(uuid, match.get('uuid')) self.assertEqual('x', match.get('source_replica_uid')) - match = urlmap.match('/%s/sync-from/x' % dbname, 'DELETE') - self.assertIsNone(match) - - match = urlmap.match('/%s/sync-from/x' % dbname, 'POST') + match = self._urlmap.match('/%s/sync-from/x' % dbname, 'POST') self.assertEqual(uuid, match.get('uuid')) self.assertEqual('x', match.get('source_replica_uid')) + def test_user_db_sync_unauthorized(self): + dbname = self._dbname + self.assertIsNone( + self._urlmap.match('/%s/sync-from/x' % dbname, 'DELETE')) + @pytest.mark.usefixtures("method_tmpdir") class EncryptedSyncTestCase( -- cgit v1.2.3 From b3e0af399b81b65d6665865d1e1b18aa589f5824 Mon Sep 17 00:00:00 2001 From: drebs Date: Tue, 31 Jan 2017 23:51:42 -0200 Subject: [test] add tests for server auth --- testing/tests/server/test_auth.py | 92 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 testing/tests/server/test_auth.py (limited to 'testing/tests') diff --git a/testing/tests/server/test_auth.py b/testing/tests/server/test_auth.py new file mode 100644 index 00000000..3ff738f2 --- /dev/null +++ b/testing/tests/server/test_auth.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- +# test_auth.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 . +""" +Tests for auth pieces. +""" +import collections + +from twisted.cred.credentials import UsernamePassword +from twisted.cred.error import UnauthorizedLogin +from twisted.internet.defer import inlineCallbacks +from twisted.trial import unittest +from twisted.web.resource import IResource + +from leap.soledad.server.auth import SoledadRealm +from leap.soledad.server.auth import TokenChecker +from leap.soledad.server.auth import TokenCredentialFactory +from leap.soledad.server.resource import SoledadResource + + +class SoledadRealmTestCase(unittest.TestCase): + + def test_returned_resource(self): + realm = SoledadRealm() + iface, avatar, logout = realm.requestAvatar('any', None, IResource) + self.assertIsInstance(avatar, SoledadResource) + self.assertIsNone(logout()) + + +class DummyServer(object): + """ + I fake the `couchdb.client.Server` GET api and always return the token + given on my creation. + """ + + def __init__(self, token): + self._token = token + + def get(self, _): + return self._token + + +class TokenCheckerTestCase(unittest.TestCase): + + @inlineCallbacks + def test_good_creds(self): + # set up a dummy server which always return a *valid* token document + token = {'user_id': 'user', 'type': 'Token'} + server = collections.defaultdict(lambda: DummyServer(token)) + # setup the checker with the custom server + checker = TokenChecker(server=server) + # assert the checker *can* verify the creds + creds = UsernamePassword('user', 'pass') + avatarId = yield checker.requestAvatarId(creds) + self.assertEqual('user', avatarId) + + @inlineCallbacks + def test_bad_creds(self): + # set up a dummy server which always return an *invalid* token document + token = None + server = collections.defaultdict(lambda: DummyServer(token)) + # setup the checker with the custom server + checker = TokenChecker(server=server) + # assert the checker *cannot* verify the creds + creds = UsernamePassword('user', '') + with self.assertRaises(UnauthorizedLogin): + yield checker.requestAvatarId(creds) + + +from twisted.web.test import test_httpauth + + +class TokenCredentialFactoryTestcase( + test_httpauth.RequestMixin, test_httpauth.BasicAuthTestsMixin, + unittest.TestCase): + + def setUp(self): + test_httpauth.BasicAuthTestsMixin.setUp(self) + self.credentialFactory = TokenCredentialFactory() -- cgit v1.2.3 From 911695e59ab60d2abaef3013330a6d41283cc733 Mon Sep 17 00:00:00 2001 From: drebs Date: Wed, 1 Feb 2017 18:16:43 -0200 Subject: [test] add tests for server auth session --- testing/tests/server/test_auth.py | 6 +- testing/tests/server/test_session.py | 209 +++++++++++++++++++++++++++++++++++ 2 files changed, 211 insertions(+), 4 deletions(-) create mode 100644 testing/tests/server/test_session.py (limited to 'testing/tests') diff --git a/testing/tests/server/test_auth.py b/testing/tests/server/test_auth.py index 3ff738f2..0e6baba3 100644 --- a/testing/tests/server/test_auth.py +++ b/testing/tests/server/test_auth.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # test_auth.py -# Copyright (C) 2013 LEAP +# Copyright (C) 2017 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 @@ -24,6 +24,7 @@ from twisted.cred.error import UnauthorizedLogin from twisted.internet.defer import inlineCallbacks from twisted.trial import unittest from twisted.web.resource import IResource +from twisted.web.test import test_httpauth from leap.soledad.server.auth import SoledadRealm from leap.soledad.server.auth import TokenChecker @@ -80,9 +81,6 @@ class TokenCheckerTestCase(unittest.TestCase): yield checker.requestAvatarId(creds) -from twisted.web.test import test_httpauth - - class TokenCredentialFactoryTestcase( test_httpauth.RequestMixin, test_httpauth.BasicAuthTestsMixin, unittest.TestCase): diff --git a/testing/tests/server/test_session.py b/testing/tests/server/test_session.py new file mode 100644 index 00000000..7883ef4a --- /dev/null +++ b/testing/tests/server/test_session.py @@ -0,0 +1,209 @@ +# -*- coding: utf-8 -*- +# test_session.py +# Copyright (C) 2017 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 . +""" +Tests for server session entrypoint. +""" +from twisted.cred.checkers import InMemoryUsernamePasswordDatabaseDontUse +from twisted.cred import portal +from twisted.web.test.test_httpauth import b64encode +from twisted.web.test.test_httpauth import Realm +from twisted.web.test.requesthelper import DummyRequest +from twisted.web.resource import getChildForRequest + +from twisted.web.resource import Resource + +from twisted.trial import unittest + +from leap.soledad.server.session import SoledadSession + +from twisted.web.static import Data +from twisted.web._auth.wrapper import UnauthorizedResource +from twisted.cred.credentials import IUsernamePassword +from twisted.cred.checkers import ANONYMOUS, AllowAnonymousAccess + + +class SoledadSessionTestCase(unittest.TestCase): + """ + Tests adapted from for + L{twisted.web.test.test_httpauth.HTTPAuthSessionWrapper}. + """ + + def makeRequest(self, *args, **kwargs): + request = DummyRequest(*args, **kwargs) + request.path = '/' + return request + + def setUp(self): + self.username = b'foo bar' + self.password = b'bar baz' + self.avatarContent = b"contents of the avatar resource itself" + self.childName = b"foo-child" + self.childContent = b"contents of the foo child of the avatar" + self.checker = InMemoryUsernamePasswordDatabaseDontUse() + self.checker.addUser(self.username, self.password) + self.avatar = Data(self.avatarContent, 'text/plain') + self.avatar.putChild( + self.childName, Data(self.childContent, 'text/plain')) + self.avatars = {self.username: self.avatar} + self.realm = Realm(self.avatars.get) + self.portal = portal.Portal(self.realm, [self.checker]) + self.wrapper = SoledadSession(self.portal) + + def _authorizedTokenLogin(self, request): + authorization = b64encode( + self.username + b':' + self.password) + request.requestHeaders.addRawHeader(b'authorization', + b'Token ' + authorization) + return getChildForRequest(self.wrapper, request) + + def test_getChildWithDefault(self): + request = self.makeRequest([self.childName]) + child = getChildForRequest(self.wrapper, request) + d = request.notifyFinish() + + def cbFinished(result): + self.assertEqual(request.responseCode, 401) + + d.addCallback(cbFinished) + request.render(child) + return d + + def _invalidAuthorizationTest(self, response): + request = self.makeRequest([self.childName]) + request.requestHeaders.addRawHeader(b'authorization', response) + child = getChildForRequest(self.wrapper, request) + d = request.notifyFinish() + + def cbFinished(result): + self.assertEqual(request.responseCode, 401) + + d.addCallback(cbFinished) + request.render(child) + return d + + def test_getChildWithDefaultUnauthorizedUser(self): + return self._invalidAuthorizationTest( + b'Basic ' + b64encode(b'foo:bar')) + + def test_getChildWithDefaultUnauthorizedPassword(self): + return self._invalidAuthorizationTest( + b'Basic ' + b64encode(self.username + b':bar')) + + def test_getChildWithDefaultUnrecognizedScheme(self): + return self._invalidAuthorizationTest(b'Quux foo bar baz') + + def test_getChildWithDefaultAuthorized(self): + request = self.makeRequest([self.childName]) + child = self._authorizedTokenLogin(request) + d = request.notifyFinish() + + def cbFinished(ignored): + self.assertEqual(request.written, [self.childContent]) + + d.addCallback(cbFinished) + request.render(child) + return d + + def test_renderAuthorized(self): + # Request it exactly, not any of its children. + request = self.makeRequest([]) + child = self._authorizedTokenLogin(request) + d = request.notifyFinish() + + def cbFinished(ignored): + self.assertEqual(request.written, [self.avatarContent]) + + d.addCallback(cbFinished) + request.render(child) + return d + + def test_decodeRaises(self): + request = self.makeRequest([self.childName]) + request.requestHeaders.addRawHeader(b'authorization', + b'Token decode should fail') + child = getChildForRequest(self.wrapper, request) + self.assertIsInstance(child, UnauthorizedResource) + + def test_parseResponse(self): + basicAuthorization = b'Basic abcdef123456' + self.assertEqual( + self.wrapper._parseHeader(basicAuthorization), + None) + tokenAuthorization = b'Token abcdef123456' + self.assertEqual( + self.wrapper._parseHeader(tokenAuthorization), + b'abcdef123456') + + def test_unexpectedDecodeError(self): + + class UnexpectedException(Exception): + pass + + class BadFactory(object): + scheme = b'bad' + + def getChallenge(self, client): + return {} + + def decode(self, response, request): + raise UnexpectedException() + + self.wrapper._credentialFactory = BadFactory() + request = self.makeRequest([self.childName]) + request.requestHeaders.addRawHeader(b'authorization', b'Bad abc') + child = getChildForRequest(self.wrapper, request) + request.render(child) + self.assertEqual(request.responseCode, 500) + + def test_unexpectedLoginError(self): + class UnexpectedException(Exception): + pass + + class BrokenChecker(object): + credentialInterfaces = (IUsernamePassword,) + + def requestAvatarId(self, credentials): + raise UnexpectedException() + + self.portal.registerChecker(BrokenChecker()) + request = self.makeRequest([self.childName]) + child = self._authorizedTokenLogin(request) + request.render(child) + self.assertEqual(request.responseCode, 500) + + def test_anonymousAccess(self): + """ + Anonymous requests are allowed if a L{Portal} has an anonymous checker + registered. + """ + unprotectedContents = b"contents of the unprotected child resource" + + self.avatars[ANONYMOUS] = Resource() + self.avatars[ANONYMOUS].putChild( + self.childName, Data(unprotectedContents, 'text/plain')) + self.portal.registerChecker(AllowAnonymousAccess()) + + request = self.makeRequest([self.childName]) + child = getChildForRequest(self.wrapper, request) + d = request.notifyFinish() + + def cbFinished(ignored): + self.assertEqual(request.written, [unprotectedContents]) + + d.addCallback(cbFinished) + request.render(child) + return d -- cgit v1.2.3 From c9cb1a814b6bfaa40de3c35a590f39d5fb0ce18e Mon Sep 17 00:00:00 2001 From: drebs Date: Wed, 1 Feb 2017 19:44:03 -0200 Subject: [test] fix session and auth tests --- testing/tests/server/test_auth.py | 11 +++++++-- testing/tests/server/test_session.py | 45 ++++++++---------------------------- 2 files changed, 19 insertions(+), 37 deletions(-) (limited to 'testing/tests') diff --git a/testing/tests/server/test_auth.py b/testing/tests/server/test_auth.py index 0e6baba3..5b215650 100644 --- a/testing/tests/server/test_auth.py +++ b/testing/tests/server/test_auth.py @@ -19,6 +19,8 @@ Tests for auth pieces. """ import collections +from contextlib import contextmanager + from twisted.cred.credentials import UsernamePassword from twisted.cred.error import UnauthorizedLogin from twisted.internet.defer import inlineCallbacks @@ -54,13 +56,18 @@ class DummyServer(object): return self._token +@contextmanager +def dummy_server(token): + yield collections.defaultdict(lambda: DummyServer(token)) + + class TokenCheckerTestCase(unittest.TestCase): @inlineCallbacks def test_good_creds(self): # set up a dummy server which always return a *valid* token document token = {'user_id': 'user', 'type': 'Token'} - server = collections.defaultdict(lambda: DummyServer(token)) + server = dummy_server(token) # setup the checker with the custom server checker = TokenChecker(server=server) # assert the checker *can* verify the creds @@ -72,7 +79,7 @@ class TokenCheckerTestCase(unittest.TestCase): def test_bad_creds(self): # set up a dummy server which always return an *invalid* token document token = None - server = collections.defaultdict(lambda: DummyServer(token)) + server = dummy_server(token) # setup the checker with the custom server checker = TokenChecker(server=server) # assert the checker *cannot* verify the creds diff --git a/testing/tests/server/test_session.py b/testing/tests/server/test_session.py index 7883ef4a..8131ddb3 100644 --- a/testing/tests/server/test_session.py +++ b/testing/tests/server/test_session.py @@ -17,24 +17,20 @@ """ Tests for server session entrypoint. """ -from twisted.cred.checkers import InMemoryUsernamePasswordDatabaseDontUse +from twisted.trial import unittest + from twisted.cred import portal +from twisted.cred.checkers import InMemoryUsernamePasswordDatabaseDontUse +from twisted.cred.credentials import IUsernamePassword +from twisted.web.resource import getChildForRequest +from twisted.web.static import Data +from twisted.web.test.requesthelper import DummyRequest from twisted.web.test.test_httpauth import b64encode from twisted.web.test.test_httpauth import Realm -from twisted.web.test.requesthelper import DummyRequest -from twisted.web.resource import getChildForRequest - -from twisted.web.resource import Resource - -from twisted.trial import unittest +from twisted.web._auth.wrapper import UnauthorizedResource from leap.soledad.server.session import SoledadSession -from twisted.web.static import Data -from twisted.web._auth.wrapper import UnauthorizedResource -from twisted.cred.credentials import IUsernamePassword -from twisted.cred.checkers import ANONYMOUS, AllowAnonymousAccess - class SoledadSessionTestCase(unittest.TestCase): """ @@ -168,6 +164,7 @@ class SoledadSessionTestCase(unittest.TestCase): child = getChildForRequest(self.wrapper, request) request.render(child) self.assertEqual(request.responseCode, 500) + self.assertEqual(len(self.flushLoggedErrors(UnexpectedException)), 1) def test_unexpectedLoginError(self): class UnexpectedException(Exception): @@ -184,26 +181,4 @@ class SoledadSessionTestCase(unittest.TestCase): child = self._authorizedTokenLogin(request) request.render(child) self.assertEqual(request.responseCode, 500) - - def test_anonymousAccess(self): - """ - Anonymous requests are allowed if a L{Portal} has an anonymous checker - registered. - """ - unprotectedContents = b"contents of the unprotected child resource" - - self.avatars[ANONYMOUS] = Resource() - self.avatars[ANONYMOUS].putChild( - self.childName, Data(unprotectedContents, 'text/plain')) - self.portal.registerChecker(AllowAnonymousAccess()) - - request = self.makeRequest([self.childName]) - child = getChildForRequest(self.wrapper, request) - d = request.notifyFinish() - - def cbFinished(ignored): - self.assertEqual(request.written, [unprotectedContents]) - - d.addCallback(cbFinished) - request.render(child) - return d + self.assertEqual(len(self.flushLoggedErrors(UnexpectedException)), 1) -- cgit v1.2.3 From 47c357213b4e39c6ced818de7eeb401e52bf92b9 Mon Sep 17 00:00:00 2001 From: drebs Date: Thu, 26 Jan 2017 18:37:45 -0200 Subject: [refactor] move wsgi sync setup to its own module Conflicts: server/src/leap/soledad/server/_wsgi.py server/src/leap/soledad/server/entrypoint.py server/src/leap/soledad/server/resource.py testing/tests/server/test__resource.py --- testing/tests/conftest.py | 2 +- testing/tests/server/test_auth.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'testing/tests') diff --git a/testing/tests/conftest.py b/testing/tests/conftest.py index 1ff1cbb7..decfb0a9 100644 --- a/testing/tests/conftest.py +++ b/testing/tests/conftest.py @@ -103,7 +103,7 @@ class SoledadServer(object): '--logfile=%s' % self._logfile, '--pidfile=%s' % self._pidfile, 'web', - '--wsgi=leap.soledad.server.application.wsgi_application', + '--class=leap.soledad.server.entrypoint.SoledadEntrypoint', '--port=2424' ]) diff --git a/testing/tests/server/test_auth.py b/testing/tests/server/test_auth.py index 5b215650..8ef35177 100644 --- a/testing/tests/server/test_auth.py +++ b/testing/tests/server/test_auth.py @@ -31,7 +31,7 @@ from twisted.web.test import test_httpauth from leap.soledad.server.auth import SoledadRealm from leap.soledad.server.auth import TokenChecker from leap.soledad.server.auth import TokenCredentialFactory -from leap.soledad.server.resource import SoledadResource +from leap.soledad.server._resource import SoledadResource class SoledadRealmTestCase(unittest.TestCase): -- cgit v1.2.3 From 47858d88ca4ca10ac363c71550b1bafe50f8f4ce Mon Sep 17 00:00:00 2001 From: drebs Date: Thu, 26 Jan 2017 19:18:15 -0200 Subject: [refactor] allow passing threadpool pool for server sync resource Conflicts: server/src/leap/soledad/server/_resource.py testing/tests/server/test__resource.py --- testing/tests/server/test_auth.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'testing/tests') diff --git a/testing/tests/server/test_auth.py b/testing/tests/server/test_auth.py index 8ef35177..f7fe0f25 100644 --- a/testing/tests/server/test_auth.py +++ b/testing/tests/server/test_auth.py @@ -23,6 +23,7 @@ from contextlib import contextmanager from twisted.cred.credentials import UsernamePassword from twisted.cred.error import UnauthorizedLogin +from twisted.internet import reactor from twisted.internet.defer import inlineCallbacks from twisted.trial import unittest from twisted.web.resource import IResource @@ -37,7 +38,9 @@ from leap.soledad.server._resource import SoledadResource class SoledadRealmTestCase(unittest.TestCase): def test_returned_resource(self): - realm = SoledadRealm() + # we have to pass a pool to the realm , otherwise tests will hang + pool = reactor.getThreadPool() + realm = SoledadRealm(sync_pool=pool) iface, avatar, logout = realm.requestAvatar('any', None, IResource) self.assertIsInstance(avatar, SoledadResource) self.assertIsNone(logout()) -- cgit v1.2.3 From 0e12cd3eb1e20bb867f34e0bf60f280d93b6182d Mon Sep 17 00:00:00 2001 From: drebs Date: Wed, 18 Jan 2017 17:28:01 -0200 Subject: [feature] add server config option for blobs --- testing/tests/server/test_server.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'testing/tests') diff --git a/testing/tests/server/test_server.py b/testing/tests/server/test_server.py index da69c423..866d9eab 100644 --- a/testing/tests/server/test_server.py +++ b/testing/tests/server/test_server.py @@ -379,5 +379,6 @@ class ConfigurationParsingTest(unittest.TestCase): 'sudo -u soledad-admin /usr/bin/create-user-db', 'admin_netrc': '/etc/couchdb/couchdb-soledad-admin.netrc', - 'batching': False} + 'batching': False, + 'blobs': False} self.assertDictEqual(expected, config['soledad-server']) -- cgit v1.2.3 From e05e9566c55e20833a2c45fe2ca67e60df009aac Mon Sep 17 00:00:00 2001 From: drebs Date: Thu, 2 Feb 2017 12:15:45 -0200 Subject: [test] move server auth tests to its own file --- testing/tests/server/test_config.py | 68 +++++++++++++++++++++++++++++++++++++ testing/tests/server/test_server.py | 46 ------------------------- 2 files changed, 68 insertions(+), 46 deletions(-) create mode 100644 testing/tests/server/test_config.py (limited to 'testing/tests') diff --git a/testing/tests/server/test_config.py b/testing/tests/server/test_config.py new file mode 100644 index 00000000..1241472b --- /dev/null +++ b/testing/tests/server/test_config.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +# test_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 . +""" +Tests for server configuration. +""" + +from twisted.trial import unittest +from pkg_resources import resource_filename + +from leap.soledad.server._config import _load_config +from leap.soledad.server._config import CONFIG_DEFAULTS + + +class ConfigurationParsingTest(unittest.TestCase): + + def setUp(self): + self.maxDiff = None + + def test_use_defaults_on_failure(self): + config = _load_config('this file will never exist') + expected = CONFIG_DEFAULTS + self.assertEquals(expected, config) + + def test_security_values_configuration(self): + # given + config_path = resource_filename('test_soledad', + 'fixture_soledad.conf') + # when + config = _load_config(config_path) + + # then + expected = {'members': ['user1', 'user2'], + 'members_roles': ['role1', 'role2'], + 'admins': ['user3', 'user4'], + 'admins_roles': ['role3', 'role3']} + self.assertDictEqual(expected, config['database-security']) + + def test_server_values_configuration(self): + # given + config_path = resource_filename('test_soledad', + 'fixture_soledad.conf') + # when + config = _load_config(config_path) + + # then + expected = {'couch_url': + 'http://soledad:passwd@localhost:5984', + 'create_cmd': + 'sudo -u soledad-admin /usr/bin/create-user-db', + 'admin_netrc': + '/etc/couchdb/couchdb-soledad-admin.netrc', + 'batching': False, + 'blobs': False} + self.assertDictEqual(expected, config['soledad-server']) diff --git a/testing/tests/server/test_server.py b/testing/tests/server/test_server.py index 866d9eab..ec0fc31d 100644 --- a/testing/tests/server/test_server.py +++ b/testing/tests/server/test_server.py @@ -21,7 +21,6 @@ import binascii import os import pytest -from pkg_resources import resource_filename from urlparse import urljoin from uuid import uuid4 @@ -40,8 +39,6 @@ from test_soledad.util import ( from leap.soledad.client import _crypto from leap.soledad.client import Soledad -from leap.soledad.server.config import load_configuration -from leap.soledad.server.config import CONFIG_DEFAULTS from leap.soledad.server.url_mapper import URLMapper @@ -339,46 +336,3 @@ class EncryptedSyncTestCase( Test if Soledad can sync many smallfiles. """ return self._test_encrypted_sym_sync(doc_size=2, number_of_docs=100) - - -class ConfigurationParsingTest(unittest.TestCase): - - def setUp(self): - self.maxDiff = None - - def test_use_defaults_on_failure(self): - config = load_configuration('this file will never exist') - expected = CONFIG_DEFAULTS - self.assertEquals(expected, config) - - def test_security_values_configuration(self): - # given - config_path = resource_filename('test_soledad', - 'fixture_soledad.conf') - # when - config = load_configuration(config_path) - - # then - expected = {'members': ['user1', 'user2'], - 'members_roles': ['role1', 'role2'], - 'admins': ['user3', 'user4'], - 'admins_roles': ['role3', 'role3']} - self.assertDictEqual(expected, config['database-security']) - - def test_server_values_configuration(self): - # given - config_path = resource_filename('test_soledad', - 'fixture_soledad.conf') - # when - config = load_configuration(config_path) - - # then - expected = {'couch_url': - 'http://soledad:passwd@localhost:5984', - 'create_cmd': - 'sudo -u soledad-admin /usr/bin/create-user-db', - 'admin_netrc': - '/etc/couchdb/couchdb-soledad-admin.netrc', - 'batching': False, - 'blobs': False} - self.assertDictEqual(expected, config['soledad-server']) -- cgit v1.2.3 From 30c47eea2b10f10204b5d61ecb550edd24e59067 Mon Sep 17 00:00:00 2001 From: drebs Date: Thu, 26 Jan 2017 08:24:39 -0200 Subject: [test] add tests for server resource and server info --- testing/tests/server/test__resource.py | 66 +++++++++++++++++++++++++++++++ testing/tests/server/test__server_info.py | 39 ++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 testing/tests/server/test__resource.py create mode 100644 testing/tests/server/test__server_info.py (limited to 'testing/tests') diff --git a/testing/tests/server/test__resource.py b/testing/tests/server/test__resource.py new file mode 100644 index 00000000..0ea46d6a --- /dev/null +++ b/testing/tests/server/test__resource.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +# test__resource.py +# Copyright (C) 2017 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 . +""" +Tests for Soledad server main resource. +""" +from twisted.trial import unittest +from twisted.web.test.test_web import DummyRequest +from twisted.web.error import Error +from twisted.web.wsgi import WSGIResource + +from leap.soledad.server._resource import SoledadResource +from leap.soledad.server._server_info import ServerInfo +from leap.soledad.server._blobs import BlobsResource + + +conf_blobs_false = {'soledad-server': {'blobs': False}} + + +class SoledadResourceTestCase(unittest.TestCase): + + def test_get_root(self): + conf = {'soledad-server': {'blobs': None}} # doesn't matter + resource = SoledadResource(conf) + path = '' + request = DummyRequest([]) + child = resource.getChild(path, request) + self.assertIsInstance(child, ServerInfo) + + def test_get_blobs_enabled(self): + conf = {'soledad-server': {'blobs': True}} + resource = SoledadResource(conf) + path = 'blobs' + request = DummyRequest([]) + child = resource.getChild(path, request) + self.assertIsInstance(child, BlobsResource) + + def test_get_blobs_disabled(self): + conf = {'soledad-server': {'blobs': False}} + resource = SoledadResource(conf) + path = 'blobs' + request = DummyRequest([]) + with self.assertRaises(Error): + resource.getChild(path, request) + + def test_get_sync(self): + conf = {'soledad-server': {'blobs': None}} # doesn't matter + resource = SoledadResource(conf) + path = 'sync' # if not 'blobs' or '', should be routed to sync + request = DummyRequest([]) + request.prepath = ['user-db'] + child = resource.getChild(path, request) + self.assertIsInstance(child, WSGIResource) diff --git a/testing/tests/server/test__server_info.py b/testing/tests/server/test__server_info.py new file mode 100644 index 00000000..80770721 --- /dev/null +++ b/testing/tests/server/test__server_info.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# test__server_info.py +# Copyright (C) 2017 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 . + +""" +Tests for Soledad server information announcement. +""" +import json + +from twisted.trial import unittest +from twisted.web.test.test_web import DummyRequest + +from leap.soledad.server._server_info import ServerInfo + + +class ServerInfoTestCase(unittest.TestCase): + + def test_blobs_enabled(self): + resource = ServerInfo(True) + response = resource.render(DummyRequest([''])) + self.assertEquals(json.loads(response), {'blobs': True}) + + def test_blobs_disabled(self): + resource = ServerInfo(False) + response = resource.render(DummyRequest([''])) + self.assertEquals(json.loads(response), {'blobs': False}) -- cgit v1.2.3 From 7432b72471fa3de03d13b9d35f6004d14deae63d Mon Sep 17 00:00:00 2001 From: drebs Date: Thu, 26 Jan 2017 18:37:45 -0200 Subject: [refactor] move wsgi sync setup to its own module --- testing/tests/server/test__resource.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'testing/tests') diff --git a/testing/tests/server/test__resource.py b/testing/tests/server/test__resource.py index 0ea46d6a..b5285195 100644 --- a/testing/tests/server/test__resource.py +++ b/testing/tests/server/test__resource.py @@ -25,6 +25,7 @@ from twisted.web.wsgi import WSGIResource from leap.soledad.server._resource import SoledadResource from leap.soledad.server._server_info import ServerInfo from leap.soledad.server._blobs import BlobsResource +from leap.soledad.server.gzip_middleware import GzipMiddleware conf_blobs_false = {'soledad-server': {'blobs': False}} @@ -64,3 +65,4 @@ class SoledadResourceTestCase(unittest.TestCase): request.prepath = ['user-db'] child = resource.getChild(path, request) self.assertIsInstance(child, WSGIResource) + self.assertIsInstance(child._application, GzipMiddleware) -- cgit v1.2.3 From 1f1a9847117d23a767e99fe80484baf232375d36 Mon Sep 17 00:00:00 2001 From: drebs Date: Thu, 26 Jan 2017 19:18:15 -0200 Subject: [refactor] allow passing threadpool pool for server sync resource --- testing/tests/server/test__resource.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'testing/tests') diff --git a/testing/tests/server/test__resource.py b/testing/tests/server/test__resource.py index b5285195..2a387416 100644 --- a/testing/tests/server/test__resource.py +++ b/testing/tests/server/test__resource.py @@ -21,6 +21,7 @@ from twisted.trial import unittest from twisted.web.test.test_web import DummyRequest from twisted.web.error import Error from twisted.web.wsgi import WSGIResource +from twisted.internet import reactor from leap.soledad.server._resource import SoledadResource from leap.soledad.server._server_info import ServerInfo @@ -28,14 +29,14 @@ from leap.soledad.server._blobs import BlobsResource from leap.soledad.server.gzip_middleware import GzipMiddleware -conf_blobs_false = {'soledad-server': {'blobs': False}} +_pool = reactor.getThreadPool() class SoledadResourceTestCase(unittest.TestCase): def test_get_root(self): conf = {'soledad-server': {'blobs': None}} # doesn't matter - resource = SoledadResource(conf) + resource = SoledadResource(conf, sync_pool=_pool) path = '' request = DummyRequest([]) child = resource.getChild(path, request) @@ -43,7 +44,7 @@ class SoledadResourceTestCase(unittest.TestCase): def test_get_blobs_enabled(self): conf = {'soledad-server': {'blobs': True}} - resource = SoledadResource(conf) + resource = SoledadResource(conf, sync_pool=_pool) path = 'blobs' request = DummyRequest([]) child = resource.getChild(path, request) @@ -51,7 +52,7 @@ class SoledadResourceTestCase(unittest.TestCase): def test_get_blobs_disabled(self): conf = {'soledad-server': {'blobs': False}} - resource = SoledadResource(conf) + resource = SoledadResource(conf, sync_pool=_pool) path = 'blobs' request = DummyRequest([]) with self.assertRaises(Error): @@ -59,7 +60,7 @@ class SoledadResourceTestCase(unittest.TestCase): def test_get_sync(self): conf = {'soledad-server': {'blobs': None}} # doesn't matter - resource = SoledadResource(conf) + resource = SoledadResource(conf, sync_pool=_pool) path = 'sync' # if not 'blobs' or '', should be routed to sync request = DummyRequest([]) request.prepath = ['user-db'] -- cgit v1.2.3 From 53b5a6788ad8416f78b24cc9880d02da73c52d70 Mon Sep 17 00:00:00 2001 From: drebs Date: Fri, 27 Jan 2017 20:30:02 -0200 Subject: [refacor] make proper use of twisted web dyamic resources in server --- testing/tests/server/test__resource.py | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) (limited to 'testing/tests') diff --git a/testing/tests/server/test__resource.py b/testing/tests/server/test__resource.py index 2a387416..30ef782d 100644 --- a/testing/tests/server/test__resource.py +++ b/testing/tests/server/test__resource.py @@ -19,8 +19,8 @@ Tests for Soledad server main resource. """ from twisted.trial import unittest from twisted.web.test.test_web import DummyRequest -from twisted.web.error import Error from twisted.web.wsgi import WSGIResource +from twisted.web.resource import getChildForRequest from twisted.internet import reactor from leap.soledad.server._resource import SoledadResource @@ -37,33 +37,30 @@ class SoledadResourceTestCase(unittest.TestCase): def test_get_root(self): conf = {'soledad-server': {'blobs': None}} # doesn't matter resource = SoledadResource(conf, sync_pool=_pool) - path = '' - request = DummyRequest([]) - child = resource.getChild(path, request) + request = DummyRequest(['']) + child = getChildForRequest(resource, request) self.assertIsInstance(child, ServerInfo) def test_get_blobs_enabled(self): conf = {'soledad-server': {'blobs': True}} resource = SoledadResource(conf, sync_pool=_pool) - path = 'blobs' - request = DummyRequest([]) - child = resource.getChild(path, request) + request = DummyRequest(['blobs']) + child = getChildForRequest(resource, request) self.assertIsInstance(child, BlobsResource) def test_get_blobs_disabled(self): conf = {'soledad-server': {'blobs': False}} resource = SoledadResource(conf, sync_pool=_pool) - path = 'blobs' - request = DummyRequest([]) - with self.assertRaises(Error): - resource.getChild(path, request) + request = DummyRequest(['blobs']) + child = getChildForRequest(resource, request) + # if blobs is disabled, the request should be routed to sync + self.assertIsInstance(child, WSGIResource) + self.assertIsInstance(child._application, GzipMiddleware) def test_get_sync(self): conf = {'soledad-server': {'blobs': None}} # doesn't matter resource = SoledadResource(conf, sync_pool=_pool) - path = 'sync' # if not 'blobs' or '', should be routed to sync - request = DummyRequest([]) - request.prepath = ['user-db'] - child = resource.getChild(path, request) + request = DummyRequest(['user-db', 'sync-from', 'source-id']) + child = getChildForRequest(resource, request) self.assertIsInstance(child, WSGIResource) self.assertIsInstance(child._application, GzipMiddleware) -- cgit v1.2.3 From a2f041de7f1ea653f078ac9cd532e2d2b774248f Mon Sep 17 00:00:00 2001 From: drebs Date: Thu, 2 Feb 2017 11:45:57 -0200 Subject: [refactor] parametrize blobs toggling in soledad server resource --- testing/tests/server/test__resource.py | 16 ++++++++-------- testing/tests/server/test_auth.py | 3 ++- 2 files changed, 10 insertions(+), 9 deletions(-) (limited to 'testing/tests') diff --git a/testing/tests/server/test__resource.py b/testing/tests/server/test__resource.py index 30ef782d..c066435e 100644 --- a/testing/tests/server/test__resource.py +++ b/testing/tests/server/test__resource.py @@ -35,22 +35,22 @@ _pool = reactor.getThreadPool() class SoledadResourceTestCase(unittest.TestCase): def test_get_root(self): - conf = {'soledad-server': {'blobs': None}} # doesn't matter - resource = SoledadResource(conf, sync_pool=_pool) + enable_blobs = None # doesn't matter + resource = SoledadResource(enable_blobs=enable_blobs, sync_pool=_pool) request = DummyRequest(['']) child = getChildForRequest(resource, request) self.assertIsInstance(child, ServerInfo) def test_get_blobs_enabled(self): - conf = {'soledad-server': {'blobs': True}} - resource = SoledadResource(conf, sync_pool=_pool) + enable_blobs = True + resource = SoledadResource(enable_blobs=enable_blobs, sync_pool=_pool) request = DummyRequest(['blobs']) child = getChildForRequest(resource, request) self.assertIsInstance(child, BlobsResource) def test_get_blobs_disabled(self): - conf = {'soledad-server': {'blobs': False}} - resource = SoledadResource(conf, sync_pool=_pool) + enable_blobs = False + resource = SoledadResource(enable_blobs=enable_blobs, sync_pool=_pool) request = DummyRequest(['blobs']) child = getChildForRequest(resource, request) # if blobs is disabled, the request should be routed to sync @@ -58,8 +58,8 @@ class SoledadResourceTestCase(unittest.TestCase): self.assertIsInstance(child._application, GzipMiddleware) def test_get_sync(self): - conf = {'soledad-server': {'blobs': None}} # doesn't matter - resource = SoledadResource(conf, sync_pool=_pool) + enable_blobs = None # doesn't matter + resource = SoledadResource(enable_blobs=enable_blobs, sync_pool=_pool) request = DummyRequest(['user-db', 'sync-from', 'source-id']) child = getChildForRequest(resource, request) self.assertIsInstance(child, WSGIResource) diff --git a/testing/tests/server/test_auth.py b/testing/tests/server/test_auth.py index f7fe0f25..00d416d5 100644 --- a/testing/tests/server/test_auth.py +++ b/testing/tests/server/test_auth.py @@ -39,8 +39,9 @@ class SoledadRealmTestCase(unittest.TestCase): def test_returned_resource(self): # we have to pass a pool to the realm , otherwise tests will hang + conf = {'soledad-server': {'blobs': False}} pool = reactor.getThreadPool() - realm = SoledadRealm(sync_pool=pool) + realm = SoledadRealm(conf=conf, sync_pool=pool) iface, avatar, logout = realm.requestAvatar('any', None, IResource) self.assertIsInstance(avatar, SoledadResource) self.assertIsNone(logout()) -- cgit v1.2.3 From 4ae57257fa2d40ceeba1558d995d3514e6f6d6fa Mon Sep 17 00:00:00 2001 From: drebs Date: Thu, 2 Feb 2017 12:28:19 -0200 Subject: [test] move server url mapper tests to its own file --- testing/tests/server/test_config.py | 2 +- testing/tests/server/test_server.py | 109 -------------------------- testing/tests/server/test_url_mapper.py | 131 ++++++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+), 110 deletions(-) create mode 100644 testing/tests/server/test_url_mapper.py (limited to 'testing/tests') diff --git a/testing/tests/server/test_config.py b/testing/tests/server/test_config.py index 1241472b..133057f5 100644 --- a/testing/tests/server/test_config.py +++ b/testing/tests/server/test_config.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # test_config.py -# Copyright (C) 2013 LEAP +# Copyright (C) 2017 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 diff --git a/testing/tests/server/test_server.py b/testing/tests/server/test_server.py index ec0fc31d..647ef5a8 100644 --- a/testing/tests/server/test_server.py +++ b/testing/tests/server/test_server.py @@ -25,7 +25,6 @@ from urlparse import urljoin from uuid import uuid4 from twisted.internet import defer -from twisted.trial import unittest from leap.soledad.common.couch.state import CouchServerState from leap.soledad.common.couch import CouchDatabase @@ -39,114 +38,6 @@ from test_soledad.util import ( from leap.soledad.client import _crypto from leap.soledad.client import Soledad -from leap.soledad.server.url_mapper import URLMapper - - -class URLMapperTestCase(unittest.TestCase): - """ - Test if the URLMapper behaves as expected. - - The following table lists the authorized actions among all possible - u1db remote actions: - - URL path | Authorized actions - -------------------------------------------------- - / | GET - /shared-db | GET - /shared-db/docs | - - /shared-db/doc/{id} | - - /shared-db/sync-from/{source} | - - /user-db | - - /user-db/docs | - - /user-db/doc/{id} | - - /user-db/sync-from/{source} | GET, PUT, POST - """ - - def setUp(self): - self._uuid = uuid4().hex - self._urlmap = URLMapper() - self._dbname = 'user-%s' % self._uuid - - def test_root_authorized(self): - match = self._urlmap.match('/', 'GET') - self.assertIsNotNone(match) - - def test_shared_authorized(self): - self.assertIsNotNone(self._urlmap.match('/shared', 'GET')) - - def test_shared_unauthorized(self): - self.assertIsNone(self._urlmap.match('/shared', 'PUT')) - self.assertIsNone(self._urlmap.match('/shared', 'DELETE')) - self.assertIsNone(self._urlmap.match('/shared', 'POST')) - - def test_shared_docs_unauthorized(self): - self.assertIsNone(self._urlmap.match('/shared/docs', 'GET')) - self.assertIsNone(self._urlmap.match('/shared/docs', 'PUT')) - self.assertIsNone(self._urlmap.match('/shared/docs', 'DELETE')) - self.assertIsNone(self._urlmap.match('/shared/docs', 'POST')) - - def test_shared_doc_authorized(self): - match = self._urlmap.match('/shared/doc/x', 'GET') - self.assertIsNotNone(match) - self.assertEqual('x', match.get('id')) - - match = self._urlmap.match('/shared/doc/x', 'PUT') - self.assertIsNotNone(match) - self.assertEqual('x', match.get('id')) - - match = self._urlmap.match('/shared/doc/x', 'DELETE') - self.assertIsNotNone(match) - self.assertEqual('x', match.get('id')) - - def test_shared_doc_unauthorized(self): - self.assertIsNone(self._urlmap.match('/shared/doc/x', 'POST')) - - def test_shared_sync_unauthorized(self): - self.assertIsNone(self._urlmap.match('/shared/sync-from/x', 'GET')) - self.assertIsNone(self._urlmap.match('/shared/sync-from/x', 'PUT')) - self.assertIsNone(self._urlmap.match('/shared/sync-from/x', 'DELETE')) - self.assertIsNone(self._urlmap.match('/shared/sync-from/x', 'POST')) - - def test_user_db_unauthorized(self): - dbname = self._dbname - self.assertIsNone(self._urlmap.match('/%s' % dbname, 'GET')) - self.assertIsNone(self._urlmap.match('/%s' % dbname, 'PUT')) - self.assertIsNone(self._urlmap.match('/%s' % dbname, 'DELETE')) - self.assertIsNone(self._urlmap.match('/%s' % dbname, 'POST')) - - def test_user_db_docs_unauthorized(self): - dbname = self._dbname - self.assertIsNone(self._urlmap.match('/%s/docs' % dbname, 'GET')) - self.assertIsNone(self._urlmap.match('/%s/docs' % dbname, 'PUT')) - self.assertIsNone(self._urlmap.match('/%s/docs' % dbname, 'DELETE')) - self.assertIsNone(self._urlmap.match('/%s/docs' % dbname, 'POST')) - - def test_user_db_doc_unauthorized(self): - dbname = self._dbname - self.assertIsNone(self._urlmap.match('/%s/doc/x' % dbname, 'GET')) - self.assertIsNone(self._urlmap.match('/%s/doc/x' % dbname, 'PUT')) - self.assertIsNone(self._urlmap.match('/%s/doc/x' % dbname, 'DELETE')) - self.assertIsNone(self._urlmap.match('/%s/doc/x' % dbname, 'POST')) - - def test_user_db_sync_authorized(self): - uuid = self._uuid - dbname = self._dbname - match = self._urlmap.match('/%s/sync-from/x' % dbname, 'GET') - self.assertEqual(uuid, match.get('uuid')) - self.assertEqual('x', match.get('source_replica_uid')) - - match = self._urlmap.match('/%s/sync-from/x' % dbname, 'PUT') - self.assertEqual(uuid, match.get('uuid')) - self.assertEqual('x', match.get('source_replica_uid')) - - match = self._urlmap.match('/%s/sync-from/x' % dbname, 'POST') - self.assertEqual(uuid, match.get('uuid')) - self.assertEqual('x', match.get('source_replica_uid')) - - def test_user_db_sync_unauthorized(self): - dbname = self._dbname - self.assertIsNone( - self._urlmap.match('/%s/sync-from/x' % dbname, 'DELETE')) @pytest.mark.usefixtures("method_tmpdir") diff --git a/testing/tests/server/test_url_mapper.py b/testing/tests/server/test_url_mapper.py new file mode 100644 index 00000000..fa99cae7 --- /dev/null +++ b/testing/tests/server/test_url_mapper.py @@ -0,0 +1,131 @@ +# -*- coding: utf-8 -*- +# test_url_mapper.py +# Copyright (C) 2017 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 . +""" +Tests for server-related functionality. +""" + +from twisted.trial import unittest +from uuid import uuid4 + +from leap.soledad.server.url_mapper import URLMapper + + +class URLMapperTestCase(unittest.TestCase): + """ + Test if the URLMapper behaves as expected. + + The following table lists the authorized actions among all possible + u1db remote actions: + + URL path | Authorized actions + -------------------------------------------------- + / | GET + /shared-db | GET + /shared-db/docs | - + /shared-db/doc/{id} | - + /shared-db/sync-from/{source} | - + /user-db | - + /user-db/docs | - + /user-db/doc/{id} | - + /user-db/sync-from/{source} | GET, PUT, POST + """ + + def setUp(self): + self._uuid = uuid4().hex + self._urlmap = URLMapper() + self._dbname = 'user-%s' % self._uuid + + def test_root_authorized(self): + match = self._urlmap.match('/', 'GET') + self.assertIsNotNone(match) + + def test_shared_authorized(self): + self.assertIsNotNone(self._urlmap.match('/shared', 'GET')) + + def test_shared_unauthorized(self): + self.assertIsNone(self._urlmap.match('/shared', 'PUT')) + self.assertIsNone(self._urlmap.match('/shared', 'DELETE')) + self.assertIsNone(self._urlmap.match('/shared', 'POST')) + + def test_shared_docs_unauthorized(self): + self.assertIsNone(self._urlmap.match('/shared/docs', 'GET')) + self.assertIsNone(self._urlmap.match('/shared/docs', 'PUT')) + self.assertIsNone(self._urlmap.match('/shared/docs', 'DELETE')) + self.assertIsNone(self._urlmap.match('/shared/docs', 'POST')) + + def test_shared_doc_authorized(self): + match = self._urlmap.match('/shared/doc/x', 'GET') + self.assertIsNotNone(match) + self.assertEqual('x', match.get('id')) + + match = self._urlmap.match('/shared/doc/x', 'PUT') + self.assertIsNotNone(match) + self.assertEqual('x', match.get('id')) + + match = self._urlmap.match('/shared/doc/x', 'DELETE') + self.assertIsNotNone(match) + self.assertEqual('x', match.get('id')) + + def test_shared_doc_unauthorized(self): + self.assertIsNone(self._urlmap.match('/shared/doc/x', 'POST')) + + def test_shared_sync_unauthorized(self): + self.assertIsNone(self._urlmap.match('/shared/sync-from/x', 'GET')) + self.assertIsNone(self._urlmap.match('/shared/sync-from/x', 'PUT')) + self.assertIsNone(self._urlmap.match('/shared/sync-from/x', 'DELETE')) + self.assertIsNone(self._urlmap.match('/shared/sync-from/x', 'POST')) + + def test_user_db_unauthorized(self): + dbname = self._dbname + self.assertIsNone(self._urlmap.match('/%s' % dbname, 'GET')) + self.assertIsNone(self._urlmap.match('/%s' % dbname, 'PUT')) + self.assertIsNone(self._urlmap.match('/%s' % dbname, 'DELETE')) + self.assertIsNone(self._urlmap.match('/%s' % dbname, 'POST')) + + def test_user_db_docs_unauthorized(self): + dbname = self._dbname + self.assertIsNone(self._urlmap.match('/%s/docs' % dbname, 'GET')) + self.assertIsNone(self._urlmap.match('/%s/docs' % dbname, 'PUT')) + self.assertIsNone(self._urlmap.match('/%s/docs' % dbname, 'DELETE')) + self.assertIsNone(self._urlmap.match('/%s/docs' % dbname, 'POST')) + + def test_user_db_doc_unauthorized(self): + dbname = self._dbname + self.assertIsNone(self._urlmap.match('/%s/doc/x' % dbname, 'GET')) + self.assertIsNone(self._urlmap.match('/%s/doc/x' % dbname, 'PUT')) + self.assertIsNone(self._urlmap.match('/%s/doc/x' % dbname, 'DELETE')) + self.assertIsNone(self._urlmap.match('/%s/doc/x' % dbname, 'POST')) + + def test_user_db_sync_authorized(self): + uuid = self._uuid + dbname = self._dbname + match = self._urlmap.match('/%s/sync-from/x' % dbname, 'GET') + self.assertEqual(uuid, match.get('uuid')) + self.assertEqual('x', match.get('source_replica_uid')) + + match = self._urlmap.match('/%s/sync-from/x' % dbname, 'PUT') + self.assertEqual(uuid, match.get('uuid')) + self.assertEqual('x', match.get('source_replica_uid')) + + match = self._urlmap.match('/%s/sync-from/x' % dbname, 'POST') + self.assertEqual(uuid, match.get('uuid')) + self.assertEqual('x', match.get('source_replica_uid')) + + def test_user_db_sync_unauthorized(self): + dbname = self._dbname + self.assertIsNone( + self._urlmap.match('/%s/sync-from/x' % dbname, 'DELETE')) -- cgit v1.2.3 From 3fb32423b75898f4eb0812c8927dd7f06839e756 Mon Sep 17 00:00:00 2001 From: Victor Shyba Date: Sun, 12 Feb 2017 23:14:55 -0300 Subject: [tests] server parameter is gone, patch the module --- testing/tests/server/test_auth.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'testing/tests') diff --git a/testing/tests/server/test_auth.py b/testing/tests/server/test_auth.py index 00d416d5..c9b941f9 100644 --- a/testing/tests/server/test_auth.py +++ b/testing/tests/server/test_auth.py @@ -29,6 +29,7 @@ from twisted.trial import unittest from twisted.web.resource import IResource from twisted.web.test import test_httpauth +import leap.soledad.server.auth as auth_module from leap.soledad.server.auth import SoledadRealm from leap.soledad.server.auth import TokenChecker from leap.soledad.server.auth import TokenCredentialFactory @@ -73,7 +74,8 @@ class TokenCheckerTestCase(unittest.TestCase): token = {'user_id': 'user', 'type': 'Token'} server = dummy_server(token) # setup the checker with the custom server - checker = TokenChecker(server=server) + checker = TokenChecker() + auth_module.couch_server = lambda url: server # assert the checker *can* verify the creds creds = UsernamePassword('user', 'pass') avatarId = yield checker.requestAvatarId(creds) @@ -85,7 +87,8 @@ class TokenCheckerTestCase(unittest.TestCase): token = None server = dummy_server(token) # setup the checker with the custom server - checker = TokenChecker(server=server) + checker = TokenChecker() + auth_module.couch_server = lambda url: server # assert the checker *cannot* verify the creds creds = UsernamePassword('user', '') with self.assertRaises(UnauthorizedLogin): -- cgit v1.2.3 From 45c58edc99009fc0f2ac044a08a74fd742326a58 Mon Sep 17 00:00:00 2001 From: Victor Shyba Date: Sun, 12 Feb 2017 23:15:30 -0300 Subject: [tests] conf format changed, no more nesting --- testing/tests/server/test_auth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'testing/tests') diff --git a/testing/tests/server/test_auth.py b/testing/tests/server/test_auth.py index c9b941f9..6eb647ee 100644 --- a/testing/tests/server/test_auth.py +++ b/testing/tests/server/test_auth.py @@ -40,7 +40,7 @@ class SoledadRealmTestCase(unittest.TestCase): def test_returned_resource(self): # we have to pass a pool to the realm , otherwise tests will hang - conf = {'soledad-server': {'blobs': False}} + conf = {'blobs': False} pool = reactor.getThreadPool() realm = SoledadRealm(conf=conf, sync_pool=pool) iface, avatar, logout = realm.requestAvatar('any', None, IResource) -- cgit v1.2.3 From 6d7dd39fb3d4f138595f885d19315008d13f8907 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 15 Feb 2017 01:53:01 +0100 Subject: [tests] fix tests --- testing/tests/server/test__server_info.py | 8 ++++++-- testing/tests/server/test_session.py | 5 ++++- 2 files changed, 10 insertions(+), 3 deletions(-) (limited to 'testing/tests') diff --git a/testing/tests/server/test__server_info.py b/testing/tests/server/test__server_info.py index 80770721..40567ef1 100644 --- a/testing/tests/server/test__server_info.py +++ b/testing/tests/server/test__server_info.py @@ -31,9 +31,13 @@ class ServerInfoTestCase(unittest.TestCase): def test_blobs_enabled(self): resource = ServerInfo(True) response = resource.render(DummyRequest([''])) - self.assertEquals(json.loads(response), {'blobs': True}) + _info = json.loads(response) + self.assertEquals(_info['blobs'], True) + self.assertTrue(isinstance(_info['version'], basestring)) def test_blobs_disabled(self): resource = ServerInfo(False) response = resource.render(DummyRequest([''])) - self.assertEquals(json.loads(response), {'blobs': False}) + _info = json.loads(response) + self.assertEquals(_info['blobs'], False) + self.assertTrue(isinstance(_info['version'], basestring)) diff --git a/testing/tests/server/test_session.py b/testing/tests/server/test_session.py index 8131ddb3..b00ab858 100644 --- a/testing/tests/server/test_session.py +++ b/testing/tests/server/test_session.py @@ -156,6 +156,7 @@ class SoledadSessionTestCase(unittest.TestCase): return {} def decode(self, response, request): + print "decode raised" raise UnexpectedException() self.wrapper._credentialFactory = BadFactory() @@ -164,7 +165,9 @@ class SoledadSessionTestCase(unittest.TestCase): child = getChildForRequest(self.wrapper, request) request.render(child) self.assertEqual(request.responseCode, 500) - self.assertEqual(len(self.flushLoggedErrors(UnexpectedException)), 1) + #self.assertEqual(len(self.flushLoggedErrors(UnexpectedException)), 1) + errors = self.flushLoggedErrors(UnexpectedException) + self.assertEqual(len(errors), 1) def test_unexpectedLoginError(self): class UnexpectedException(Exception): -- cgit v1.2.3 From 13fdd28cd7448b11a35c794e69e5c64e1c9cd154 Mon Sep 17 00:00:00 2001 From: Victor Shyba Date: Wed, 15 Feb 2017 05:17:43 -0300 Subject: [style] pep8 --- testing/tests/server/test_session.py | 1 - 1 file changed, 1 deletion(-) (limited to 'testing/tests') diff --git a/testing/tests/server/test_session.py b/testing/tests/server/test_session.py index b00ab858..ebb94476 100644 --- a/testing/tests/server/test_session.py +++ b/testing/tests/server/test_session.py @@ -165,7 +165,6 @@ class SoledadSessionTestCase(unittest.TestCase): child = getChildForRequest(self.wrapper, request) request.render(child) self.assertEqual(request.responseCode, 500) - #self.assertEqual(len(self.flushLoggedErrors(UnexpectedException)), 1) errors = self.flushLoggedErrors(UnexpectedException) self.assertEqual(len(errors), 1) -- cgit v1.2.3 From e6ed77ce83a37dd4fffb8ac560ae34fbee8acc22 Mon Sep 17 00:00:00 2001 From: Victor Shyba Date: Wed, 15 Feb 2017 17:43:17 -0300 Subject: [tests] add tests for preamble encoding --- testing/tests/client/test_crypto.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) (limited to 'testing/tests') diff --git a/testing/tests/client/test_crypto.py b/testing/tests/client/test_crypto.py index 5411a4e8..852448e0 100644 --- a/testing/tests/client/test_crypto.py +++ b/testing/tests/client/test_crypto.py @@ -235,6 +235,37 @@ class SoledadCryptoAESTestCase(BaseSoledadTest): _crypto.decrypt_sym(cyphertext, wrongkey, iv) +class PreambleTestCase(unittest.TestCase): + class doc_info: + doc_id = 'D-deadbeef' + rev = '397932e0c77f45fcb7c3732930e7e9b2:1' + + def setUp(self): + inf = BytesIO(snowden1) + self.blob = _crypto.BlobEncryptor( + self.doc_info, inf, + secret='A' * 96) + + def test_preamble_starts_with_magic_signature(self): + preamble = self.blob._encode_preamble() + assert preamble.startswith(_crypto.BLOB_SIGNATURE_MAGIC) + + def test_preamble_has_cipher_metadata(self): + preamble = self.blob._encode_preamble() + unpacked = _crypto.PACMAN.unpack(preamble) + encryption_scheme, encryption_method = unpacked[1:3] + assert encryption_scheme in _crypto.ENC_SCHEME + assert encryption_method in _crypto.ENC_METHOD + assert unpacked[4] == self.blob.iv + + def test_preamble_has_document_sync_metadata(self): + preamble = self.blob._encode_preamble() + unpacked = _crypto.PACMAN.unpack(preamble) + doc_id, doc_rev = unpacked[5:] + assert doc_id == self.doc_info.doc_id + assert doc_rev == self.doc_info.rev + + def _aes_encrypt(key, iv, data): backend = default_backend() cipher = Cipher(algorithms.AES(key), modes.GCM(iv), backend=backend) -- cgit v1.2.3 From d2ef605af73a592ea21c5bae005f53f483e310a6 Mon Sep 17 00:00:00 2001 From: Victor Shyba Date: Thu, 16 Feb 2017 04:48:58 -0300 Subject: [feature] add doc size to preamble That's necessary for blobs-io. Current code includes backwards compatibility branching and tests, which shall be removed on next releases. --- testing/tests/client/test_crypto.py | 40 ++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) (limited to 'testing/tests') diff --git a/testing/tests/client/test_crypto.py b/testing/tests/client/test_crypto.py index 852448e0..2d4d827b 100644 --- a/testing/tests/client/test_crypto.py +++ b/testing/tests/client/test_crypto.py @@ -110,7 +110,7 @@ class BlobTestCase(unittest.TestCase): assert len(preamble) == _crypto.PACMAN.size unpacked_data = _crypto.PACMAN.unpack(preamble) - magic, sch, meth, ts, iv, doc_id, rev = unpacked_data + magic, sch, meth, ts, iv, doc_id, rev, _ = unpacked_data assert magic == _crypto.BLOB_SIGNATURE_MAGIC assert sch == 1 assert meth == _crypto.ENC_METHOD.aes_256_gcm @@ -241,9 +241,9 @@ class PreambleTestCase(unittest.TestCase): rev = '397932e0c77f45fcb7c3732930e7e9b2:1' def setUp(self): - inf = BytesIO(snowden1) + self.cleartext = BytesIO(snowden1) self.blob = _crypto.BlobEncryptor( - self.doc_info, inf, + self.doc_info, self.cleartext, secret='A' * 96) def test_preamble_starts_with_magic_signature(self): @@ -261,15 +261,45 @@ class PreambleTestCase(unittest.TestCase): def test_preamble_has_document_sync_metadata(self): preamble = self.blob._encode_preamble() unpacked = _crypto.PACMAN.unpack(preamble) - doc_id, doc_rev = unpacked[5:] + doc_id, doc_rev = unpacked[5:7] assert doc_id == self.doc_info.doc_id assert doc_rev == self.doc_info.rev + def test_preamble_has_document_size(self): + preamble = self.blob._encode_preamble() + unpacked = _crypto.PACMAN.unpack(preamble) + size = unpacked[7] + assert size == len(snowden1) -def _aes_encrypt(key, iv, data): + @defer.inlineCallbacks + def test_preamble_can_come_without_size(self): + # XXX: This test case is here only to test backwards compatibility! + preamble = self.blob._encode_preamble() + # repack preamble using legacy format, without doc size + unpacked = _crypto.PACMAN.unpack(preamble) + preamble_without_size = _crypto.LEGACY_PACMAN.pack(*unpacked[0:7]) + # encrypt it manually for custom tag + ciphertext, tag = _aes_encrypt(self.blob.sym_key, self.blob.iv, + self.cleartext.getvalue(), + aead=preamble_without_size) + ciphertext = ciphertext + tag + # encode it + ciphertext = base64.urlsafe_b64encode(ciphertext) + preamble_without_size = base64.urlsafe_b64encode(preamble_without_size) + # decrypt it + ciphertext = preamble_without_size + ' ' + ciphertext + cleartext = yield _crypto.BlobDecryptor( + self.doc_info, BytesIO(ciphertext), + secret='A' * 96).decrypt() + assert cleartext == self.cleartext.getvalue() + + +def _aes_encrypt(key, iv, data, aead=''): backend = default_backend() cipher = Cipher(algorithms.AES(key), modes.GCM(iv), backend=backend) encryptor = cipher.encryptor() + if aead: + encryptor.authenticate_additional_data(aead) return encryptor.update(data) + encryptor.finalize(), encryptor.tag -- cgit v1.2.3 From ac6d87e83f91ed61b160e7cdd968f4a6f3d68f34 Mon Sep 17 00:00:00 2001 From: Victor Shyba Date: Thu, 16 Feb 2017 17:18:06 -0300 Subject: [style] add deprecation warning on legacy decoder --- testing/tests/client/test_crypto.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'testing/tests') diff --git a/testing/tests/client/test_crypto.py b/testing/tests/client/test_crypto.py index 2d4d827b..399fdc99 100644 --- a/testing/tests/client/test_crypto.py +++ b/testing/tests/client/test_crypto.py @@ -292,6 +292,9 @@ class PreambleTestCase(unittest.TestCase): self.doc_info, BytesIO(ciphertext), secret='A' * 96).decrypt() assert cleartext == self.cleartext.getvalue() + warnings = self.flushWarnings() + assert len(warnings) == 1 + assert 'legacy document without size' in warnings[0]['message'] def _aes_encrypt(key, iv, data, aead=''): -- cgit v1.2.3 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. --- 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 +- 4 files changed, 12 insertions(+), 14 deletions(-) (limited to 'testing/tests') 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 From 7eb1ffa1d49a7e0016c5980da71151e715abc77a Mon Sep 17 00:00:00 2001 From: drebs Date: Tue, 21 Feb 2017 14:16:26 -0300 Subject: [feat] add configurable blobs path in server - Resolves: #8777 --- testing/tests/server/test_config.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'testing/tests') diff --git a/testing/tests/server/test_config.py b/testing/tests/server/test_config.py index 133057f5..d2a8a9de 100644 --- a/testing/tests/server/test_config.py +++ b/testing/tests/server/test_config.py @@ -64,5 +64,6 @@ class ConfigurationParsingTest(unittest.TestCase): 'admin_netrc': '/etc/couchdb/couchdb-soledad-admin.netrc', 'batching': False, - 'blobs': False} + 'blobs': False, + 'blobs_path': '/srv/leap/soledad/blobs'} self.assertDictEqual(expected, config['soledad-server']) -- cgit v1.2.3 From 9a73db4ca31c86bb8c657360e891d55329d65283 Mon Sep 17 00:00:00 2001 From: Victor Shyba Date: Wed, 11 Jan 2017 18:33:05 -0300 Subject: [test] fix test_sync_target for error handling It needs a defer.Deferred --- testing/tests/sync/test_sync_target.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'testing/tests') diff --git a/testing/tests/sync/test_sync_target.py b/testing/tests/sync/test_sync_target.py index 34702362..25136ba1 100644 --- a/testing/tests/sync/test_sync_target.py +++ b/testing/tests/sync/test_sync_target.py @@ -63,7 +63,8 @@ class TestSoledadParseReceivedDocResponse(unittest.TestCase): """ def parse(self, stream): - parser = DocStreamReceiver(None, None, lambda *_: defer.succeed(42)) + parser = DocStreamReceiver(None, defer.Deferred(), + lambda *_: defer.succeed(42)) parser.dataReceived(stream) parser.finish() -- cgit v1.2.3 From d285f07b365645a0c327a8d41402d710456583b6 Mon Sep 17 00:00:00 2001 From: drebs Date: Mon, 27 Feb 2017 11:22:11 -0300 Subject: [test] use new way of indicating the tcp port for twisted web --- testing/tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'testing/tests') diff --git a/testing/tests/conftest.py b/testing/tests/conftest.py index decfb0a9..1d3125d5 100644 --- a/testing/tests/conftest.py +++ b/testing/tests/conftest.py @@ -104,7 +104,7 @@ class SoledadServer(object): '--pidfile=%s' % self._pidfile, 'web', '--class=leap.soledad.server.entrypoint.SoledadEntrypoint', - '--port=2424' + '--port=tcp:2424' ]) def _create_conf_file(self): -- cgit v1.2.3 From 5af57250f2b1cd174fa75c559bac6ea179c43d09 Mon Sep 17 00:00:00 2001 From: drebs Date: Tue, 28 Feb 2017 10:22:06 -0300 Subject: [test] bugfix: actually use an empty local db in download benchmarks We were previously not using an empty local db for download benchmark tests, so there was actually nothing to sync. This commit fixes that by adding a way to force an empty local db on soledad client instantiation. --- testing/tests/benchmarks/test_sync.py | 2 +- testing/tests/conftest.py | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) (limited to 'testing/tests') diff --git a/testing/tests/benchmarks/test_sync.py b/testing/tests/benchmarks/test_sync.py index 1501d74b..ee2e2960 100644 --- a/testing/tests/benchmarks/test_sync.py +++ b/testing/tests/benchmarks/test_sync.py @@ -41,7 +41,7 @@ def create_download(downloads, size): # ensures we are dealing with properly encrypted docs def setup(): - return soledad_client() + return soledad_client(force_fresh_db=True) def sync(clean_client): return clean_client.sync() diff --git a/testing/tests/conftest.py b/testing/tests/conftest.py index 1d3125d5..49cc35f6 100644 --- a/testing/tests/conftest.py +++ b/testing/tests/conftest.py @@ -191,9 +191,19 @@ def soledad_client(tmpdir, soledad_server, remote_db, soledad_dbs, request): soledad_dbs(default_uuid) # get a soledad instance - def create(): - secrets_path = os.path.join(tmpdir.strpath, '%s.secret' % default_uuid) - local_db_path = os.path.join(tmpdir.strpath, '%s.db' % default_uuid) + def create(force_fresh_db=False): + secrets_file = '%s.secret' % default_uuid + secrets_path = os.path.join(tmpdir.strpath, secrets_file) + + # in some tests we might want to use the same user and remote database + # but with a clean/empty local database (i.e. download benchmarks), so + # here we provide a way to do that. + db_file = '%s.db' % default_uuid + if force_fresh_db: + prefix = uuid4().hex + db_file = prefix + '-' + db_file + local_db_path = os.path.join(tmpdir.strpath, db_file) + soledad_client = Soledad( default_uuid, unicode(passphrase), -- cgit v1.2.3 From 6deadf8adeb8c6b4a98e2d69e2bf3b1447a6b4ca Mon Sep 17 00:00:00 2001 From: drebs Date: Wed, 1 Mar 2017 13:39:04 -0300 Subject: [test] mark benchmark tests using their group names --- testing/tests/benchmarks/conftest.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'testing/tests') diff --git a/testing/tests/benchmarks/conftest.py b/testing/tests/benchmarks/conftest.py index a9cc3464..1b99d96e 100644 --- a/testing/tests/benchmarks/conftest.py +++ b/testing/tests/benchmarks/conftest.py @@ -12,12 +12,30 @@ from leap.common.events import server server.ensure_server() +# +# pytest customizations +# + def pytest_addoption(parser): parser.addoption( "--num-docs", type="int", default=100, help="the number of documents to use in performance tests") +# mark benchmark tests using their group names (thanks ionelmc! :) +def pytest_collection_modifyitems(items): + for item in items: + bench = item.get_marker("benchmark") + if bench and bench.kwargs.get('group'): + group = bench.kwargs['group'] + marker = getattr(pytest.mark, 'benchmark_' + group) + item.add_marker(marker) + + +# +# benchmark fixtures +# + @pytest.fixture() def payload(): def generate(size): -- cgit v1.2.3 From 32c950d16e7bea55bb7af3f4650af857be9027e0 Mon Sep 17 00:00:00 2001 From: drebs Date: Wed, 1 Mar 2017 21:02:41 -0300 Subject: [test] improve twistd startup and termination - use subprocess.check_call() to ensure any errors during twistd startup will properly show up on test reports. - use SIGTERM instead of SIGKILL to gracefully terminate twistd. --- testing/tests/conftest.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'testing/tests') diff --git a/testing/tests/conftest.py b/testing/tests/conftest.py index 49cc35f6..c077828f 100644 --- a/testing/tests/conftest.py +++ b/testing/tests/conftest.py @@ -6,7 +6,7 @@ import signal import time from hashlib import sha512 -from subprocess import call +from subprocess import check_call from urlparse import urljoin from uuid import uuid4 @@ -98,7 +98,7 @@ class SoledadServer(object): def start(self): self._create_conf_file() # start the server - call([ + check_call([ 'twistd', '--logfile=%s' % self._logfile, '--pidfile=%s' % self._pidfile, @@ -118,7 +118,7 @@ class SoledadServer(object): def stop(self): pid = get_pid(self._pidfile) - os.kill(pid, signal.SIGKILL) + os.kill(pid, signal.SIGTERM) @pytest.fixture(scope='module') -- cgit v1.2.3 From 32c6d2eae998ceec302995e52c70a0636ca1e463 Mon Sep 17 00:00:00 2001 From: drebs Date: Thu, 2 Mar 2017 15:47:04 -0300 Subject: [test] add comments explaining behaviour of upload/download benchmark --- testing/tests/benchmarks/test_sync.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'testing/tests') diff --git a/testing/tests/benchmarks/test_sync.py b/testing/tests/benchmarks/test_sync.py index ee2e2960..fcfab998 100644 --- a/testing/tests/benchmarks/test_sync.py +++ b/testing/tests/benchmarks/test_sync.py @@ -11,6 +11,12 @@ def load_up(client, amount, payload): yield gatherResults(deferreds) +# Each test created with this function will: +# +# - get a fresh client. +# - iterate: +# - setup: create N docs of a certain size +# - benchmark: sync() -- uploads N docs. def create_upload(uploads, size): @pytest.inlineCallbacks @pytest.mark.benchmark(group="test_upload") @@ -29,6 +35,14 @@ test_upload_100_100k = create_upload(100, 100 * 1000) test_upload_1000_10k = create_upload(1000, 10 * 1000) +# Each test created with this function will: +# +# - get a fresh client. +# - create N docs of a certain size +# - sync (uploads those docs) +# - iterate: +# - setup: get a fresh client with empty local db +# - benchmark: sync() -- downloads N docs. def create_download(downloads, size): @pytest.inlineCallbacks @pytest.mark.benchmark(group="test_download") -- cgit v1.2.3