diff options
Diffstat (limited to 'common/src')
| -rw-r--r-- | common/src/leap/soledad/common/couch/state.py | 46 | ||||
| -rw-r--r-- | common/src/leap/soledad/common/tests/test_server.py | 31 | 
2 files changed, 77 insertions, 0 deletions
diff --git a/common/src/leap/soledad/common/couch/state.py b/common/src/leap/soledad/common/couch/state.py index 39d49fa0..40c0d55b 100644 --- a/common/src/leap/soledad/common/couch/state.py +++ b/common/src/leap/soledad/common/couch/state.py @@ -19,11 +19,14 @@ Server state using CouchDatabase as backend.  """  import re  import logging +import time  from urlparse import urljoin +from hashlib import sha512  from u1db.remote.server_state import ServerState  from leap.soledad.common.command import exec_validated_cmd  from leap.soledad.common.couch import CouchDatabase +from leap.soledad.common.couch import couch_server  from u1db.errors import Unauthorized @@ -50,6 +53,12 @@ class CouchServerState(ServerState):      Inteface of the WSGI server with the CouchDB backend.      """ +    TOKENS_DB_PREFIX = "tokens_" +    TOKENS_DB_EXPIRE = 30 * 24 * 3600  # 30 days in seconds +    TOKENS_TYPE_KEY = "type" +    TOKENS_TYPE_DEF = "Token" +    TOKENS_USER_ID_KEY = "user_id" +      def __init__(self, couch_url, create_cmd=None):          """          Initialize the couch server state. @@ -112,3 +121,40 @@ class CouchServerState(ServerState):                               delete databases.          """          raise Unauthorized() + +    def verify_token(self, uuid, token): +        """ +        Query couchdb to decide if C{token} is valid for C{uuid}. + +        @param uuid: The user uuid. +        @type uuid: str +        @param token: The token. +        @type token: str +        """ +        with couch_server(self.couch_url) as server: +            # the tokens db rotates every 30 days, and the current db name is +            # "tokens_NNN", where NNN is the number of seconds since epoch divided +            # by the rotate period in seconds. When rotating, old and new tokens +            # db coexist during a certain window of time and valid tokens are +            # replicated from the old db to the new one. See: +            # https://leap.se/code/issues/6785 +            dbname = self._tokens_dbname() +            db = server[dbname] +        # lookup key is a hash of the token to prevent timing attacks. +        token = db.get(sha512(token).hexdigest()) +        if token is None: +            return False +        # we compare uuid hashes to avoid possible timing attacks that +        # might exploit python's builtin comparison operator behaviour, +        # which fails immediatelly when non-matching bytes are found. +        couch_uuid_hash = sha512(token[self.TOKENS_USER_ID_KEY]).digest() +        req_uuid_hash = sha512(uuid).digest() +        if token[self.TOKENS_TYPE_KEY] != self.TOKENS_TYPE_DEF \ +                or couch_uuid_hash != req_uuid_hash: +            return False +        return True + +    def _tokens_dbname(self): +        dbname = self.TOKENS_DB_PREFIX + \ +            str(int(time.time() / self.TOKENS_DB_EXPIRE)) +        return dbname diff --git a/common/src/leap/soledad/common/tests/test_server.py b/common/src/leap/soledad/common/tests/test_server.py index d75275d6..e1129a9f 100644 --- a/common/src/leap/soledad/common/tests/test_server.py +++ b/common/src/leap/soledad/common/tests/test_server.py @@ -24,6 +24,7 @@ import time  import binascii  from pkg_resources import resource_filename  from uuid import uuid4 +from hashlib import sha512  from urlparse import urljoin  from twisted.internet import defer @@ -46,6 +47,36 @@ from leap.soledad.server import LockResource  from leap.soledad.server import load_configuration  from leap.soledad.server import CONFIG_DEFAULTS  from leap.soledad.server.auth import URLToAuthorization +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):  | 
