Browse Source

reorganising, exceptions and comments on new auth mecanism

Guénaël Muller 7 years ago
parent
commit
ccc7fb3aff

+ 4 - 4
tracim/__init__.py View File

@@ -8,11 +8,11 @@ from hapic.ext.pyramid import PyramidContext
8 8
 
9 9
 from tracim.extensions import hapic
10 10
 from tracim.config import CFG
11
-from tracim.lib.utils.auth import basic_auth_check_credentials
12 11
 from tracim.lib.utils.request import TracimRequest
13
-from tracim.lib.utils.auth import AcceptAllAuthorizationPolicy
14
-from tracim.lib.utils.auth import BASIC_AUTH_WEBUI_REALM
15
-from tracim.lib.utils.auth import TRACIM_DEFAULT_PERM
12
+from tracim.lib.utils.authentification import basic_auth_check_credentials
13
+from tracim.lib.utils.authentification import BASIC_AUTH_WEBUI_REALM
14
+from tracim.lib.utils.authorization import AcceptAllAuthorizationPolicy
15
+from tracim.lib.utils.authorization import TRACIM_DEFAULT_PERM
16 16
 from tracim.views.example_api.example_api_controller import ExampleApiController
17 17
 from tracim.views.default.default_controller import DefaultController
18 18
 

+ 4 - 0
tracim/exceptions.py View File

@@ -71,3 +71,7 @@ class WorkspaceNotFound(NotFound):
71 71
 
72 72
 class InsufficientUserWorkspaceRole(TracimException):
73 73
     pass
74
+
75
+
76
+class ImmutableAttribute(TracimException):
77
+    pass

+ 0 - 146
tracim/lib/utils/auth.py View File

@@ -1,146 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-import typing
3
-
4
-from pyramid.interfaces import IAuthorizationPolicy
5
-from zope.interface import implementer
6
-
7
-try:
8
-    from json.decoder import JSONDecodeError
9
-except ImportError:  # python3.4
10
-    JSONDecodeError = ValueError
11
-from sqlalchemy.orm.exc import NoResultFound
12
-
13
-from pyramid.request import Request
14
-
15
-from tracim.models.auth import User
16
-from tracim.models.data import Workspace
17
-from tracim.lib.core.user import UserApi
18
-from tracim.lib.core.workspace import WorkspaceApi
19
-
20
-from tracim.exceptions import NotAuthentificated
21
-from tracim.exceptions import WorkspaceNotFound
22
-from tracim.exceptions import InsufficientUserWorkspaceRole
23
-BASIC_AUTH_WEBUI_REALM = "tracim"
24
-TRACIM_DEFAULT_PERM = 'tracim'
25
-
26
-
27
-def get_safe_user(
28
-        request: Request,
29
-) -> User:
30
-    """
31
-    Get current pyramid authenticated user from request
32
-    :param request: pyramid request
33
-    :return: current authenticated user
34
-    """
35
-    app_config = request.registry.settings['CFG']
36
-    uapi = UserApi(None, session=request.dbsession, config=app_config)
37
-    user = None
38
-    try:
39
-        login = request.authenticated_userid
40
-        if not login:
41
-            raise NotAuthentificated('not authenticated user_id,'
42
-                                     'Failed Authentification ?')
43
-        user = uapi.get_one_by_email(login)
44
-    except NoResultFound:
45
-        raise NotAuthentificated('User not found')
46
-    return user
47
-
48
-
49
-def get_workspace(user: User, request: Request) -> typing.Optional[Workspace]:
50
-    """
51
-    Get current workspace from request
52
-    :param user: User who want to check the workspace
53
-    :param request: pyramid request
54
-    :return:
55
-    """
56
-    workspace_id = ''
57
-    try:
58
-        if 'workspace_id' not in request.json_body:
59
-            return None
60
-        workspace_id = request.json_body['workspace_id']
61
-        wapi = WorkspaceApi(current_user=user, session=request.dbsession)
62
-        workspace = wapi.get_one(workspace_id)
63
-    except JSONDecodeError:
64
-        raise WorkspaceNotFound('Bad json body')
65
-    except NoResultFound:
66
-        raise WorkspaceNotFound(
67
-            'Workspace {} does not exist '
68
-            'or is not visible for this user'.format(workspace_id)
69
-        )
70
-    return workspace
71
-
72
-###
73
-# BASIC AUTH
74
-###
75
-
76
-
77
-def basic_auth_check_credentials(
78
-        login: str,
79
-        cleartext_password: str,
80
-        request: 'TracimRequest'
81
-) -> typing.Optional[list]:
82
-    """
83
-    Check credential for pyramid basic_auth
84
-    :param login: login of user
85
-    :param cleartext_password: user password in cleartext
86
-    :param request: Pyramid request
87
-    :return: None if auth failed, list of permissions if auth succeed
88
-    """
89
-
90
-    # Do not accept invalid user
91
-    user = _get_basic_auth_unsafe_user(request)
92
-    if not user \
93
-            or user.email != login \
94
-            or not user.validate_password(cleartext_password):
95
-        return None
96
-    return []
97
-
98
-
99
-def _get_basic_auth_unsafe_user(
100
-    request: Request,
101
-) -> typing.Optional[User]:
102
-    """
103
-    :param request: pyramid request
104
-    :return: User or None
105
-    """
106
-    app_config = request.registry.settings['CFG']
107
-    uapi = UserApi(None, session=request.dbsession, config=app_config)
108
-    try:
109
-        login = request.unauthenticated_userid
110
-        if not login:
111
-            return None
112
-        user = uapi.get_one_by_email(login)
113
-    except NoResultFound:
114
-        return None
115
-    return user
116
-
117
-####
118
-
119
-
120
-def require_workspace_role(minimal_required_role):
121
-    def decorator(func):
122
-
123
-        def wrapper(self, request: 'TracimRequest'):
124
-            user = request.current_user
125
-            workspace = request.current_workspace
126
-            if workspace.get_user_role(user) >= minimal_required_role:
127
-                return func(self, request)
128
-            raise InsufficientUserWorkspaceRole()
129
-
130
-        return wrapper
131
-    return decorator
132
-
133
-###
134
-
135
-
136
-@implementer(IAuthorizationPolicy)
137
-class AcceptAllAuthorizationPolicy(object):
138
-    """
139
-    Simple AuthorizationPolicy to avoid trouble with pyramid.
140
-    Acceot any request.
141
-    """
142
-    def permits(self, context, principals, permision):
143
-        return True
144
-
145
-    def principals_allowed_by_permission(self, context, permission):
146
-        raise NotImplementedError()

