浏览代码

fix access for deleting comments + other access troube

Guénaël Muller 6 年前
父节点
当前提交
adfa1785e8

+ 16 - 1
tracim/fixtures/content.py 查看文件

23
         bob = self._session.query(models.User) \
23
         bob = self._session.query(models.User) \
24
             .filter(models.User.email == 'bob@fsf.local') \
24
             .filter(models.User.email == 'bob@fsf.local') \
25
             .one()
25
             .one()
26
+        john_the_reader = self._session.query(models.User) \
27
+            .filter(models.User.email == 'john-the-reader@reader.local') \
28
+            .one()
29
+
26
         admin_workspace_api = WorkspaceApi(
30
         admin_workspace_api = WorkspaceApi(
27
             current_user=admin,
31
             current_user=admin,
28
             session=self._session,
32
             session=self._session,
43
             session=self._session,
47
             session=self._session,
44
             config=self._config
48
             config=self._config
45
         )
49
         )
50
+        reader_content_api = ContentApi(
51
+            current_user=john_the_reader,
52
+            session=self._session,
53
+            config=self._config
54
+        )
46
         role_api = RoleApi(
55
         role_api = RoleApi(
47
             current_user=admin,
56
             current_user=admin,
48
             session=self._session,
57
             session=self._session,
73
             role_level=UserRoleInWorkspace.CONTENT_MANAGER,
82
             role_level=UserRoleInWorkspace.CONTENT_MANAGER,
74
             with_notif=False,
83
             with_notif=False,
75
         )
84
         )
85
+        role_api.create_one(
86
+            user=john_the_reader,
87
+            workspace=recipe_workspace,
88
+            role_level=UserRoleInWorkspace.READER,
89
+            with_notif=False,
90
+        )
76
         # Folders
91
         # Folders
77
 
92
 
78
         tool_workspace = content_api.create(
93
         tool_workspace = content_api.create(
273
             content='<p>What about Apple Pie ? There are Awesome !</p>',
288
             content='<p>What about Apple Pie ? There are Awesome !</p>',
274
             do_save=True,
289
             do_save=True,
275
         )
290
         )
276
-        content_api.create_comment(
291
+        reader_content_api.create_comment(
277
             parent=best_cake_thread,
292
             parent=best_cake_thread,
278
             content='<p>You are right, but Kouign-amann are clearly better.</p>',
293
             content='<p>You are right, but Kouign-amann are clearly better.</p>',
279
             do_save=True,
294
             do_save=True,

+ 9 - 0
tracim/fixtures/users_and_groups.py 查看文件

61
         bob.password = 'foobarbaz'
61
         bob.password = 'foobarbaz'
62
         self._session.add(bob)
62
         self._session.add(bob)
63
         g2.users.append(bob)
63
         g2.users.append(bob)
64
+
65
+        g2 = self._session.query(models.Group).\
66
+            filter(models.Group.group_name == 'users').one()
67
+        lawrence = models.User()
68
+        lawrence.display_name = 'John Reader'
69
+        lawrence.email = 'john-the-reader@reader.local'
70
+        lawrence.password = 'read'
71
+        self._session.add(lawrence)
72
+        g2.users.append(lawrence)

+ 32 - 0
tracim/lib/utils/authorization.py 查看文件

146
             raise ContentTypeNotAllowed()
146
             raise ContentTypeNotAllowed()
147
         return wrapper
147
         return wrapper
148
     return decorator
148
     return decorator
149
+
150
+
151
+def require_comment_ownership_or_role(
152
+        minimal_required_role_for_owner: int,
153
+        minimal_required_role_for_anyone: int,
154
+) -> None:
155
+    """
156
+    Decorator for view to restrict access of tracim request if role is
157
+    not high enough and user is not owner of the current_content
158
+    :param minimal_required_role_for_owner_access: minimal role for owner
159
+    of current_content to access to this view
160
+    :param minimal_required_role_for_anyone: minimal role for anyone to
161
+    access to this view.
162
+    :return:
163
+    """
164
+    def decorator(func):
165
+        @functools.wraps(func)
166
+        def wrapper(self, context, request: 'TracimRequest'):
167
+            user = request.current_user
168
+            workspace = request.current_workspace
169
+            comment = request.current_comment
170
+            # INFO - G.M - 2018-06-178 - find minimal role required
171
+            if comment.owner.user_id == user.user_id:
172
+                minimal_required_role = minimal_required_role_for_owner
173
+            else:
174
+                minimal_required_role = minimal_required_role_for_anyone
175
+            # INFO - G.M - 2018-06-178 - normal role test
176
+            if workspace.get_user_role(user) >= minimal_required_role:
177
+                return func(self, context, request)
178
+            raise InsufficientUserWorkspaceRole()
179
+        return wrapper
180
+    return decorator

+ 62 - 0
tracim/lib/utils/request.py 查看文件

38
             decode_param_names,
38
             decode_param_names,
39
             **kw
39
             **kw
40
         )
40
         )
