Bläddra i källkod

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 år sedan
förälder
incheckning
4041960a77

+ 0 - 10
tracim/lib/core/user.py Visa fil

106
         except (WrongUserPassword, NoResultFound, UserNotExist):
106
         except (WrongUserPassword, NoResultFound, UserNotExist):
107
             raise AuthenticationFailed()
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
     # Actions
109
     # Actions
120
 
110
 
121
     def update(
111
     def update(

+ 20 - 0
tracim/lib/utils/authorization.py Visa fil

44
 # We prefer to use decorators
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
 def require_profile(group):
67
 def require_profile(group):
48
     """
68
     """
49
     Decorator for view to restrict access of tracim request if profile is
69
     Decorator for view to restrict access of tracim request if profile is

+ 59 - 4
tracim/lib/utils/request.py Visa fil

1
-import typing
1
+# -*- coding: utf-8 -*-
2
+"""
3
+TracimRequest and related functions
4
+"""
2
 from pyramid.request import Request
5
 from pyramid.request import Request
3
 from sqlalchemy.orm.exc import NoResultFound
6
 from sqlalchemy.orm.exc import NoResultFound
4
 
7
 
5
-from tracim.exceptions import NotAuthentificated
8
+from tracim.exceptions import NotAuthentificated, UserNotExist
6
 from tracim.exceptions import WorkspaceNotFound
9
 from tracim.exceptions import WorkspaceNotFound
7
 from tracim.exceptions import ImmutableAttribute
10
 from tracim.exceptions import ImmutableAttribute
8
 from tracim.lib.core.user import UserApi
11
 from tracim.lib.core.user import UserApi
32
             decode_param_names,
35
             decode_param_names,
33
             **kw
36
             **kw
34
         )
37
         )
38
+        # Current workspace, found by request headers or content
35
         self._current_workspace = None  # type: Workspace
39
         self._current_workspace = None  # type: Workspace
40
+        # Authenticated user
36
         self._current_user = None  # type: User
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
     @property
46
     @property
39
     def current_workspace(self) -> Workspace:
47
     def current_workspace(self) -> Workspace:
62
 
70
 
63
     @property
71
     @property
64
     def current_user(self) -> User:
72
     def current_user(self) -> User:
73
+        """
74
+        Get user from authentication mecanism.
75
+        """
65
         if self._current_user is None:
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
         return self._current_user
78
         return self._current_user
68
 
79
 
69
     @current_user.setter
80
     @current_user.setter
74
             )
85
             )
75
         self._current_user = user
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
 # Utils for TracimRequest
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
         request: TracimRequest,
137
         request: TracimRequest,
83
 ) -> User:
138
 ) -> User:
84
     """
139
     """

+ 5 - 13
tracim/views/core_api/user_controller.py Visa fil

1
 from pyramid.config import Configurator
1
 from pyramid.config import Configurator
2
 from sqlalchemy.orm.exc import NoResultFound
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
 from tracim.models.context_models import WorkspaceInContext
6
 from tracim.models.context_models import WorkspaceInContext
5
 
7
 
6
 try:  # Python 3.5+
8
 try:  # Python 3.5+
21
 class UserController(Controller):
23
 class UserController(Controller):
22
 
24
 
23
     @hapic.with_api_doc()
25
     @hapic.with_api_doc()
24
-    @hapic.input_path(UserIdPathSchema())
25
     @hapic.handle_exception(NotAuthentificated, HTTPStatus.UNAUTHORIZED)
26
     @hapic.handle_exception(NotAuthentificated, HTTPStatus.UNAUTHORIZED)
26
     @hapic.handle_exception(InsufficientUserProfile, HTTPStatus.FORBIDDEN)
27
     @hapic.handle_exception(InsufficientUserProfile, HTTPStatus.FORBIDDEN)
27
     @hapic.handle_exception(UserNotExist, HTTPStatus.NOT_FOUND)
28
     @hapic.handle_exception(UserNotExist, HTTPStatus.NOT_FOUND)
29
+    @require_same_user_or_profile(Group.TIM_ADMIN)
30
+    @hapic.input_path(UserIdPathSchema())
28
     @hapic.output_body(WorkspaceDigestSchema(many=True),)
31
     @hapic.output_body(WorkspaceDigestSchema(many=True),)
29
     def user_workspace(self, context, request: TracimRequest, hapic_data=None):
32
     def user_workspace(self, context, request: TracimRequest, hapic_data=None):
30
         """
33
         """
31
         Get list of user workspaces
34
         Get list of user workspaces
32
         """
35
         """
33
         app_config = request.registry.settings['CFG']
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
         wapi = WorkspaceApi(
37
         wapi = WorkspaceApi(
46
             current_user=request.current_user,  # User
38
             current_user=request.current_user,  # User
47
             session=request.dbsession,
39
             session=request.dbsession,
49
         )
41
         )
50
         return [
42
         return [
51
             WorkspaceInContext(workspace, request.dbsession, app_config)
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
     def bind(self, configurator: Configurator) -> None:
47
     def bind(self, configurator: Configurator) -> None: