Browse Source

add candidate user in request, require_same_user_or_profile decorator and use it to have easier auth policy check for user data

Guénaël Muller 6 years ago
parent
commit
4041960a77

+ 0 - 10
tracim/lib/core/user.py View File

@@ -106,16 +106,6 @@ class UserApi(object):
106 106
         except (WrongUserPassword, NoResultFound, UserNotExist):
107 107
             raise AuthenticationFailed()
108 108
 
109
-    def can_see_private_info_of_user(self, user: User):
110
-        """
111
-        Return boolean wheter current api user has right
112
-        to see private information of a user.
113
-        :param user:
114
-        :return:
115
-        """
116
-        return self._user and (
117
-                self._user.user_id == user.user_id or
118
-                self._user.profile.id >= Group.TIM_ADMIN)
119 109
     # Actions
120 110
 
121 111
     def update(

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

@@ -44,6 +44,26 @@ class AcceptAllAuthorizationPolicy(object):
44 44
 # We prefer to use decorators
45 45
 
46 46
 
47
+def require_same_user_or_profile(group):
48
+    """
49
+    Decorator for view to restrict access of tracim request if candidate user
50
+    is distinct from authenticated user and not with high enough profile.
51
+    :param group: value from Group Object
52
+    like Group.TIM_USER or Group.TIM_MANAGER
53
+    :return:
54
+    """
55
+    def decorator(func):
56
+        def wrapper(self, context, request: 'TracimRequest'):
57
+            auth_user = request.current_user
58
+            candidate_user = request.candidate_user
59
+            if auth_user.user_id == candidate_user.user_id or \
60
+                    auth_user.profile.id >= group:
61
+                return func(self, context, request)
62
+            raise InsufficientUserProfile()
63
+        return wrapper
64
+    return decorator
65
+
66
+
47 67
 def require_profile(group):
48 68
     """
49 69
     Decorator for view to restrict access of tracim request if profile is

+ 59 - 4
tracim/lib/utils/request.py View File

@@ -1,8 +1,11 @@
1
-import typing
1
+# -*- coding: utf-8 -*-
2
+"""
3
+TracimRequest and related functions
4
+"""
2 5
 from pyramid.request import Request
3 6
 from sqlalchemy.orm.exc import NoResultFound
4 7
 
5
-from tracim.exceptions import NotAuthentificated
8
+from tracim.exceptions import NotAuthentificated, UserNotExist
6 9
 from tracim.exceptions import WorkspaceNotFound
7 10
 from tracim.exceptions import ImmutableAttribute
8 11
 from tracim.lib.core.user import UserApi
@@ -32,8 +35,13 @@ class TracimRequest(Request):
32 35
             decode_param_names,
33 36
             **kw
34 37
         )
38
+        # Current workspace, found by request headers or content
35 39
         self._current_workspace = None  # type: Workspace
40
+        # Authenticated user
36 41
         self._current_user = None  # type: User
42
+        # User found from request headers, content, distinct from authenticated
43
+        # user
44
+        self._user_candidate = None  # type: User
37 45
 
38 46
     @property
39 47
     def current_workspace(self) -> Workspace:
@@ -62,8 +70,11 @@ class TracimRequest(Request):
62 70
 
63 71
     @property
64 72
     def current_user(self) -> User:
73
+        """
74
+        Get user from authentication mecanism.
75
+        """
65 76
         if self._current_user is None:
66
-            self.current_user = get_safe_user(self)
77
+            self.current_user = get_auth_safe_user(self)
67 78
         return self._current_user
68 79
 
69 80
     @current_user.setter
@@ -74,11 +85,55 @@ class TracimRequest(Request):
74 85
             )
75 86
         self._current_user = user
76 87
 
88
+    # TODO - G.M - 24-05-2018 - Find a better naming for this ?
89
+    @property
90
+    def candidate_user(self) -> User:
91
+        """
92
+        Get user from headers/body request. This user is not
93
+        the one found by authentication mecanism. This user
94
+        can help user to know about who one page is about in
95
+        a similar way as current_workspace.
96
+        """
97
+        if self._user_candidate is None:
98
+            self.candidate_user = get_candidate_user(self)
99
+        return self._user_candidate
100
+
101
+    @candidate_user.setter
102
+    def candidate_user(self, user: User) -> None:
103
+        if self._user_candidate is not None:
104
+            raise ImmutableAttribute(
105
+                "Can't modify already setted candidate_user"
106
+            )
107
+        self._user_candidate = user
77 108
 ###
78 109
 # Utils for TracimRequest
79 110
 ###
80 111
 
81
-def get_safe_user(
112
+
113
+def get_candidate_user(
114
+        request: TracimRequest
115
+) -> User:
116
+    """
117
+    Get candidate user
118
+    :param request: pyramid request
119
+    :return: user found from header/body
120
+    """
121
+    app_config = request.registry.settings['CFG']
122
+    uapi = UserApi(None, session=request.dbsession, config=app_config)
123
+
124
+    try:
125
+        login = None
126
+        if 'user_id' in request.matchdict:
127
+            login = request.matchdict['user_id']
128
+        if not login:
129
+            raise UserNotExist('no user_id found, incorrect request ?')
130
+        user = uapi.get_one(login)
131
+    except NoResultFound:
132
+        raise NotAuthentificated('User not found')
133
+    return user
134
+
135
+
136
+def get_auth_safe_user(
82 137
         request: TracimRequest,
83 138
 ) -> User:
84 139
     """

+ 5 - 13
tracim/views/core_api/user_controller.py View File

@@ -1,6 +1,8 @@
1 1
 from pyramid.config import Configurator
2 2
 from sqlalchemy.orm.exc import NoResultFound
3 3
 
4
+from tracim.lib.utils.authorization import require_same_user_or_profile
5
+from tracim.models import Group
4 6
 from tracim.models.context_models import WorkspaceInContext
5 7
 
6 8
 try:  # Python 3.5+
@@ -21,27 +23,17 @@ from tracim.views.core_api.schemas import UserIdPathSchema, \
21 23
 class UserController(Controller):
22 24
 
23 25
     @hapic.with_api_doc()
24
-    @hapic.input_path(UserIdPathSchema())
25 26
     @hapic.handle_exception(NotAuthentificated, HTTPStatus.UNAUTHORIZED)
26 27
     @hapic.handle_exception(InsufficientUserProfile, HTTPStatus.FORBIDDEN)
27 28
     @hapic.handle_exception(UserNotExist, HTTPStatus.NOT_FOUND)
29
+    @require_same_user_or_profile(Group.TIM_ADMIN)
30
+    @hapic.input_path(UserIdPathSchema())
28 31
     @hapic.output_body(WorkspaceDigestSchema(many=True),)
29 32
     def user_workspace(self, context, request: TracimRequest, hapic_data=None):
30 33
         """
31 34
         Get list of user workspaces
32 35
         """
33 36
         app_config = request.registry.settings['CFG']
34
-
35
-        uid = hapic_data.path['user_id']
36
-        uapi = UserApi(
37
-            request.current_user,
38
-            session=request.dbsession,
39
-            config=app_config,
40
-        )
41
-        user = uapi.get_one(uid)
42
-        if not uapi.can_see_private_info_of_user(user):
43
-            raise InsufficientUserProfile()
44
-
45 37
         wapi = WorkspaceApi(
46 38
             current_user=request.current_user,  # User
47 39
             session=request.dbsession,
@@ -49,7 +41,7 @@ class UserController(Controller):
49 41
         )
50 42
         return [
51 43
             WorkspaceInContext(workspace, request.dbsession, app_config)
52
-            for workspace in wapi.get_all_for_user(user)
44
+            for workspace in wapi.get_all_for_user(request.candidate_user)
53 45
         ]
54 46
 
55 47
     def bind(self, configurator: Configurator) -> None: