diff options
| author | drebs <drebs@leap.se> | 2016-12-18 14:21:54 -0200 | 
|---|---|---|
| committer | Kali Kaneko <kali@leap.se> | 2017-02-09 17:41:34 +0100 | 
| commit | e73d36621052a69aae327200c063ac1689bcf9e0 (patch) | |
| tree | c53a1d4ab94827147ee0f599e2d344621936cf63 | |
| parent | a39af0e003ba95c9b7ab554aa4a4c5ce316a43c7 (diff) | |
[feat] reuse the url mapper instead of creating it for every request
| -rw-r--r-- | common/src/leap/soledad/common/__init__.py | 2 | ||||
| -rw-r--r-- | server/src/leap/soledad/server/auth.py | 82 | ||||
| -rw-r--r-- | 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")  | 