41
+        # Current comment, found in request path
42
+        self._current_comment = None  # type: Content
43
+
41
         # Current content, found in request path
44
         # Current content, found in request path
42
         self._current_content = None  # type: Content
45
         self._current_content = None  # type: Content
43
 
46
 
120
             )
123
             )
121
         self._current_content = content
124
         self._current_content = content
122
 
125
 
126
+    @property
127
+    def current_comment(self) -> User:
128
+        """
129
+        Get current comment from path
130
+        """
131
+        if self._current_comment is None:
132
+            self._current_comment = self._get_current_comment(
133
+                self.current_user,
134
+                self.current_workspace,
135
+                self.current_content,
136
+                self
137
+                )
138
+        return self._current_comment
139
+
140
+    @current_comment.setter
141
+    def current_comment(self, content: Content) -> None:
142
+        if self._current_comment is not None:
143
+            raise ImmutableAttribute(
144
+                "Can't modify already setted current_content"
145
+            )
146
+        self._current_comment = content
123
     # TODO - G.M - 24-05-2018 - Find a better naming for this ?
147
     # TODO - G.M - 24-05-2018 - Find a better naming for this ?
124
     @property
148
     @property
125
     def candidate_user(self) -> User:
149
     def candidate_user(self) -> User:
170
     ###
194
     ###
171
     # Utils for TracimRequest
195
     # Utils for TracimRequest
172
     ###
196
     ###
197
+    def _get_current_comment(
198
+            self,
199
+            user: User,
200
+            workspace: Workspace,
201
+            content: Content,
202
+            request: 'TracimRequest'
203
+    ):
204
+        """
205
+        Get current content from request
206
+        :param user: User who want to check the workspace
207
+        :param request: pyramid request
208
+        :return: current content
209
+        """
210
+        comment_id = ''
211
+        try:
212
+            if 'comment_id' in request.matchdict:
213
+                comment_id = int(request.matchdict['comment_id'])
214
+            if not comment_id:
215
+                raise ContentNotFoundInTracimRequest('No comment_id property found in request')  # nopep8
216
+            api = ContentApi(
217
+                current_user=user,
218
+                session=request.dbsession,
219
+                config=request.registry.settings['CFG']
220
+            )
221
+            comment = api.get_one(
222
+                comment_id,
223
+                content_type=ContentType.Comment,
224
+                workspace=workspace,
225
+                parent=content,
226
+            )
227
+        except JSONDecodeError:
228
+            raise ContentNotFound('Bad json body')
229
+        except NoResultFound:
230
+            raise ContentNotFound(
231
+                'Comment {} does not exist '
232
+                'or is not visible for this user'.format(comment_id)
233
+            )
234
+        return comment
173
 
235
 