+ 55 - 0
tracim/lib/utils/authentification.py View File

@@ -0,0 +1,55 @@
1
+import typing
2
+
3
+from pyramid.request import Request
4
+from sqlalchemy.orm.exc import NoResultFound
5
+
6
+from tracim import TracimRequest
7
+from tracim.lib.core.user import UserApi
8
+from tracim.models import User
9
+
10
+BASIC_AUTH_WEBUI_REALM = "tracim"
11
+
12
+
13
+###
14
+# Pyramid HTTP Basic Auth
15
+###
16
+
17
+def basic_auth_check_credentials(
18
+        login: str,
19
+        cleartext_password: str,
20
+        request: TracimRequest
21
+) -> typing.Optional[list]:
22
+    """
23
+    Check credential for pyramid basic_auth
24
+    :param login: login of user
25
+    :param cleartext_password: user password in cleartext
26
+    :param request: Pyramid request
27
+    :return: None if auth failed, list of permissions if auth succeed
28
+    """
29
+
30
+    # Do not accept invalid user
31
+    user = _get_basic_auth_unsafe_user(request)
32
+    if not user \
33
+            or user.email != login \
34
+            or not user.validate_password(cleartext_password):
35
+        return None
36
+    return []
37
+
38
+
39
+def _get_basic_auth_unsafe_user(
40
+    request: Request,
41
+) -> typing.Optional[User]:
42
+    """
43
+    :param request: pyramid request
44
+    :return: User or None
45
+    """
46
+    app_config = request.registry.settings['CFG']
47
+    uapi = UserApi(None, session=request.dbsession, config=app_config)
48
+    try:
49
+        login = request.unauthenticated_userid
50
+        if not login:
51
+            return None
52
+        user = uapi.get_one_by_email(login)
53
+    except NoResultFound:
54
+        return None
55
+    return user

