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 --- server/src/leap/soledad/server/auth.py | 50 ++++++++++++++++++++----------- server/src/leap/soledad/server/session.py | 4 +-- 2 files changed, 35 insertions(+), 19 deletions(-) (limited to 'server') diff --git a/server/src/leap/soledad/server/auth.py b/server/src/leap/soledad/server/auth.py index 13245cfe..bcef2e7c 100644 --- a/server/src/leap/soledad/server/auth.py +++ b/server/src/leap/soledad/server/auth.py @@ -29,6 +29,7 @@ from twisted.cred.credentials import IUsernamePassword from twisted.cred.credentials import UsernamePassword from twisted.cred.portal import IRealm from twisted.cred.portal import Portal +from twisted.internet import defer from twisted.web.iweb import ICredentialFactory from twisted.web.resource import IResource @@ -57,31 +58,45 @@ class TokenChecker(object): TOKENS_TYPE_DEF = "Token" TOKENS_USER_ID_KEY = "user_id" - def __init__(self): - config = get_config() - self.couch_url = config['couch_url'] + def __init__(self, server=None): + if server is None: + config = get_config() + couch_url = config['couch_url'] + server = couch_server(couch_url) + self._server = server + self._dbs = {} def _tokens_dbname(self): dbname = self.TOKENS_DB_PREFIX + \ str(int(time.time() / self.TOKENS_DB_EXPIRE)) return dbname + def _get_db(self, dbname): + if dbname not in self._dbs: + self._dbs[dbname] = self._server[dbname] + return self._dbs[dbname] + + def _tokens_db(self): + # the tokens db rotates every 30 days, and the current db name is + # "tokens_NNN", where NNN is the number of seconds since epoch + # divide dby 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 = self._get_db(dbname) + return db + def requestAvatarId(self, credentials): uuid = credentials.username token = credentials.password - 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 - # divide dby 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. + db = self._tokens_db() token = db.get(sha512(token).hexdigest()) if token is None: - return False + return defer.fail(error.UnauthorizedLogin()) + # 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. @@ -89,8 +104,9 @@ class TokenChecker(object): 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 + return defer.fail(error.UnauthorizedLogin()) + + return defer.succeed(uuid) @implementer(ICredentialFactory) @@ -103,7 +119,7 @@ class TokenCredentialFactory(object): def decode(self, response, request): try: - creds = response.decode('base64') + creds = binascii.a2b_base64(response + b'===') except binascii.Error: raise error.LoginFailed('Invalid credentials') @@ -114,5 +130,5 @@ class TokenCredentialFactory(object): raise error.LoginFailed('Invalid credentials') -portal = Portal(SoledadRealm(), [TokenChecker()]) +get_portal = lambda: Portal(SoledadRealm(), [TokenChecker()]) credentialFactory = TokenCredentialFactory() diff --git a/server/src/leap/soledad/server/session.py b/server/src/leap/soledad/server/session.py index 4ed2721c..f1626115 100644 --- a/server/src/leap/soledad/server/session.py +++ b/server/src/leap/soledad/server/session.py @@ -26,7 +26,7 @@ from twisted.web.guard import HTTPAuthSessionWrapper from twisted.web.resource import ErrorPage from twisted.web.resource import IResource -from leap.soledad.server.auth import portal +from leap.soledad.server.auth import get_portal from leap.soledad.server.auth import credentialFactory from leap.soledad.server.url_mapper import URLMapper @@ -50,7 +50,7 @@ class SoledadSession(HTTPAuthSessionWrapper): def __init__(self): self._mapper = URLMapper() - self._portal = portal + self._portal = get_portal() self._credentialFactory = credentialFactory def _matchPath(self, request): -- cgit v1.2.3