Browse Source

refactor authorization/authen with workspace_specific role

Guénaël Muller 7 years ago
parent
commit
e9556c457c
3 changed files with 130 additions and 25 deletions
  1. 5 1
      tracim/__init__.py
  2. 98 20
      tracim/lib/utils/auth.py
  3. 27 4
      tracim/views/default/default_controller.py

+ 5 - 1
tracim/__init__.py View File

@@ -11,6 +11,7 @@ from tracim.extensions import hapic
11 11
 from tracim.config import CFG
12 12
 from tracim.lib.utils.auth import check_credentials
13 13
 from tracim.lib.utils.auth import Root
14
+from tracim.lib.utils.auth import BASIC_AUTH_WEBUI_REALM
14 15
 from tracim.views.example_api.example_api_controller import ExampleApiController
15 16
 from tracim.views.default.default_controller import DefaultController
16 17
 
@@ -24,7 +25,10 @@ def main(global_config, **settings):
24 25
     settings['CFG'] = app_config
25 26
     configurator = Configurator(settings=settings, autocommit=True)
26 27
     # Add BasicAuthPolicy + ACL AuthorizationPolicy
27
-    authn_policy = BasicAuthAuthenticationPolicy(check_credentials)
28
+    authn_policy = BasicAuthAuthenticationPolicy(
29
+        check_credentials,
30
+        realm=BASIC_AUTH_WEBUI_REALM,
31
+    )
28 32
     configurator.set_authorization_policy(ACLAuthorizationPolicy())
29 33
     configurator.set_authentication_policy(authn_policy)
30 34
     configurator.set_root_factory(lambda request: Root())

+ 98 - 20
tracim/lib/utils/auth.py View File

@@ -1,37 +1,115 @@
1
+# -*- coding: utf-8 -*-
1 2
 import typing
3
+
4
+from json.decoder import JSONDecodeError
5
+from sqlalchemy.orm.exc import NoResultFound
6
+
7
+from pyramid.request import Request
2 8
 from pyramid.security import ALL_PERMISSIONS
3 9
 from pyramid.security import Allow
4
-from pyramid.security import Authenticated
5
-from tracim.lib.core.user import UserApi
10
+from pyramid.security import unauthenticated_userid
11
+
6 12
 from tracim.models.auth import Group
13
+from tracim.models.auth import User
14
+from tracim.models.data import Workspace
15
+from tracim.models.data import UserRoleInWorkspace
16
+from tracim.lib.core.user import UserApi
7 17
 from tracim.lib.core.workspace import WorkspaceApi
18
+from tracim.lib.core.userworkspace import RoleApi
8 19
 
9 20
 # INFO - G.M - 06-04-2018 - Auth for pyramid
10 21
 # based on this tutorial : https://docs.pylonsproject.org/projects/pyramid-cookbook/en/latest/auth/basic.html  # nopep8
22
+BASIC_AUTH_WEBUI_REALM = "tracim"
11 23
 
24
+# Global Permissions
25
+ADMIN_PERM = 'admin'
26
+MANAGE_GLOBAL_PERM = 'manage_global'
27
+USER_PERM = 'user'
28
+# Workspace-specific permission
29
+READ_PERM = 'read'
30
+CONTRIBUTE_PERM = 'contribute'
31
+MANAGE_CONTENT_PERM = 'manage_content'
32
+MANAGE_WORKSPACE_PERM = 'manage_workspace'
12 33
 
13
-def check_credentials(username, password, request) -> typing.Optional[dict]:
14
-    permissions = None
34
+
35
+def get_user(request: Request) -> typing.Optional[User]:
36
+    """
37
+    Get current pyramid user from request
38
+    :param request: pyramid request
39
+    :return:
40
+    """
15 41
     app_config = request.registry.settings['CFG']
16 42
     uapi = UserApi(None, session=request.dbsession, config=app_config)
43
+    user = None
17 44
     try:
18
-        user = uapi.get_one_by_email(username)
19
-        if user.validate_password(password):
20
-            permissions = []
21
-            for group in user.groups:
22
-                permissions.append(group.group_name)
23
-            # TODO - G.M - 06-04-2018 - Add workspace specific permission ?
24
-    # TODO - G.M - 06-04-2018 - Better catch for exception of bad password, bad
25
-    # user
26
-    except:
45
+        login = unauthenticated_userid(request)
46
+        user = uapi.get_one_by_email(login)
47
+    except NoResultFound:
27 48
         pass
49
+    return user
50
+
51
+
52
+def get_workspace(request: Request) -> typing.Optional[Workspace]:
53
+    """
54
+    Get current workspace from request
55
+    :param request: pyramid request
56
+    :return:
57
+    """
58
+    workspace = None
59
+    try:
60
+        if 'workspace_id' not in request.json_body:
61
+            return None
62
+        workspace_id = request.json_body['workspace_id']
63
+        wapi = WorkspaceApi(current_user=None, session=request.dbsession)
64
+        workspace = wapi.get_one(workspace_id)
65
+    except JSONDecodeError:
66
+        pass
67
+    except NoResultFound:
68
+        pass
69
+    return workspace
70
+
71
+
72
+def check_credentials(
73
+        login: str,
74
+        cleartext_password: str,
75
+        request: Request
76
+) -> typing.Optional[list]:
77
+    """
78
+    Check credential for pyramid basic_auth, checks also for
79
+    global and Workspace related permissions.
80
+    :param login: login of user
81
+    :param cleartext_password: user password in cleartext
82
+    :param request: Pyramid request
83
+    :return: None if auth failed, list of permissions if auth succeed
84
+    """
85
+    user = get_user(request)
86
+
87
+    # Do not accept invalid user
88
+    if not user \
89
+            or user.email != login \
90
+            or not user.validate_password(cleartext_password):
91
+        return None
92
+    permissions = []
93
+
94
+    # Global groups
95
+    for group in user.groups:
96
+        permissions.append(group.group_id)
97
+
98
+    # Current workspace related group
99
+    workspace = get_workspace(request)
100
+    if workspace:
101
+        roleapi = RoleApi(current_user=user, session=request.dbsession)
102
+        role = roleapi.get_one(
103
+            user_id=user.user_id,
104
+            workspace_id=workspace.workspace_id,
105
+        )
106
+        permissions.append(role)
107
+
28 108
     return permissions
29 109
 
30 110
 
31
-class Root:
32
-    # root
33
-    __acl__ = (
34
-        (Allow, Group.TIM_ADMIN_GROUPNAME, ALL_PERMISSIONS),
35
-        (Allow, Group.TIM_MANAGER_GROUPNAME, 'manager'),
36
-        (Allow, Group.TIM_USER_GROUPNAME, 'user'),
37
-    )
111
+class Root(object):
112
+    """
113
+    Root of all Pyramid requests, used to store global acl
114
+    """
115
+    __acl__ = ()

+ 27 - 4
tracim/views/default/default_controller.py View File

@@ -7,6 +7,14 @@ from pyramid.httpexceptions import HTTPUnauthorized
7 7
 from pyramid.httpexceptions import HTTPForbidden
8 8
 from pyramid.security import forget
9 9
 
10
+from tracim.lib.utils.auth import MANAGE_CONTENT_PERM
11
+from tracim.lib.utils.auth import MANAGE_WORKSPACE_PERM
12
+from tracim.lib.utils.auth import MANAGE_GLOBAL_PERM
13
+from tracim.lib.utils.auth import READ_PERM
14
+from tracim.lib.utils.auth import CONTRIBUTE_PERM
15
+from tracim.lib.utils.auth import ADMIN_PERM
16
+from tracim.lib.utils.auth import USER_PERM
17
+
10 18
 
11 19
 class DefaultController(Controller):
12 20
 
@@ -36,6 +44,15 @@ class DefaultController(Controller):
36 44
         return {'project': project}
37 45
 
38 46
     @classmethod
47
+    def test_contributor_page(cls, request):
48
+        try:
49
+            app_config = request.registry.settings['CFG']
50
+            project = 'contributor'
51
+        except Exception as e:
52
+            return Response(e, content_type='text/plain', status=500)
53
+        return {'project': project}
54
+
55
+    @classmethod
39 56
     def test_admin_page(cls, request):
40 57
         try:
41 58
             app_config = request.registry.settings['CFG']
@@ -62,7 +79,6 @@ class DefaultController(Controller):
62 79
             return Response(e, content_type='text/plain', status=500)
63 80
         return {'project': project}
64 81
 
65
-
66 82
     def bind(self, configurator: Configurator):
67 83
         configurator.add_static_view('static', 'static', cache_max_age=3600)
68 84
         configurator.add_view(
@@ -78,25 +94,32 @@ class DefaultController(Controller):
78 94
             renderer='tracim:templates/mytemplate.jinja2',
79 95
         )
80 96
 
97
+        configurator.add_route('test_contributor', '/test_contributor')
98
+        configurator.add_view(
99
+            self.test_contributor_page,
100
+            route_name='test_contributor',
101
+            renderer='tracim:templates/mytemplate.jinja2',
102
+            permission=CONTRIBUTE_PERM,
103
+        )
81 104
         configurator.add_route('test_admin', '/test_admin')
82 105
         configurator.add_view(
83 106
             self.test_admin_page,
84 107
             route_name='test_admin',
85 108
             renderer='tracim:templates/mytemplate.jinja2',
86
-            permission='admin',
109
+            permission=ADMIN_PERM,
87 110
         )
88 111
         configurator.add_route('test_manager', '/test_manager')
89 112
         configurator.add_view(
90 113
             self.test_user_page,
91 114
             route_name='test_manager',
92 115
             renderer='tracim:templates/mytemplate.jinja2',
93
-            permission='manager',
116
+            permission=MANAGE_GLOBAL_PERM,
94 117
         )
95 118
         configurator.add_route('test_user', '/test_user')
96 119
         configurator.add_view(
97 120
             self.test_user_page,
98 121
             route_name='test_user',
99 122
             renderer='tracim:templates/mytemplate.jinja2',
100
-            permission='user',
123
+            permission=USER_PERM,
101 124
         )
102 125
         configurator.add_forbidden_view(self.forbidden_view)