+ 62 - 0
tracim/lib/utils/authorization.py View File

@@ -0,0 +1,62 @@
1
+# -*- coding: utf-8 -*-
2
+from typing import TYPE_CHECKING
3
+
4
+from pyramid.interfaces import IAuthorizationPolicy
5
+from zope.interface import implementer
6
+try:
7
+    from json.decoder import JSONDecodeError
8
+except ImportError:  # python3.4
9
+    JSONDecodeError = ValueError
10
+
11
+from tracim.exceptions import InsufficientUserWorkspaceRole
12
+if TYPE_CHECKING:
13
+    from tracim import TracimRequest
14
+###
15
+# Pyramid default permission/authorization mecanism
16
+
17
+# INFO - G.M - 12-04-2018 - Setiing a Default permission on view is
18
+#  needed to activate AuthentificationPolicy and
19
+# AuthorizationPolicy on pyramid request
20
+TRACIM_DEFAULT_PERM = 'tracim'
21
+
22
+
23
+@implementer(IAuthorizationPolicy)
24
+class AcceptAllAuthorizationPolicy(object):
25
+    """
26
+    Empty AuthorizationPolicy : Allow all request. As Pyramid need
27
+    a Authorization policy when we use AuthentificationPolicy, this
28
+    class permit use to disable pyramid authorization mecanism with
29
+    working a AuthentificationPolicy.
30
+    """
31
+    def permits(self, context, principals, permision):
32
+        return True
33
+
34
+    def principals_allowed_by_permission(self, context, permission):
35
+        raise NotImplementedError()
36
+
37
+###
38
+# Authorization decorators for views
39
+
40
+# INFO - G.M - 12-04-2018
41
+# Instead of relying on pyramid authorization mecanism
42
+# We prefer to use decorators
43
+
44
+
45
+def require_workspace_role(minimal_required_role):
46
+    """
47
+    Decorator for view to restrict access of tracim request if role
48
+    is not high enough
49
+    :param minimal_required_role:
50
+    :return:
51
+    """
52
+    def decorator(func):
53
+
54
+        def wrapper(self, request: 'TracimRequest'):
55
+            user = request.current_user
56
+            workspace = request.current_workspace
57
+            if workspace.get_user_role(user) >= minimal_required_role:
58
+                return func(self, request)
59
+            raise InsufficientUserWorkspaceRole()
60
+
61
+        return wrapper
62
+    return decorator

+ 83 - 7
tracim/lib/utils/request.py View File

@@ -1,15 +1,22 @@
1
-from contextlib import contextmanager
2
-
3
-from pyramid.decorator import reify
1
+import typing
4 2
 from pyramid.request import Request
3
+from sqlalchemy.orm.exc import NoResultFound
4
+
5
+from tracim.exceptions import NotAuthentificated
6
+from tracim.exceptions import WorkspaceNotFound
7
+from tracim.exceptions import ImmutableAttribute
8
+from tracim.lib.core.user import UserApi
9
+from tracim.lib.core.workspace import WorkspaceApi
10
+from tracim.lib.utils.authorization import JSONDecodeError
5 11
 
6 12
 from tracim.models import User
7 13
 from tracim.models.data import Workspace
8
-from tracim.lib.utils.auth import get_safe_user
9
-from tracim.lib.utils.auth import get_workspace
10 14
 
11 15
 
12 16
 class TracimRequest(Request):
