diff options
5 files changed, 115 insertions, 11 deletions
diff --git a/common/changes/feature_3501-add-verification-for-couch-permissions b/common/changes/feature_3501-add-verification-for-couch-permissions new file mode 100644 index 00000000..3c2687d5 --- /dev/null +++ b/common/changes/feature_3501-add-verification-for-couch-permissions @@ -0,0 +1 @@ +  o Add verification for couch permissions. Closes #3501. diff --git a/common/src/leap/soledad/common/couch.py b/common/src/leap/soledad/common/couch.py index 9642e8f3..973f8b49 100644 --- a/common/src/leap/soledad/common/couch.py +++ b/common/src/leap/soledad/common/couch.py @@ -21,6 +21,8 @@  import uuid  import re  import simplejson as json +import socket +import logging  from base64 import b64encode, b64decode @@ -30,7 +32,7 @@ from u1db.backends.inmemory import InMemoryIndex  from u1db.remote.server_state import ServerState  from u1db.errors import DatabaseDoesNotExist  from couchdb.client import Server, Document as CouchDocument -from couchdb.http import ResourceNotFound +from couchdb.http import ResourceNotFound, Unauthorized  from leap.soledad.common.objectstore import ( @@ -39,6 +41,9 @@ from leap.soledad.common.objectstore import (  ) +logger = logging.getLogger(__name__) + +  class InvalidURLError(Exception):      """      Exception raised when Soledad encounters a malformed URL. @@ -410,6 +415,15 @@ class CouchSyncTarget(ObjectStoreSyncTarget):      """      Functionality for using a CouchDatabase as a synchronization target.      """ +    pass + + +class NotEnoughCouchPermissions(Exception): +    """ +    Raised when failing to assert for enough permissions on underlying Couch +    Database. +    """ +    pass  class CouchServerState(ServerState): @@ -417,8 +431,83 @@ class CouchServerState(ServerState):      Inteface of the WSGI server with the CouchDB backend.      """ -    def __init__(self, couch_url): +    def __init__(self, couch_url, shared_db_name, tokens_db_name, +                 user_db_prefix): +        """ +        Initialize the couch server state. + +        @param couch_url: The URL for the couch database. +        @type couch_url: str +        @param shared_db_name: The name of the shared database. +        @type shared_db_name: str +        @param tokens_db_name: The name of the tokens database. +        @type tokens_db_name: str +        @param user_db_prefix: The prefix for user database names. +        @type user_db_prefix: str +        """          self._couch_url = couch_url +        self._shared_db_name = shared_db_name +        self._tokens_db_name = tokens_db_name +        self._user_db_prefix = user_db_prefix +        try: +            self._check_couch_permissions() +        except NotEnoughCouchPermissions: +            logger.error("Not enough permissions on underlying couch " +                         "database (%s)." % self._couch_url) +        except (socket.error, socket.gaierror, socket.herror, +                socket.timeout), e: +            logger.error("Socket problem while trying to reach underlying " +                         "couch database: (%s, %s)." % +                         (self._couch_url, e)) + +    def _check_couch_permissions(self): +        """ +        Assert that Soledad Server has enough permissions on the underlying couch +        database. + +        Soledad Server has to be able to do the following in the couch server: + +            * Create, read and write from/to 'shared' db. +            * Create, read and write from/to 'user-<anything>' dbs. +            * Read from 'tokens' db. + +        This function tries to perform the actions above using the "low level" +        couch library to ensure that Soledad Server can do everything it needs on +        the underlying couch database. + +        @param couch_url: The URL of the couch database. +        @type couch_url: str + +        @raise NotEnoughCouchPermissions: Raised in case there are not enough +            permissions to read/write/create the needed couch databases. +        @rtype: bool +        """ + +        def _open_couch_db(dbname): +            server = Server(url=self._couch_url) +            try: +                server[dbname] +            except ResourceNotFound: +                server.create(dbname) +            return server[dbname] + +        def _create_delete_test_doc(db): +            doc_id, _ = db.save({'test': 'document'}) +            doc = db[doc_id] +            db.delete(doc) + +        try: +            # test read/write auth for shared db +            _create_delete_test_doc( +                _open_couch_db(self._shared_db_name)) +            # test read/write auth for user-<something> db +            _create_delete_test_doc( +                _open_couch_db('%stest-db' % self._user_db_prefix)) +            # test read auth for tokens db +            tokensdb = _open_couch_db(self._tokens_db_name) +            tokensdb.info() +        except Unauthorized: +            raise NotEnoughCouchPermissions(self._couch_url)      def open_database(self, dbname):          """ diff --git a/common/src/leap/soledad/common/tests/test_server.py b/common/src/leap/soledad/common/tests/test_server.py index beb7e04d..1ea4d615 100644 --- a/common/src/leap/soledad/common/tests/test_server.py +++ b/common/src/leap/soledad/common/tests/test_server.py @@ -310,7 +310,8 @@ class EncryptedSyncTestCase(              secret_id=secret_id)      def make_app(self): -        self.request_state = CouchServerState(self._couch_url) +        self.request_state = CouchServerState( +            self._couch_url, 'shared', 'tokens', 'user-')          return self.make_app_with_state(self.request_state)      def setUp(self): diff --git a/server/changes/feature_3501-add-verification-for-couch-permissions b/server/changes/feature_3501-add-verification-for-couch-permissions new file mode 100644 index 00000000..9206c708 --- /dev/null +++ b/server/changes/feature_3501-add-verification-for-couch-permissions @@ -0,0 +1 @@ +  o Verify for couch permissions when starting server. Closes #3501. diff --git a/server/src/leap/soledad/server/__init__.py b/server/src/leap/soledad/server/__init__.py index 67b0611d..b4b715e2 100644 --- a/server/src/leap/soledad/server/__init__.py +++ b/server/src/leap/soledad/server/__init__.py @@ -19,12 +19,19 @@  """  A U1DB server that stores data using CouchDB as its persistence layer. -This should be run with: -    twistd -n web --wsgi=leap.soledad.server.application --port=2424 +This is written as a Twisted application and intended to be run using the +twistd command. To start the soledad server, run: + +    twistd -n web --wsgi=leap.soledad.server.application --port=X + +An initscript is included and will be installed system wide to make it +feasible to start and stop the Soledad server service using a standard +interface.  """  import configparser +  from u1db.remote import http_app @@ -116,13 +123,18 @@ def load_configuration(file_path):  # Run as Twisted WSGI Resource  #----------------------------------------------------------------------------- -conf = load_configuration('/etc/leap/soledad-server.conf') -state = CouchServerState(conf['couch_url']) - -# WSGI application that may be used by `twistd -web` -application = SoledadTokenAuthMiddleware(SoledadApp(state)) +def application(environ, start_response): +    conf = load_configuration('/etc/leap/soledad-server.conf') +    state = CouchServerState( +        conf['couch_url'], +        SoledadApp.SHARED_DB_NAME, +        SoledadTokenAuthMiddleware.TOKENS_DB, +        SoledadApp.USER_DB_PREFIX) +    # WSGI application that may be used by `twistd -web` +    application = SoledadTokenAuthMiddleware(SoledadApp(state)) +    resource = WSGIResource(reactor, reactor.getThreadPool(), application) +    return application(environ, start_response) -resource = WSGIResource(reactor, reactor.getThreadPool(), application)  from ._version import get_versions  __version__ = get_versions()['version']  | 
