From e73d36621052a69aae327200c063ac1689bcf9e0 Mon Sep 17 00:00:00 2001 From: drebs Date: Sun, 18 Dec 2016 14:21:54 -0200 Subject: [feat] reuse the url mapper instead of creating it for every request --- common/src/leap/soledad/common/__init__.py | 2 - server/src/leap/soledad/server/auth.py | 82 +++------- testing/tests/server/test_server.py | 251 +++++++++++++---------------- 3 files changed, 136 insertions(+), 199 deletions(-) diff --git a/common/src/leap/soledad/common/__init__.py b/common/src/leap/soledad/common/__init__.py index 1ba6ab89..4948ad20 100644 --- a/common/src/leap/soledad/common/__init__.py +++ b/common/src/leap/soledad/common/__init__.py @@ -30,8 +30,6 @@ Soledad routines common to client and server. # SHARED_DB_NAME = 'shared' -SHARED_DB_LOCK_DOC_ID_PREFIX = 'lock-' -USER_DB_PREFIX = 'user-' # diff --git a/server/src/leap/soledad/server/auth.py b/server/src/leap/soledad/server/auth.py index f3d9c8a8..c026a282 100644 --- a/server/src/leap/soledad/server/auth.py +++ b/server/src/leap/soledad/server/auth.py @@ -26,67 +26,31 @@ from routes.mapper import Mapper from leap.soledad.common.log import getLogger from leap.soledad.common.l2db import DBNAME_CONSTRAINTS, errors as u1db_errors from leap.soledad.common import SHARED_DB_NAME -from leap.soledad.common import USER_DB_PREFIX logger = getLogger(__name__) -class URLToAuthorization(object): +class URLMapper(object): """ - Verify if actions can be performed by a user. + Maps the URLs users can access. """ - HTTP_METHOD_GET = 'GET' - HTTP_METHOD_PUT = 'PUT' - HTTP_METHOD_DELETE = 'DELETE' - HTTP_METHOD_POST = 'POST' - - def __init__(self, uuid): - """ - Initialize the mapper. - - The C{uuid} is used to create the rules that will either allow or - disallow the user to perform specific actions. - - @param uuid: The user uuid. - @type uuid: str - @param user_db_prefix: The string prefix of users' databases. - @type user_db_prefix: str - """ + def __init__(self): self._map = Mapper(controller_scan=None) - self._user_db_name = "%s%s" % (USER_DB_PREFIX, uuid) - self._uuid = uuid - self._register_auth_info() - - def is_authorized(self, environ): - """ - Return whether an HTTP request that produced the CGI C{environ} - corresponds to an authorized action. - - @param environ: Dictionary containing CGI variables. - @type environ: dict - - @return: Whether the action is authorized or not. - @rtype: bool - """ - return self._map.match(environ=environ) is not None + self._connect_urls() + self._map.create_regs() - def _register(self, pattern, http_methods): - """ - Register a C{pattern} in the mapper as valid for C{http_methods}. + def match(self, environ): + return self._map.match(environ=environ) - @param pattern: The URL pattern that corresponds to the user action. - @type pattern: str - @param http_methods: A list of authorized HTTP methods. - @type http_methods: list of str - """ + def _connect(self, pattern, http_methods): self._map.connect( None, pattern, http_methods=http_methods, conditions=dict(method=http_methods), requirements={'dbname': DBNAME_CONSTRAINTS}) - def _register_auth_info(self): + def _connect_urls(self): """ Register the authorization info in the mapper using C{SHARED_DB_NAME} as the user's database name. @@ -106,21 +70,15 @@ class URLToAuthorization(object): /user-db/sync-from/{source} | GET, PUT, POST """ # auth info for global resource - self._register('/', [self.HTTP_METHOD_GET]) + self._connect('/', ['GET']) # auth info for shared-db database resource - self._register('/%s' % SHARED_DB_NAME, [self.HTTP_METHOD_GET]) + self._connect('/%s' % SHARED_DB_NAME, ['GET']) # auth info for shared-db doc resource - self._register( - '/%s/doc/{id:.*}' % SHARED_DB_NAME, - [self.HTTP_METHOD_GET, self.HTTP_METHOD_PUT, - self.HTTP_METHOD_DELETE]) + self._connect('/%s/doc/{id:.*}' % SHARED_DB_NAME, + ['GET', 'PUT', 'DELETE']) # auth info for user-db sync resource - self._register( - '/%s/sync-from/{source_replica_uid}' % self._user_db_name, - [self.HTTP_METHOD_GET, self.HTTP_METHOD_PUT, - self.HTTP_METHOD_POST]) - # generate the regular expressions - self._map.create_regs() + self._connect('/user-{uuid}/sync-from/{source_replica_uid}', + ['GET', 'PUT', 'POST']) class SoledadAuthMiddleware(object): @@ -176,6 +134,7 @@ class SoledadAuthMiddleware(object): @type prefix: str """ self._app = app + self._mapper = URLMapper() def _error(self, start_response, status, description, message=None): """ @@ -310,14 +269,17 @@ class SoledadAuthMiddleware(object): @param environ: Dictionary containing CGI variables. @type environ: dict - @param uuid: The user's uuid. + @param uuid: The user's uuid from the Authorization header. @type uuid: str - @return: Whether the user is authorize to perform the requested action + @return: Whether the user is authorized to perform the requested action over the requested db. @rtype: bool """ - return URLToAuthorization(uuid).is_authorized(environ) + match = self._mapper.match(environ) + if not match: + return False + return uuid == match.get('uuid') @abstractmethod def _get_auth_error_string(self): diff --git a/testing/tests/server/test_server.py b/testing/tests/server/test_server.py index cae2e75c..09242736 100644 --- a/testing/tests/server/test_server.py +++ b/testing/tests/server/test_server.py @@ -45,7 +45,7 @@ from leap.soledad.client import _crypto from leap.soledad.client import Soledad 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 URLMapper from leap.soledad.server.auth import SoledadTokenAuthMiddleware @@ -116,175 +116,152 @@ class ServerAuthorizationTestCase(BaseSoledadTest): /user-db/sync-from/{source} | GET, PUT, POST """ uuid = uuid4().hex - authmap = URLToAuthorization(uuid,) - dbname = authmap._user_db_name + urlmap = URLMapper() + dbname = 'user-%s' % uuid + # test global auth - self.assertTrue( - authmap.is_authorized(self._make_environ('/', 'GET'))) + match = urlmap.match(self._make_environ('/', 'GET')) + # test shared-db database resource auth - self.assertTrue( - authmap.is_authorized( - self._make_environ('/shared', 'GET'))) - self.assertFalse( - authmap.is_authorized( + match = urlmap.match( + self._make_environ('/shared', 'GET')) + self.assertIsNotNone(match) + + self.assertIsNone( + urlmap.match( self._make_environ('/shared', 'PUT'))) - self.assertFalse( - authmap.is_authorized( + + self.assertIsNone( + urlmap.match( self._make_environ('/shared', 'DELETE'))) - self.assertFalse( - authmap.is_authorized( + + self.assertIsNone( + urlmap.match( self._make_environ('/shared', 'POST'))) + # test shared-db docs resource auth - self.assertFalse( - authmap.is_authorized( + self.assertIsNone( + urlmap.match( self._make_environ('/shared/docs', 'GET'))) - self.assertFalse( - authmap.is_authorized( + + self.assertIsNone( + urlmap.match( self._make_environ('/shared/docs', 'PUT'))) - self.assertFalse( - authmap.is_authorized( + + self.assertIsNone( + urlmap.match( self._make_environ('/shared/docs', 'DELETE'))) - self.assertFalse( - authmap.is_authorized( + + self.assertIsNone( + urlmap.match( self._make_environ('/shared/docs', 'POST'))) + # test shared-db doc resource auth - self.assertTrue( - authmap.is_authorized( - self._make_environ('/shared/doc/x', 'GET'))) - self.assertTrue( - authmap.is_authorized( - self._make_environ('/shared/doc/x', 'PUT'))) - self.assertTrue( - authmap.is_authorized( - self._make_environ('/shared/doc/x', 'DELETE'))) - self.assertFalse( - authmap.is_authorized( + match = urlmap.match( + self._make_environ('/shared/doc/x', 'GET')) + self.assertIsNotNone(match) + self.assertEqual('x', match.get('id')) + + match = urlmap.match( + self._make_environ('/shared/doc/x', 'PUT')) + self.assertIsNotNone(match) + self.assertEqual('x', match.get('id')) + + match = urlmap.match( + self._make_environ('/shared/doc/x', 'DELETE')) + self.assertEqual('x', match.get('id')) + + self.assertIsNone( + urlmap.match( self._make_environ('/shared/doc/x', 'POST'))) + # test shared-db sync resource auth - self.assertFalse( - authmap.is_authorized( + self.assertIsNone( + urlmap.match( self._make_environ('/shared/sync-from/x', 'GET'))) - self.assertFalse( - authmap.is_authorized( + + self.assertIsNone( + urlmap.match( self._make_environ('/shared/sync-from/x', 'PUT'))) - self.assertFalse( - authmap.is_authorized( + + self.assertIsNone( + urlmap.match( self._make_environ('/shared/sync-from/x', 'DELETE'))) - self.assertFalse( - authmap.is_authorized( + + self.assertIsNone( + urlmap.match( self._make_environ('/shared/sync-from/x', 'POST'))) + # test user-db database resource auth - self.assertFalse( - authmap.is_authorized( + self.assertIsNone( + urlmap.match( self._make_environ('/%s' % dbname, 'GET'))) - self.assertFalse( - authmap.is_authorized( + + self.assertIsNone( + urlmap.match( self._make_environ('/%s' % dbname, 'PUT'))) - self.assertFalse( - authmap.is_authorized( + + self.assertIsNone( + urlmap.match( self._make_environ('/%s' % dbname, 'DELETE'))) - self.assertFalse( - authmap.is_authorized( + + self.assertIsNone( + urlmap.match( self._make_environ('/%s' % dbname, 'POST'))) + # test user-db docs resource auth - self.assertFalse( - authmap.is_authorized( + self.assertIsNone( + urlmap.match( self._make_environ('/%s/docs' % dbname, 'GET'))) - self.assertFalse( - authmap.is_authorized( + + self.assertIsNone( + urlmap.match( self._make_environ('/%s/docs' % dbname, 'PUT'))) - self.assertFalse( - authmap.is_authorized( + + self.assertIsNone( + urlmap.match( self._make_environ('/%s/docs' % dbname, 'DELETE'))) - self.assertFalse( - authmap.is_authorized( + + self.assertIsNone( + urlmap.match( self._make_environ('/%s/docs' % dbname, 'POST'))) + # test user-db doc resource auth - self.assertFalse( - authmap.is_authorized( + self.assertIsNone( + urlmap.match( self._make_environ('/%s/doc/x' % dbname, 'GET'))) - self.assertFalse( - authmap.is_authorized( + + self.assertIsNone( + urlmap.match( self._make_environ('/%s/doc/x' % dbname, 'PUT'))) - self.assertFalse( - authmap.is_authorized( + + self.assertIsNone( + urlmap.match( self._make_environ('/%s/doc/x' % dbname, 'DELETE'))) - self.assertFalse( - authmap.is_authorized( + + self.assertIsNone( + urlmap.match( self._make_environ('/%s/doc/x' % dbname, 'POST'))) + # test user-db sync resource auth - self.assertTrue( - authmap.is_authorized( - self._make_environ('/%s/sync-from/x' % dbname, 'GET'))) - self.assertTrue( - authmap.is_authorized( - self._make_environ('/%s/sync-from/x' % dbname, 'PUT'))) - self.assertFalse( - authmap.is_authorized( - self._make_environ('/%s/sync-from/x' % dbname, 'DELETE'))) - self.assertTrue( - authmap.is_authorized( - self._make_environ('/%s/sync-from/x' % dbname, 'POST'))) - - def test_verify_action_with_wrong_dbnames(self): - """ - Test if authorization fails for a wrong dbname. - """ - uuid = uuid4().hex - authmap = URLToAuthorization(uuid) - dbname = 'somedb' - # test wrong-db database resource auth - self.assertFalse( - authmap.is_authorized( - self._make_environ('/%s' % dbname, 'GET'))) - self.assertFalse( - authmap.is_authorized( - self._make_environ('/%s' % dbname, 'PUT'))) - self.assertFalse( - authmap.is_authorized( - self._make_environ('/%s' % dbname, 'DELETE'))) - self.assertFalse( - authmap.is_authorized( - self._make_environ('/%s' % dbname, 'POST'))) - # test wrong-db docs resource auth - self.assertFalse( - authmap.is_authorized( - self._make_environ('/%s/docs' % dbname, 'GET'))) - self.assertFalse( - authmap.is_authorized( - self._make_environ('/%s/docs' % dbname, 'PUT'))) - self.assertFalse( - authmap.is_authorized( - self._make_environ('/%s/docs' % dbname, 'DELETE'))) - self.assertFalse( - authmap.is_authorized( - self._make_environ('/%s/docs' % dbname, 'POST'))) - # test wrong-db doc resource auth - self.assertFalse( - authmap.is_authorized( - self._make_environ('/%s/doc/x' % dbname, 'GET'))) - self.assertFalse( - authmap.is_authorized( - self._make_environ('/%s/doc/x' % dbname, 'PUT'))) - self.assertFalse( - authmap.is_authorized( - self._make_environ('/%s/doc/x' % dbname, 'DELETE'))) - self.assertFalse( - authmap.is_authorized( - self._make_environ('/%s/doc/x' % dbname, 'POST'))) - # test wrong-db sync resource auth - self.assertFalse( - authmap.is_authorized( - self._make_environ('/%s/sync-from/x' % dbname, 'GET'))) - self.assertFalse( - authmap.is_authorized( - self._make_environ('/%s/sync-from/x' % dbname, 'PUT'))) - self.assertFalse( - authmap.is_authorized( - self._make_environ('/%s/sync-from/x' % dbname, 'DELETE'))) - self.assertFalse( - authmap.is_authorized( - self._make_environ('/%s/sync-from/x' % dbname, 'POST'))) + match = urlmap.match( + self._make_environ('/%s/sync-from/x' % dbname, 'GET')) + self.assertEqual(uuid, match.get('uuid')) + self.assertEqual('x', match.get('source_replica_uid')) + + match = urlmap.match( + self._make_environ('/%s/sync-from/x' % dbname, 'PUT')) + self.assertEqual(uuid, match.get('uuid')) + self.assertEqual('x', match.get('source_replica_uid')) + + match = urlmap.match( + self._make_environ('/%s/sync-from/x' % dbname, 'DELETE')) + self.assertIsNone(match) + + match = urlmap.match( + self._make_environ('/%s/sync-from/x' % dbname, 'POST')) + self.assertEqual(uuid, match.get('uuid')) + self.assertEqual('x', match.get('source_replica_uid')) @pytest.mark.usefixtures("method_tmpdir") -- cgit v1.2.3