17
+    """
18
+    Request with tracim specific params/methods
19
+    """
13 20
     def __init__(
14 21
             self,
15 22
             environ,
@@ -30,13 +37,27 @@ class TracimRequest(Request):
30 37
 
31 38
     @property
32 39
     def current_workspace(self) -> Workspace:
40
+        """
41
+        Get current workspace of the request according to authentification and
42
+        request headers (to retrieve workspace). Setted by default value the
43
+        first time if not configured.
44
+        :return: Workspace of the request
45
+        """
33 46
         if self._current_workspace is None:
34 47
             self.current_workspace = get_workspace(self.current_user, self)
35 48
         return self._current_workspace
36 49
 
37 50
     @current_workspace.setter
38 51
     def current_workspace(self, workspace: Workspace) -> None:
39
-        assert self._current_workspace is None
52
+        """
53
+        Setting current_workspace
54
+        :param workspace:
55
+        :return:
56
+        """
57
+        if self._current_workspace is not None:
58
+            raise ImmutableAttribute(
59
+                "Can't modify already setted current_workspace"
60
+            )
40 61
         self._current_workspace = workspace
41 62
 
42 63
     @property
@@ -47,5 +68,60 @@ class TracimRequest(Request):
47 68
 
48 69
     @current_user.setter
49 70
     def current_user(self, user: User) -> None:
50
-        assert self._current_user is None
71
+        if self._current_user is not None:
72
+            raise ImmutableAttribute(
73
+                "Can't modify already setted current_user"
74
+            )
51 75
         self._current_user = user
76
+
77
+
78
+###
79
+# Utils for TracimRequest
80
+###
81
+
82
+def get_safe_user(
83
+        request: TracimRequest,
84
+) -> User:
85
+    """
86
+    Get current pyramid authenticated user from request
87
+    :param request: pyramid request
88
+    :return: current authenticated user
89
+    """
90
+    app_config = request.registry.settings['CFG']
91
+    uapi = UserApi(None, session=request.dbsession, config=app_config)
92
+    try:
93
+        login = request.authenticated_userid
94
+        if not login:
95
+            raise NotAuthentificated('not authenticated user_id,'
96
+                                     'Failed Authentification ?')
97
+        user = uapi.get_one_by_email(login)
98
+    except NoResultFound:
99
+        raise NotAuthentificated('User not found')
100
+    return user
101
+
102
+
103
+def get_workspace(
104
+        user: User,
105
+        request: TracimRequest
106
+) -> Workspace:
107
+    """
108
+    Get current workspace from request
109
+    :param user: User who want to check the workspace
110
+    :param request: pyramid request
111
+    :return: current workspace
112
+    """
113
+    workspace_id = ''
114
+    try:
115
+        if 'workspace_id' not in request.json_body:
116
+            raise WorkspaceNotFound('No workspace_id param in json body')
117
+        workspace_id = request.json_body['workspace_id']
118
+        wapi = WorkspaceApi(current_user=user, session=request.dbsession)
119
+        workspace = wapi.get_one(workspace_id)
120
+    except JSONDecodeError:
121
+        raise WorkspaceNotFound('Bad json body')
122
+    except NoResultFound:
123
+        raise WorkspaceNotFound(
124
+            'Workspace {} does not exist '
125
+            'or is not visible for this user'.format(workspace_id)
126
+        )
127
+    return workspace

+ 6 - 7
tracim/views/default/default_controller.py View File

@@ -1,6 +1,7 @@
1 1
 # coding=utf-8
2 2
 from pyramid.request import Request
3 3
 
4
+from tracim import TracimRequest
4 5
 from tracim.models.data import UserRoleInWorkspace
5 6
 from tracim.views.controllers import Controller
6 7
 from pyramid.config import Configurator
@@ -10,7 +11,7 @@ from pyramid.httpexceptions import HTTPUnauthorized
10 11
 from pyramid.httpexceptions import HTTPForbidden
11 12
 from pyramid.security import forget, authenticated_userid
12 13
 
13
-from tracim.lib.utils.auth import require_workspace_role
14
+from tracim.lib.utils.authorization import require_workspace_role
14 15
 
15 16
 
16 17
 class DefaultController(Controller):
@@ -31,12 +32,10 @@ class DefaultController(Controller):
31 32
 
32 33
     # TODO - G.M - 10-04-2018 - [cleanup][tempExample] - Drop this method
33 34
     @require_workspace_role(UserRoleInWorkspace.READER)
34
-    def test_config(self, request: Request):
35
-        try:
36
-            app_config = request.registry.settings['CFG']
37
-            project = app_config.WEBSITE_TITLE
38
-        except Exception as e:
39
-            return Response(e, content_type='text/plain', status=500)
35
+    def test_config(self, request: TracimRequest):
36
+        app_config = request.registry.settings['CFG']
37
+        project = app_config.WEBSITE_TITLE
38
+        request.current_user = "lapin"
40 39
         return {'project': project}
41 40
 
42 41
     # TODO - G.M - 10-04-2018 - [cleanup][tempExample] - Drop this method