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
 from tracim.config import CFG
11
 from tracim.config import CFG
12
 from tracim.lib.utils.auth import check_credentials
12
 from tracim.lib.utils.auth import check_credentials
13
 from tracim.lib.utils.auth import Root
13
 from tracim.lib.utils.auth import Root
14
+from tracim.lib.utils.auth import BASIC_AUTH_WEBUI_REALM
14
 from tracim.views.example_api.example_api_controller import ExampleApiController
15
 from tracim.views.example_api.example_api_controller import ExampleApiController
15
 from tracim.views.default.default_controller import DefaultController
16
 from tracim.views.default.default_controller import DefaultController
16
 
17
 
24
     settings['CFG'] = app_config
25
     settings['CFG'] = app_config
25
     configurator = Configurator(settings=settings, autocommit=True)
26
     configurator = Configurator(settings=settings, autocommit=True)
26
     # Add BasicAuthPolicy + ACL AuthorizationPolicy
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
     configurator.set_authorization_policy(ACLAuthorizationPolicy())
32
     configurator.set_authorization_policy(ACLAuthorizationPolicy())
29
     configurator.set_authentication_policy(authn_policy)
33
     configurator.set_authentication_policy(authn_policy)
30
     configurator.set_root_factory(lambda request: Root())
34
     configurator.set_root_factory(lambda request: Root())

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

1
+# -*- coding: utf-8 -*-
1
 import typing
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
 from pyramid.security import ALL_PERMISSIONS
8
 from pyramid.security import ALL_PERMISSIONS
3
 from pyramid.security import Allow
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
 from tracim.models.auth import Group
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
 from tracim.lib.core.workspace import WorkspaceApi
17
 from tracim.lib.core.workspace import WorkspaceApi
18
+from tracim.lib.core.userworkspace import RoleApi
8
 
19
 
9
 # INFO - G.M - 06-04-2018 - Auth for pyramid
20
 # INFO - G.M - 06-04-2018 - Auth for pyramid
10
 # based on this tutorial : https://docs.pylonsproject.org/projects/pyramid-cookbook/en/latest/auth/basic.html  # nopep8
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
     app_config = request.registry.settings['CFG']
41
     app_config = request.registry.settings['CFG']
16
     uapi = UserApi(None, session=request.dbsession, config=app_config)
42
     uapi = UserApi(None, session=request.dbsession, config=app_config)
43
+    user = None
17
     try:
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
         pass
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
     return permissions
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
 from pyramid.httpexceptions import HTTPForbidden
7
 from pyramid.httpexceptions import HTTPForbidden
8
 from pyramid.security import forget
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
 class DefaultController(Controller):
19
 class DefaultController(Controller):
12
 
20
 
36
         return {'project': project}
44
         return {'project': project}
37
 
45
 
38
     @classmethod
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
     def test_admin_page(cls, request):
56
     def test_admin_page(cls, request):
40
         try:
57
         try:
41
             app_config = request.registry.settings['CFG']
58
             app_config = request.registry.settings['CFG']
62
             return Response(e, content_type='text/plain', status=500)
79
             return Response(e, content_type='text/plain', status=500)
63
         return {'project': project}
80
         return {'project': project}
64
 
81
 
65
-
66
     def bind(self, configurator: Configurator):
82
     def bind(self, configurator: Configurator):
67
         configurator.add_static_view('static', 'static', cache_max_age=3600)
83
         configurator.add_static_view('static', 'static', cache_max_age=3600)
68
         configurator.add_view(
84
         configurator.add_view(
78
             renderer='tracim:templates/mytemplate.jinja2',
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
         configurator.add_route('test_admin', '/test_admin')
104
         configurator.add_route('test_admin', '/test_admin')
82
         configurator.add_view(
105
         configurator.add_view(
83
             self.test_admin_page,
106
             self.test_admin_page,
84
             route_name='test_admin',
107
             route_name='test_admin',
85
             renderer='tracim:templates/mytemplate.jinja2',
108
             renderer='tracim:templates/mytemplate.jinja2',
86
-            permission='admin',
109
+            permission=ADMIN_PERM,
87
         )
110
         )
88
         configurator.add_route('test_manager', '/test_manager')
111
         configurator.add_route('test_manager', '/test_manager')
89
         configurator.add_view(
112
         configurator.add_view(
90
             self.test_user_page,
113
             self.test_user_page,
91
             route_name='test_manager',
114
             route_name='test_manager',
92
             renderer='tracim:templates/mytemplate.jinja2',
115
             renderer='tracim:templates/mytemplate.jinja2',
93
-            permission='manager',
116
+            permission=MANAGE_GLOBAL_PERM,
94
         )
117
         )
95
         configurator.add_route('test_user', '/test_user')
118
         configurator.add_route('test_user', '/test_user')
96
         configurator.add_view(
119
         configurator.add_view(
97
             self.test_user_page,
120
             self.test_user_page,
98
             route_name='test_user',
121
             route_name='test_user',
99
             renderer='tracim:templates/mytemplate.jinja2',
122
             renderer='tracim:templates/mytemplate.jinja2',
100
-            permission='user',
123
+            permission=USER_PERM,
101
         )
124
         )
102
         configurator.add_forbidden_view(self.forbidden_view)
125
         configurator.add_forbidden_view(self.forbidden_view)