174
     def _get_current_content(
236
     def _get_current_content(
175
             self,
237
             self,

+ 155 - 9
tracim/tests/functional/test_comments.py 查看文件

50
         assert comment['parent_id'] == 7
50
         assert comment['parent_id'] == 7
51
         assert comment['raw_content'] == '<p>You are right, but Kouign-amann are clearly better.</p>'  # nopep8
51
         assert comment['raw_content'] == '<p>You are right, but Kouign-amann are clearly better.</p>'  # nopep8
52
         assert comment['author']
52
         assert comment['author']
53
-        assert comment['author']['user_id'] == 1
53
+        assert comment['author']['user_id'] == 4
54
         # TODO - G.M - 2018-06-172 - [avatar] setup avatar url
54
         # TODO - G.M - 2018-06-172 - [avatar] setup avatar url
55
         assert comment['author']['avatar_url'] == None
55
         assert comment['author']['avatar_url'] == None
56
-        assert comment['author']['public_name'] == 'Global manager'
56
+        assert comment['author']['public_name'] == 'John Reader'
57
 
57
 
58
     def test_api__post_content_comment__ok_200__nominal_case(self) -> None:
58
     def test_api__post_content_comment__ok_200__nominal_case(self) -> None:
59
         """
59
         """
88
         assert len(res.json_body) == 4
88
         assert len(res.json_body) == 4
89
         assert comment == res.json_body[3]
89
         assert comment == res.json_body[3]
90
 
90
 
91
-    def test_api__delete_content_comment__ok_200__nominal_case(self) -> None:
91
+    def test_api__delete_content_comment__ok_200__workspace_manager_owner(self) -> None:
92
         """
92
         """
93
-        Get alls comments of a content
93
+        delete comment (user is workspace_manager and owner)
94
         """
94
         """
95
         self.testapp.authorization = (
95
         self.testapp.authorization = (
96
             'Basic',
96
             'Basic',
101
         )
101
         )
102
         res = self.testapp.get('/api/v2/workspaces/2/contents/7/comments', status=200)
102
         res = self.testapp.get('/api/v2/workspaces/2/contents/7/comments', status=200)
103
         assert len(res.json_body) == 3
103
         assert len(res.json_body) == 3
104
-        comment = res.json_body[2]
105
-        assert comment['content_id'] == 20
104
+        comment = res.json_body[0]
105
+        assert comment['content_id'] == 18
106
         assert comment['parent_id'] == 7
106
         assert comment['parent_id'] == 7
107
-        assert comment['raw_content'] == '<p>You are right, but Kouign-amann are clearly better.</p>'   # nopep8
107
+        assert comment['raw_content'] == '<p> What is for you the best cake ever ? </br> I personnally vote for Chocolate cupcake !</p>'   # nopep8
108
         assert comment['author']
108
         assert comment['author']
109
         assert comment['author']['user_id'] == 1
109
         assert comment['author']['user_id'] == 1
110
         # TODO - G.M - 2018-06-172 - [avatar] setup avatar url
110
         # TODO - G.M - 2018-06-172 - [avatar] setup avatar url
112
         assert comment['author']['public_name'] == 'Global manager'
112
         assert comment['author']['public_name'] == 'Global manager'
113
 
113
 
114
         res = self.testapp.delete(
114
         res = self.testapp.delete(
115
-            '/api/v2/workspaces/2/contents/7/comments/20',
115
+            '/api/v2/workspaces/2/contents/7/comments/18',
116
+            status=204
117
+        )
118
+        res = self.testapp.get('/api/v2/workspaces/2/contents/7/comments', status=200)
119
+        assert len(res.json_body) == 2
120
+        assert not [content for content in res.json_body if content['content_id'] == 18]  # nopep8
121
+
122
+    def test_api__delete_content_comment__ok_200__workspace_manager(self) -> None:
123
+        """
124
+        delete comment (user is workspace_manager)
125
+        """
126
+        self.testapp.authorization = (
127
+            'Basic',
128
+            (
129
+                'admin@admin.admin',
130
+                'admin@admin.admin'
131
+            )
132
+        )
133
+        res = self.testapp.get('/api/v2/workspaces/2/contents/7/comments', status=200)
134
+        assert len(res.json_body) == 3
135
+        comment = res.json_body[1]
136
+        assert comment['content_id'] == 19
137
+        assert comment['parent_id'] == 7
138
+        assert comment['raw_content'] == '<p>What about Apple Pie ? There are Awesome !</p>'   # nopep8
139
+        assert comment['author']
140
+        assert comment['author']['user_id'] == 3
141
+        # TODO - G.M - 2018-06-172 - [avatar] setup avatar url
142
+        assert comment['author']['avatar_url'] is None
143
+        assert comment['author']['public_name'] == 'Bob i.'
144
+
145
+        res = self.testapp.delete(
146
+            '/api/v2/workspaces/2/contents/7/comments/19',
116
             status=204
147
             status=204
117
         )
148
         )
118
         res = self.testapp.get('/api/v2/workspaces/2/contents/7/comments', status=200)
149
         res = self.testapp.get('/api/v2/workspaces/2/contents/7/comments', status=200)
119
         assert len(res.json_body) == 2
150
         assert len(res.json_body) == 2
120
-        assert not [content for content in res.json_body if content['content_id'] == 20]  # nopep8
151
+        assert not [content for content in res.json_body if content['content_id'] == 19]  # nopep8
152
+
153
+    def test_api__delete_content_comment__ok_200__content_manager_owner(self) -> None:
154
+        """
155
+        delete comment (user is content-manager and owner)
156
+        """
157
+        self.testapp.authorization = (
158
+            'Basic',
159
+            (
160
+                'admin@admin.admin',
161
+                'admin@admin.admin'
162
+            )
163
+        )
164
+        res = self.testapp.get('/api/v2/workspaces/2/contents/7/comments', status=200)
165
+        assert len(res.json_body) == 3
166
+        comment = res.json_body[1]
167
+        assert comment['content_id'] == 19
168
+        assert comment['parent_id'] == 7
169
+        assert comment['raw_content'] == '<p>What about Apple Pie ? There are Awesome !</p>'   # nopep8
170
+        assert comment['author']
171
+        assert comment['author']['user_id'] == 3
172
+        # TODO - G.M - 2018-06-172 - [avatar] setup avatar url
173
+        assert comment['author']['avatar_url'] is None
174
+        assert comment['author']['public_name'] == 'Bob i.'
175
+
176
+        res = self.testapp.delete(
177
+            '/api/v2/workspaces/2/contents/7/comments/19',
178
+            status=204
179
+        )
180
+        res = self.testapp.get('/api/v2/workspaces/2/contents/7/comments', status=200)
181
+        assert len(res.json_body) == 2
182
+        assert not [content for content in res.json_body if content['content_id'] == 19]  # nopep8
183
+
184
+    def test_api__delete_content_comment__err_403__content_manager(self) -> None:
185
+        """
186
+        delete comment (user is content-manager)
187
+        """
188
+        self.testapp.authorization = (
189
+            'Basic',
190
+            (
191
+                'bob@fsf.local',
192
+                'foobarbaz'
193
+            )
194
+        )
195
+        res = self.testapp.get('/api/v2/workspaces/2/contents/7/comments', status=200)
196
+        assert len(res.json_body) == 3
197
+        comment = res.json_body[2]
198
+        assert comment['content_id'] == 20
199
+        assert comment['parent_id'] == 7
200
+        assert comment['raw_content'] == '<p>You are right, but Kouign-amann are clearly better.</p>'   # nopep8
201
+        assert comment['author']
202
+        assert comment['author']['user_id'] == 4
203
+        # TODO - G.M - 2018-06-172 - [avatar] setup avatar url
204
+        assert comment['author']['avatar_url'] is None
205
+        assert comment['author']['public_name'] == 'John Reader'
206
+
207
+        res = self.testapp.delete(
208
+            '/api/v2/workspaces/2/contents/7/comments/20',
209
+            status=403
210
+        )
211
+
212
+    def test_api__delete_content_comment__err_403__reader_owner(self) -> None:
213
+        """
214
+        delete comment (user is reader and owner)
215
+        """
216
+        self.testapp.authorization = (
217
+            'Basic',
218
+            (
219
+                'bob@fsf.local',
220
+                'foobarbaz'
221
+            )
222
+        )
223
+        res = self.testapp.get('/api/v2/workspaces/2/contents/7/comments', status=200)
224
+        assert len(res.json_body) == 3
225
+        comment = res.json_body[2]
226
+        assert comment['content_id'] == 20
227
+        assert comment['parent_id'] == 7
228
+        assert comment['raw_content'] == '<p>You are right, but Kouign-amann are clearly better.</p>'   # nopep8
229
+        assert comment['author']
230
+        assert comment['author']['user_id'] == 4
231
+        # TODO - G.M - 2018-06-172 - [avatar] setup avatar url
232
+        assert comment['author']['avatar_url'] is None
233
+        assert comment['author']['public_name'] == 'John Reader'
234
+
235
+        res = self.testapp.delete(
236
+            '/api/v2/workspaces/2/contents/7/comments/20',
237
+            status=403
238
+        )
239
+
240
+    def test_api__delete_content_comment__err_403__reader(self) -> None:
241
+        """
242
+        delete comment (user is reader)
243
+        """
244
+        self.testapp.authorization = (
245
+            'Basic',
246
+            (
247
+                'bob@fsf.local',
248
+                'foobarbaz'
249
+            )
250
+        )
251
+        res = self.testapp.get('/api/v2/workspaces/2/contents/7/comments', status=200)
252
+        assert len(res.json_body) == 3
253
+        comment = res.json_body[2]
254
+        assert comment['content_id'] == 20
255
+        assert comment['parent_id'] == 7
256
+        assert comment['raw_content'] == '<p>You are right, but Kouign-amann are clearly better.</p>'   # nopep8
257
+        assert comment['author']
258
+        assert comment['author']['user_id'] == 4
259
+        # TODO - G.M - 2018-06-172 - [avatar] setup avatar url
260
+        assert comment['author']['avatar_url'] is None
261
+        assert comment['author']['public_name'] == 'John Reader'
262
+
263
+        res = self.testapp.delete(
264
+            '/api/v2/workspaces/2/contents/7/comments/20',
265
+            status=403
266
+        )

+ 14 - 5
tracim/views/contents_api/comment_controller.py 查看文件

1
 # coding=utf-8
1
 # coding=utf-8
2
 import transaction
2
 import transaction
3
 from pyramid.config import Configurator
3
 from pyramid.config import Configurator
4
+
4
 try:  # Python 3.5+
5
 try:  # Python 3.5+
5
     from http import HTTPStatus
6
     from http import HTTPStatus
6
 except ImportError:
7
 except ImportError:
10
 from tracim.extensions import hapic
11
 from tracim.extensions import hapic
11
 from tracim.lib.core.content import ContentApi
12
 from tracim.lib.core.content import ContentApi
12
 from tracim.lib.core.workspace import WorkspaceApi
13
 from tracim.lib.core.workspace import WorkspaceApi
14
+from tracim.lib.utils.authorization import require_workspace_role
15
+from tracim.lib.utils.authorization import require_comment_ownership_or_role
13
 from tracim.views.controllers import Controller
16
 from tracim.views.controllers import Controller
14
 from tracim.views.core_api.schemas import CommentSchema
17
 from tracim.views.core_api.schemas import CommentSchema
15
 from tracim.views.core_api.schemas import CommentsPathSchema
18
 from tracim.views.core_api.schemas import CommentsPathSchema
17
 from tracim.views.core_api.schemas import WorkspaceAndContentIdPathSchema
20
 from tracim.views.core_api.schemas import WorkspaceAndContentIdPathSchema
18
 from tracim.views.core_api.schemas import NoContentSchema
21
 from tracim.views.core_api.schemas import NoContentSchema
19
 from tracim.exceptions import WorkspaceNotFound
22
 from tracim.exceptions import WorkspaceNotFound
20
-from tracim.exceptions import InsufficientUserProfile
23
+from tracim.exceptions import InsufficientUserWorkspaceRole
21
 from tracim.exceptions import NotAuthenticated
24
 from tracim.exceptions import NotAuthenticated
22
 from tracim.exceptions import AuthenticationFailed
25
 from tracim.exceptions import AuthenticationFailed
23
 from tracim.models.contents import ContentTypeLegacy as ContentType
26
 from tracim.models.contents import ContentTypeLegacy as ContentType
24
 from tracim.models.revision_protection import new_revision
27
 from tracim.models.revision_protection import new_revision
28
+from tracim.models.data import UserRoleInWorkspace
25
 
29
 
26
 COMMENT_ENDPOINTS_TAG = 'Comments'
30
 COMMENT_ENDPOINTS_TAG = 'Comments'
27
 
31
 
28
 
32
 
29
 class CommentController(Controller):
33
 class CommentController(Controller):
30
-    pass
31
 
34
 
32
     @hapic.with_api_doc(tags=[COMMENT_ENDPOINTS_TAG])
35
     @hapic.with_api_doc(tags=[COMMENT_ENDPOINTS_TAG])
33
     @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
36
     @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
34
-    @hapic.handle_exception(InsufficientUserProfile, HTTPStatus.FORBIDDEN)
37
+    @hapic.handle_exception(InsufficientUserWorkspaceRole, HTTPStatus.FORBIDDEN)
35
     @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
38
     @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
36
     @hapic.handle_exception(AuthenticationFailed, HTTPStatus.BAD_REQUEST)
39
     @hapic.handle_exception(AuthenticationFailed, HTTPStatus.BAD_REQUEST)
40
+    @require_workspace_role(UserRoleInWorkspace.READER)
37
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
41
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
38
     @hapic.output_body(CommentSchema(many=True),)
42
     @hapic.output_body(CommentSchema(many=True),)
39
     def content_comments(self, context, request: TracimRequest, hapic_data=None):
43
     def content_comments(self, context, request: TracimRequest, hapic_data=None):
60
 
64
 
61
     @hapic.with_api_doc(tags=[COMMENT_ENDPOINTS_TAG])
65
     @hapic.with_api_doc(tags=[COMMENT_ENDPOINTS_TAG])
62
     @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
66
     @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
63
-    @hapic.handle_exception(InsufficientUserProfile, HTTPStatus.FORBIDDEN)
67
+    @hapic.handle_exception(InsufficientUserWorkspaceRole, HTTPStatus.FORBIDDEN)
64
     @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
68
     @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
65
     @hapic.handle_exception(AuthenticationFailed, HTTPStatus.BAD_REQUEST)
69
     @hapic.handle_exception(AuthenticationFailed, HTTPStatus.BAD_REQUEST)
70
+    @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
66
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
71
     @hapic.input_path(WorkspaceAndContentIdPathSchema())
67
     @hapic.input_body(SetCommentSchema())
72
     @hapic.input_body(SetCommentSchema())
68
     @hapic.output_body(CommentSchema(),)
73
     @hapic.output_body(CommentSchema(),)
91
 
96
 
92
     @hapic.with_api_doc(tags=[COMMENT_ENDPOINTS_TAG])
97
     @hapic.with_api_doc(tags=[COMMENT_ENDPOINTS_TAG])
93
     @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
98
     @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
94
-    @hapic.handle_exception(InsufficientUserProfile, HTTPStatus.FORBIDDEN)
99
+    @hapic.handle_exception(InsufficientUserWorkspaceRole, HTTPStatus.FORBIDDEN)
95
     @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
100
     @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
96
     @hapic.handle_exception(AuthenticationFailed, HTTPStatus.BAD_REQUEST)
101
     @hapic.handle_exception(AuthenticationFailed, HTTPStatus.BAD_REQUEST)
102
+    @require_comment_ownership_or_role(
103
+        minimal_required_role_for_anyone=UserRoleInWorkspace.WORKSPACE_MANAGER,
104
+        minimal_required_role_for_owner=UserRoleInWorkspace.CONTRIBUTOR,
105
+    )
97
     @hapic.input_path(CommentsPathSchema())
106
     @hapic.input_path(CommentsPathSchema())
98
     @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
107
     @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
99
     def delete_comment(self, context, request: TracimRequest, hapic_data=None):
108
     def delete_comment(self, context, request: TracimRequest, hapic_data=None):

+ 5 - 5
tracim/views/contents_api/html_document_controller.py 查看文件

22
 from tracim.lib.utils.authorization import require_content_types
22
 from tracim.lib.utils.authorization import require_content_types
23
 from tracim.lib.utils.authorization import require_workspace_role
23
 from tracim.lib.utils.authorization import require_workspace_role
24
 from tracim.exceptions import WorkspaceNotFound, ContentTypeNotAllowed
24
 from tracim.exceptions import WorkspaceNotFound, ContentTypeNotAllowed
25
-from tracim.exceptions import InsufficientUserProfile
25
+from tracim.exceptions import InsufficientUserWorkspaceRole
26
 from tracim.exceptions import NotAuthenticated
26
 from tracim.exceptions import NotAuthenticated
27
 from tracim.exceptions import AuthenticationFailed
27
 from tracim.exceptions import AuthenticationFailed
28
 from tracim.models.contents import ContentTypeLegacy as ContentType
28
 from tracim.models.contents import ContentTypeLegacy as ContentType
36
 
36
 
37
     @hapic.with_api_doc(tags=[HTML_DOCUMENT_ENDPOINTS_TAG])
37
     @hapic.with_api_doc(tags=[HTML_DOCUMENT_ENDPOINTS_TAG])
38
     @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
38
     @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
39
-    @hapic.handle_exception(InsufficientUserProfile, HTTPStatus.FORBIDDEN)
39
+    @hapic.handle_exception(InsufficientUserWorkspaceRole, HTTPStatus.FORBIDDEN)
40
     @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
40
     @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
41
     @hapic.handle_exception(AuthenticationFailed, HTTPStatus.BAD_REQUEST)
41
     @hapic.handle_exception(AuthenticationFailed, HTTPStatus.BAD_REQUEST)
42
     @hapic.handle_exception(ContentTypeNotAllowed, HTTPStatus.BAD_REQUEST)
42
     @hapic.handle_exception(ContentTypeNotAllowed, HTTPStatus.BAD_REQUEST)
62
 
62
 
63
     @hapic.with_api_doc(tags=[HTML_DOCUMENT_ENDPOINTS_TAG])
63
     @hapic.with_api_doc(tags=[HTML_DOCUMENT_ENDPOINTS_TAG])
64
     @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
64
     @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
65
-    @hapic.handle_exception(InsufficientUserProfile, HTTPStatus.FORBIDDEN)
65
+    @hapic.handle_exception(InsufficientUserWorkspaceRole, HTTPStatus.FORBIDDEN)
66
     @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
66
     @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
67
     @hapic.handle_exception(AuthenticationFailed, HTTPStatus.BAD_REQUEST)
67
     @hapic.handle_exception(AuthenticationFailed, HTTPStatus.BAD_REQUEST)
68
     @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
68
     @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
100
 
100
 
101
     @hapic.with_api_doc(tags=[HTML_DOCUMENT_ENDPOINTS_TAG])
101
     @hapic.with_api_doc(tags=[HTML_DOCUMENT_ENDPOINTS_TAG])
102
     @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
102
     @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
103
-    @hapic.handle_exception(InsufficientUserProfile, HTTPStatus.FORBIDDEN)
103
+    @hapic.handle_exception(InsufficientUserWorkspaceRole, HTTPStatus.FORBIDDEN)
104
     @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
104
     @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
105
     @hapic.handle_exception(AuthenticationFailed, HTTPStatus.BAD_REQUEST)
105
     @hapic.handle_exception(AuthenticationFailed, HTTPStatus.BAD_REQUEST)
106
     @require_workspace_role(UserRoleInWorkspace.READER)
106
     @require_workspace_role(UserRoleInWorkspace.READER)
129
 
129
 
130
     @hapic.with_api_doc(tags=[HTML_DOCUMENT_ENDPOINTS_TAG])
130
     @hapic.with_api_doc(tags=[HTML_DOCUMENT_ENDPOINTS_TAG])
131
     @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
131
     @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
132
-    @hapic.handle_exception(InsufficientUserProfile, HTTPStatus.FORBIDDEN)
132
+    @hapic.handle_exception(InsufficientUserWorkspaceRole, HTTPStatus.FORBIDDEN)
133
     @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
133
     @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
134
     @hapic.handle_exception(AuthenticationFailed, HTTPStatus.BAD_REQUEST)
134
     @hapic.handle_exception(AuthenticationFailed, HTTPStatus.BAD_REQUEST)
135
     @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
135
     @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)

+ 5 - 5
tracim/views/contents_api/threads_controller.py 查看文件

22
 from tracim.lib.utils.authorization import require_content_types
22
 from tracim.lib.utils.authorization import require_content_types
23
 from tracim.lib.utils.authorization import require_workspace_role
23
 from tracim.lib.utils.authorization import require_workspace_role
24
 from tracim.exceptions import WorkspaceNotFound, ContentTypeNotAllowed
24
 from tracim.exceptions import WorkspaceNotFound, ContentTypeNotAllowed
25
-from tracim.exceptions import InsufficientUserProfile
25
+from tracim.exceptions import InsufficientUserWorkspaceRole
26
 from tracim.exceptions import NotAuthenticated
26
 from tracim.exceptions import NotAuthenticated
27
 from tracim.exceptions import AuthenticationFailed
27
 from tracim.exceptions import AuthenticationFailed
28
 from tracim.models.contents import ContentTypeLegacy as ContentType
28
 from tracim.models.contents import ContentTypeLegacy as ContentType
36
 
36
 
37
     @hapic.with_api_doc(tags=[THREAD_ENDPOINTS_TAG])
37
     @hapic.with_api_doc(tags=[THREAD_ENDPOINTS_TAG])
38
     @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
38
     @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
39
-    @hapic.handle_exception(InsufficientUserProfile, HTTPStatus.FORBIDDEN)
39
+    @hapic.handle_exception(InsufficientUserWorkspaceRole, HTTPStatus.FORBIDDEN)
40
     @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
40
     @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
41
     @hapic.handle_exception(AuthenticationFailed, HTTPStatus.BAD_REQUEST)
41
     @hapic.handle_exception(AuthenticationFailed, HTTPStatus.BAD_REQUEST)
42
     @hapic.handle_exception(ContentTypeNotAllowed, HTTPStatus.BAD_REQUEST)
42
     @hapic.handle_exception(ContentTypeNotAllowed, HTTPStatus.BAD_REQUEST)
62
 
62
 
63
     @hapic.with_api_doc(tags=[THREAD_ENDPOINTS_TAG])
63
     @hapic.with_api_doc(tags=[THREAD_ENDPOINTS_TAG])
64
     @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
64
     @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
65
-    @hapic.handle_exception(InsufficientUserProfile, HTTPStatus.FORBIDDEN)
65
+    @hapic.handle_exception(InsufficientUserWorkspaceRole, HTTPStatus.FORBIDDEN)
66
     @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
66
     @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
67
     @hapic.handle_exception(AuthenticationFailed, HTTPStatus.BAD_REQUEST)
67
     @hapic.handle_exception(AuthenticationFailed, HTTPStatus.BAD_REQUEST)
68
     @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
68
     @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
100
 
100
 
101
     @hapic.with_api_doc(tags=[THREAD_ENDPOINTS_TAG])
101
     @hapic.with_api_doc(tags=[THREAD_ENDPOINTS_TAG])
102
     @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
102
     @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
103
-    @hapic.handle_exception(InsufficientUserProfile, HTTPStatus.FORBIDDEN)
103
+    @hapic.handle_exception(InsufficientUserWorkspaceRole, HTTPStatus.FORBIDDEN)
104
     @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
104
     @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
105
     @hapic.handle_exception(AuthenticationFailed, HTTPStatus.BAD_REQUEST)
105
     @hapic.handle_exception(AuthenticationFailed, HTTPStatus.BAD_REQUEST)
106
     @require_workspace_role(UserRoleInWorkspace.READER)
106
     @require_workspace_role(UserRoleInWorkspace.READER)
129
 
129
 
130
     @hapic.with_api_doc(tags=[THREAD_ENDPOINTS_TAG])
130
     @hapic.with_api_doc(tags=[THREAD_ENDPOINTS_TAG])
131
     @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
131
     @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
132
-    @hapic.handle_exception(InsufficientUserProfile, HTTPStatus.FORBIDDEN)
132
+    @hapic.handle_exception(InsufficientUserWorkspaceRole, HTTPStatus.FORBIDDEN)
133
     @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
133
     @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
134
     @hapic.handle_exception(AuthenticationFailed, HTTPStatus.BAD_REQUEST)
134
     @hapic.handle_exception(AuthenticationFailed, HTTPStatus.BAD_REQUEST)
135
     @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
135
     @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)