diff options
-rw-r--r-- | src/leap/soledad/common/couch/state.py | 86 | ||||
-rw-r--r-- | src/leap/soledad/server/_wsgi.py | 3 | ||||
-rw-r--r-- | src/leap/soledad/server/entrypoints.py | 2 | ||||
-rw-r--r-- | tests/couch/test_command.py | 7 | ||||
-rw-r--r-- | tests/couch/test_state.py | 23 |
5 files changed, 46 insertions, 75 deletions
diff --git a/src/leap/soledad/common/couch/state.py b/src/leap/soledad/common/couch/state.py index d9ff61a2..8cbe0934 100644 --- a/src/leap/soledad/common/couch/state.py +++ b/src/leap/soledad/common/couch/state.py @@ -17,12 +17,10 @@ """ Server state using CouchDatabase as backend. """ +import couchdb import re -import treq from six.moves.urllib.parse import urljoin -from twisted.internet import defer -from urlparse import urlsplit from leap.soledad.common.log import getLogger from leap.soledad.common.couch import CouchDatabase @@ -53,61 +51,14 @@ def is_db_name_valid(name): return re.match(db_name_regex, name) is not None -@defer.inlineCallbacks -def _check_db(url, db, auth, agent=None): - # if there are documents, ensure that a config doc exists - db_url = urljoin(url, '%s/' % db) - config_doc_url = urljoin(db_url, CONFIG_DOC_ID) - res = yield treq.get(config_doc_url, auth=auth, agent=agent) - raise Exception - - if res.code != 200 and res.code != 404: - raise Exception - - if res.code == 404: - res = yield treq.get(urljoin(db_url, '_all_docs'), auth=auth, - params={'limit': 1}, agent=agent) - docs = yield res.json() - if docs['total_rows'] != 0: - logger.error( - "Missing couch config document in database %s" % db) - raise MissingCouchConfigDocumentError(db) - - if res.code == 200: - config_doc = yield res.json() - if config_doc[SCHEMA_VERSION_KEY] != SCHEMA_VERSION: - logger.error( - "Unsupported database schema in database %s" % db) - raise WrongCouchSchemaVersionError(db) - - -@defer.inlineCallbacks -def check_schema_versions(couch_url, agent=None): - """ - Check that all user databases use the correct couch schema. - """ - url = urlsplit(couch_url) - auth = (url.username, url.password) if url.username else None - url = "%s://%s:%d" % (url.scheme, url.hostname, url.port) - res = yield treq.get(urljoin(url, '_all_dbs'), auth=auth, agent=agent) - dbs = yield res.json() - deferreds = [] - semaphore = defer.DeferredSemaphore(20) - for db in dbs: - if not db.startswith('user-'): - continue - d = semaphore.run(_check_db, url, db, auth, agent=agent) - deferreds.append(d) - yield defer.gatherResults(deferreds) - - class CouchServerState(ServerState): """ Inteface of the WSGI server with the CouchDB backend. """ - def __init__(self, couch_url, create_cmd=None): + def __init__(self, couch_url, create_cmd=None, + check_schema_versions=False): """ Initialize the couch server state. @@ -118,9 +69,40 @@ class CouchServerState(ServerState): name and should access CouchDB with necessary privileges, which server lacks for security reasons. :type create_cmd: str + :param check_schema_versions: Whether to check couch schema version of + user dbs. Set to False as this is only + intended to run once during start-up. + :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: + logger.error( + "Unsupported database schema in database %s" % dbname) + raise WrongCouchSchemaVersionError(dbname) + else: + result = db.view('_all_docs', limit=1) + if result.total_rows != 0: + logger.error( + "Missing couch config document in database %s" + % dbname) + raise MissingCouchConfigDocumentError(dbname) def open_database(self, dbname): """ diff --git a/src/leap/soledad/server/_wsgi.py b/src/leap/soledad/server/_wsgi.py index 510cb7b9..f1b0018d 100644 --- a/src/leap/soledad/server/_wsgi.py +++ b/src/leap/soledad/server/_wsgi.py @@ -33,7 +33,8 @@ __all__ = ['init_couch_state', 'get_sync_resource'] def _get_couch_state(conf): - state = CouchServerState(conf['couch_url'], create_cmd=conf['create_cmd']) + state = CouchServerState(conf['couch_url'], create_cmd=conf['create_cmd'], + check_schema_versions=True) SoledadBackend.BATCH_SUPPORT = conf.get('batching', False) return state diff --git a/src/leap/soledad/server/entrypoints.py b/src/leap/soledad/server/entrypoints.py index 9bccbcf4..7d18ca58 100644 --- a/src/leap/soledad/server/entrypoints.py +++ b/src/leap/soledad/server/entrypoints.py @@ -26,7 +26,6 @@ from twisted.internet import reactor from twisted.python import threadpool from twisted.logger import Logger -from ..common.couch.state import check_schema_versions from .auth import localPortal, publicPortal from .session import SoledadSession from ._config import get_config @@ -74,5 +73,4 @@ def check_conf(): reactor.callWhenRunning(check_conf) -reactor.callWhenRunning(check_schema_versions, conf['couch_url']) reactor.callWhenRunning(init_couch_state, conf) diff --git a/tests/couch/test_command.py b/tests/couch/test_command.py index 52719065..9fb2c153 100644 --- a/tests/couch/test_command.py +++ b/tests/couch/test_command.py @@ -10,7 +10,7 @@ class CommandBasedDBCreationTest(unittest.TestCase): def test_ensure_db_using_custom_command(self): state = couch_state.CouchServerState( - "url", create_cmd="/bin/echo") + "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) @@ -20,11 +20,12 @@ class CommandBasedDBCreationTest(unittest.TestCase): def test_raises_unauthorized_on_failure(self): state = couch_state.CouchServerState( - "url", create_cmd="inexistent") + "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/tests/couch/test_state.py b/tests/couch/test_state.py index 07c03702..e5ac3704 100644 --- a/tests/couch/test_state.py +++ b/tests/couch/test_state.py @@ -2,15 +2,13 @@ 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 check_schema_versions +from leap.soledad.common.couch.state import CouchServerState from uuid import uuid4 from leap.soledad.common.errors import WrongCouchSchemaVersionError from leap.soledad.common.errors import MissingCouchConfigDocumentError from test_soledad.util import CouchDBTestCase -from twisted.internet import defer - class CouchDesignDocsTests(CouchDBTestCase): @@ -18,26 +16,17 @@ class CouchDesignDocsTests(CouchDBTestCase): CouchDBTestCase.setUp(self) self.db = self.couch_server.create('user-' + uuid4().hex) self.addCleanup(self.delete_db, self.db.name) - from twisted.internet import reactor - from twisted.web.client import HTTPConnectionPool, Agent - self.pool = HTTPConnectionPool(reactor, persistent=False) - self.agent = Agent(reactor, pool=self.pool) - - @defer.inlineCallbacks - def tearDown(self): - yield self.pool.closeCachedConnections() - @defer.inlineCallbacks def test_wrong_couch_version_raises(self): wrong_schema_version = SCHEMA_VERSION + 1 self.db.create( {'_id': CONFIG_DOC_ID, SCHEMA_VERSION_KEY: wrong_schema_version}) with pytest.raises(WrongCouchSchemaVersionError): - yield check_schema_versions(self.couch_url, agent=self.agent) + CouchServerState(self.couch_url, create_cmd='/bin/echo', + check_schema_versions=True) - @defer.inlineCallbacks - @pytest.mark.runme def test_missing_config_doc_raises(self): self.db.create({}) - with pytest.raises(Exception): - yield check_schema_versions(self.couch_url, agent=self.agent) + with pytest.raises(MissingCouchConfigDocumentError): + CouchServerState(self.couch_url, create_cmd='/bin/echo', + check_schema_versions=True) |