summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordrebs <drebs@leap.se>2016-10-03 19:27:42 -0300
committerdrebs <drebs@leap.se>2016-10-03 19:27:42 -0300
commit4e06eb370b99f2d343e96f774a3ad9b8b77c9548 (patch)
tree749fd8131c31a562168978de27e7efd45b696364
parente13aefd14e82794622613802733713c6226e1d59 (diff)
[feature] check for user dbs couch schema versions
-rw-r--r--common/src/leap/soledad/common/couch/state.py34
-rw-r--r--common/src/leap/soledad/common/errors.py13
-rw-r--r--server/src/leap/soledad/server/__init__.py2
-rw-r--r--testing/tests/couch/conftest.py31
-rw-r--r--testing/tests/couch/test_command.py8
-rw-r--r--testing/tests/couch/test_state.py23
-rw-r--r--testing/tests/perf/conftest.py4
-rw-r--r--testing/tests/server/test_server.py4
8 files changed, 109 insertions, 10 deletions
diff --git a/common/src/leap/soledad/common/couch/state.py b/common/src/leap/soledad/common/couch/state.py
index e3cd1a24..1d045a9d 100644
--- a/common/src/leap/soledad/common/couch/state.py
+++ b/common/src/leap/soledad/common/couch/state.py
@@ -17,6 +17,7 @@
"""
Server state using CouchDatabase as backend.
"""
+import couchdb
import re
import time
from urlparse import urljoin
@@ -25,9 +26,14 @@ from hashlib import sha512
from leap.soledad.common.log import getLogger
from leap.soledad.common.couch import CouchDatabase
from leap.soledad.common.couch import couch_server
+from leap.soledad.common.couch import CONFIG_DOC_ID
+from leap.soledad.common.couch import SCHEMA_VERSION
+from leap.soledad.common.couch import SCHEMA_VERSION_KEY
from leap.soledad.common.command import exec_validated_cmd
from leap.soledad.common.l2db.remote.server_state import ServerState
from leap.soledad.common.l2db.errors import Unauthorized
+from leap.soledad.common.errors import WrongCouchSchemaVersionError
+from leap.soledad.common.errors import MissingCouchConfigDocumentError
logger = getLogger(__name__)
@@ -59,15 +65,41 @@ class CouchServerState(ServerState):
TOKENS_TYPE_DEF = "Token"
TOKENS_USER_ID_KEY = "user_id"
- def __init__(self, couch_url, create_cmd=None):
+ def __init__(self, couch_url, create_cmd=None,
+ check_schema_versions=True):
"""
Initialize the couch server state.
:param couch_url: The URL for the couch database.
:type couch_url: str
+ :param check_schema_versions: Whether to check couch schema version of
+ user dbs.
+ :type check_schema_versions: bool
"""
self.couch_url = couch_url
self.create_cmd = create_cmd
+ if check_schema_versions:
+ self._check_schema_versions()
+
+ def _check_schema_versions(self):
+ """
+ Check that all user databases use the correct couch schema.
+ """
+ server = couchdb.client.Server(self.couch_url)
+ for dbname in server:
+ if not dbname.startswith('user-'):
+ continue
+ db = server[dbname]
+
+ # if there are documents, ensure that a config doc exists
+ config_doc = db.get(CONFIG_DOC_ID)
+ if config_doc:
+ if config_doc[SCHEMA_VERSION_KEY] != SCHEMA_VERSION:
+ raise WrongCouchSchemaVersionError(dbname)
+ else:
+ result = db.view('_all_docs', limit=1)
+ if result.total_rows != 0:
+ raise MissingCouchConfigDocumentError(dbname)
def open_database(self, dbname):
"""
diff --git a/common/src/leap/soledad/common/errors.py b/common/src/leap/soledad/common/errors.py
index dec871c9..d543a3de 100644
--- a/common/src/leap/soledad/common/errors.py
+++ b/common/src/leap/soledad/common/errors.py
@@ -77,7 +77,6 @@ http_errors.ERROR_STATUSES = set(
class InvalidURLError(Exception):
-
"""
Exception raised when Soledad encounters a malformed URL.
"""
@@ -90,3 +89,15 @@ class BackendNotReadyError(SoledadError):
"""
wire_description = "backend not ready"
status = 500
+
+
+class WrongCouchSchemaVersionError(SoledadError):
+ """
+ Raised in case there is a user database with wrong couch schema version.
+ """
+
+
+class MissingCouchConfigDocumentError(SoledadError):
+ """
+ Raised if a database has documents but lacks the couch config document.
+ """
diff --git a/server/src/leap/soledad/server/__init__.py b/server/src/leap/soledad/server/__init__.py
index 97bcf888..2e1a453a 100644
--- a/server/src/leap/soledad/server/__init__.py
+++ b/server/src/leap/soledad/server/__init__.py
@@ -90,7 +90,7 @@ from leap.soledad.server.gzip_middleware import GzipMiddleware
from leap.soledad.server.sync import SyncResource
from leap.soledad.server.sync import MAX_REQUEST_SIZE
from leap.soledad.server.sync import MAX_ENTRY_SIZE
-from soledad.server.config import load_configuration
+from leap.soledad.server.config import load_configuration
from leap.soledad.common import SHARED_DB_NAME
from leap.soledad.common.backend import SoledadBackend
diff --git a/testing/tests/couch/conftest.py b/testing/tests/couch/conftest.py
new file mode 100644
index 00000000..1074f091
--- /dev/null
+++ b/testing/tests/couch/conftest.py
@@ -0,0 +1,31 @@
+import couchdb
+import pytest
+import random
+import string
+
+
+@pytest.fixture
+def random_name():
+ return 'user-' + ''.join(
+ random.choice(
+ string.ascii_lowercase) for _ in range(10))
+
+
+class RandomDatabase(object):
+
+ def __init__(self, couch_url, name):
+ self.couch_url = couch_url
+ self.name = name
+ self.server = couchdb.client.Server(couch_url)
+ self.database = self.server.create(name)
+
+ def teardown(self):
+ self.server.delete(self.name)
+
+
+@pytest.fixture
+def db(random_name, request):
+ couch_url = request.config.getoption('--couch-url')
+ db = RandomDatabase(couch_url, random_name)
+ request.addfinalizer(db.teardown)
+ return db
diff --git a/testing/tests/couch/test_command.py b/testing/tests/couch/test_command.py
index 6a96ebf9..68097fb1 100644
--- a/testing/tests/couch/test_command.py
+++ b/testing/tests/couch/test_command.py
@@ -9,7 +9,8 @@ from mock import Mock
class CommandBasedDBCreationTest(unittest.TestCase):
def test_ensure_db_using_custom_command(self):
- state = couch_state.CouchServerState("url", create_cmd="/bin/echo")
+ state = couch_state.CouchServerState(
+ "url", create_cmd="/bin/echo", check_schema_versions=False)
mock_db = Mock()
mock_db.replica_uid = 'replica_uid'
state.open_database = Mock(return_value=mock_db)
@@ -18,11 +19,12 @@ class CommandBasedDBCreationTest(unittest.TestCase):
self.assertEquals(mock_db.replica_uid, replica_uid)
def test_raises_unauthorized_on_failure(self):
- state = couch_state.CouchServerState("url", create_cmd="inexistent")
+ state = couch_state.CouchServerState(
+ "url", create_cmd="inexistent", check_schema_versions=False)
self.assertRaises(u1db_errors.Unauthorized,
state.ensure_database, "user-1337")
def test_raises_unauthorized_by_default(self):
- state = couch_state.CouchServerState("url")
+ state = couch_state.CouchServerState("url", check_schema_versions=False)
self.assertRaises(u1db_errors.Unauthorized,
state.ensure_database, "user-1337")
diff --git a/testing/tests/couch/test_state.py b/testing/tests/couch/test_state.py
new file mode 100644
index 00000000..a53ba076
--- /dev/null
+++ b/testing/tests/couch/test_state.py
@@ -0,0 +1,23 @@
+import pytest
+
+from leap.soledad.common.couch import CONFIG_DOC_ID
+from leap.soledad.common.couch import SCHEMA_VERSION
+from leap.soledad.common.couch import SCHEMA_VERSION_KEY
+from leap.soledad.common.couch.state import CouchServerState
+
+from leap.soledad.common.errors import WrongCouchSchemaVersionError
+from leap.soledad.common.errors import MissingCouchConfigDocumentError
+
+
+def test_wrong_couch_version_raises(db):
+ wrong_schema_version = SCHEMA_VERSION + 1
+ db.database.create(
+ {'_id': CONFIG_DOC_ID, SCHEMA_VERSION_KEY: wrong_schema_version})
+ with pytest.raises(WrongCouchSchemaVersionError):
+ CouchServerState(db.couch_url, create_cmd='/bin/echo')
+
+
+def test_missing_config_doc_raises(db):
+ db.database.create({})
+ with pytest.raises(MissingCouchConfigDocumentError):
+ CouchServerState(db.couch_url, create_cmd='/bin/echo')
diff --git a/testing/tests/perf/conftest.py b/testing/tests/perf/conftest.py
index 3681025f..5ac1f3c0 100644
--- a/testing/tests/perf/conftest.py
+++ b/testing/tests/perf/conftest.py
@@ -193,8 +193,8 @@ def soledad_server(tmpdir_factory, request):
def txbenchmark(benchmark):
def blockOnThread(*args, **kwargs):
return threads.deferToThread(
- benchmark, threads.blockingCallFromThread,
- reactor, *args, **kwargs)
+ benchmark, threads.blockingCallFromThread,
+ reactor, *args, **kwargs)
return blockOnThread
diff --git a/testing/tests/server/test_server.py b/testing/tests/server/test_server.py
index 18f92d88..6bbcf002 100644
--- a/testing/tests/server/test_server.py
+++ b/testing/tests/server/test_server.py
@@ -43,8 +43,8 @@ from test_soledad.util import (
from leap.soledad.common import crypto
from leap.soledad.client import Soledad
-from leap.soledad.server import load_configuration
-from leap.soledad.server import CONFIG_DEFAULTS
+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 SoledadTokenAuthMiddleware