Selaa lähdekoodia

Merge pull request #18 from tracim/feature/add_few_delete_endpoints

Bastien Sevajol 6 vuotta sitten
vanhempi
commit
a9dc619980
No account linked to committer's email

+ 2 - 1
backend/doc/roles.md Näytä tiedosto

@@ -9,7 +9,7 @@ The other is workspace related and is called "workspace role".
9 9
 
10 10
 |                               | Normal User | Managers    | Admin          |
11 11
 |-------------------------------|-------------|-------------|----------------|
12
-| slug                            | users       | managers    | administrators |
12
+| slug                          | users       | managers    | administrators |
13 13
 |-------------------------------|-------------|-------------|---------|
14 14
 
15 15
 
@@ -20,6 +20,7 @@ The other is workspace related and is called "workspace role".
20 20
 |-------------------------------|-------------|-------------|---------|
21 21
 | create workspace              |  no         | yes         | yes     |
22 22
 | invite user to tracim         |  no         | yes, if manager of a given workspace         | yes     |
23
+| delete workspace              |  no         | yes, if manager of a given workspace         | yes     |
23 24
 |-------------------------------|-------------|-------------|---------|
24 25
 | set user global profile rights|  no         | no          | yes     |
25 26
 | deactivate user               |  no         | no          | yes     |

+ 13 - 12
backend/tracim_backend/lib/core/workspace.py Näytä tiedosto

@@ -27,7 +27,8 @@ class WorkspaceApi(object):
27 27
             session: Session,
28 28
             current_user: User,
29 29
             config: CFG,
30
-            force_role: bool=False
30
+            force_role: bool=False,
31
+            show_deleted: bool=False,
31 32
     ):
32 33
         """
33 34
         :param current_user: Current user of context
@@ -37,18 +38,22 @@ class WorkspaceApi(object):
37 38
         self._user = current_user
38 39
         self._config = config
39 40
         self._force_role = force_role
41
+        self.show_deleted = show_deleted
40 42
 
41 43
     def _base_query_without_roles(self):
42
-        return self._session.query(Workspace).filter(Workspace.is_deleted == False)
44
+        query = self._session.query(Workspace)
45
+        if not self.show_deleted:
46
+            query = query.filter(Workspace.is_deleted == False)
47
+        return query
43 48
 
44 49
     def _base_query(self):
45 50
         if not self._force_role and self._user.profile.id>=Group.TIM_ADMIN:
46 51
             return self._base_query_without_roles()
47 52
 
48
-        return self._session.query(Workspace).\
49
-            join(Workspace.roles).\
50
-            filter(UserRoleInWorkspace.user_id == self._user.user_id).\
51
-            filter(Workspace.is_deleted == False)
53
+        query = self._base_query_without_roles()
54
+        query = query.join(Workspace.roles).\
55
+            filter(UserRoleInWorkspace.user_id == self._user.user_id)
56
+        return query
52 57
 
53 58
     def get_workspace_with_context(
54 59
             self,
@@ -207,17 +212,13 @@ class WorkspaceApi(object):
207 212
     def save(self, workspace: Workspace):
208 213
         self._session.flush()
209 214
 
210
-    def delete_one(self, workspace_id, flush=True):
211
-        workspace = self.get_one(workspace_id)
215
+    def delete(self, workspace: Workspace, flush=True):
212 216
         workspace.is_deleted = True
213 217
 
214 218
         if flush:
215 219
             self._session.flush()
216 220
 
217
-    def restore_one(self, workspace_id, flush=True):
218
-        workspace = self._session.query(Workspace)\
219
-            .filter(Workspace.is_deleted==True)\
220
-            .filter(Workspace.workspace_id==workspace_id).one()
221
+    def undelete(self, workspace: Workspace, flush=True):
221 222
         workspace.is_deleted = False
222 223
 
223 224
         if flush:

+ 34 - 0
backend/tracim_backend/lib/utils/authorization.py Näytä tiedosto

@@ -90,6 +90,40 @@ def require_profile(group: int) -> typing.Callable:
90 90
     return decorator
91 91
 
92 92
 
93
+def require_profile_or_other_profile_with_workspace_role(
94
+        allow_all_group: int,
95
+        allow_if_role_group: int,
96
+        minimal_required_role: int,
97
+) -> typing.Callable:
98
+    """
99
+    Allow access for allow_all_group profile
100
+    or allow access for allow_if_role_group
101
+    profile if mininal_required_role is correct.
102
+    :param allow_all_group: value from Group Object
103
+    like Group.TIM_USER or Group.TIM_MANAGER
104
+    :param allow_if_role_group: value from Group Object
105
+    like Group.TIM_USER or Group.TIM_MANAGER
106
+    :param minimal_required_role: value from UserInWorkspace Object like
107
+    UserRoleInWorkspace.CONTRIBUTOR or UserRoleInWorkspace.READER
108
+    :return: decorator
109
+    """
110
+    def decorator(func: typing.Callable) -> typing.Callable:
111
+        @functools.wraps(func)
112
+        def wrapper(self, context, request: 'TracimRequest') -> typing.Callable:
113
+            user = request.current_user
114
+            workspace = request.current_workspace
115
+            if user.profile.id >= allow_all_group:
116
+                return func(self, context, request)
117
+            elif user.profile.id >= allow_if_role_group:
118
+                if workspace.get_user_role(user) >= minimal_required_role:
119
+                    return func(self, context, request)
120
+                raise InsufficientUserRoleInWorkspace()
121
+            else:
122
+                raise InsufficientUserProfile()
123
+        return wrapper
124
+    return decorator
125
+
126
+
93 127
 def require_workspace_role(minimal_required_role: int) -> typing.Callable:
94 128
     """
95 129
     Restricts access to endpoint to minimal role or raise an exception.

+ 4 - 2
backend/tracim_backend/lib/utils/request.py Näytä tiedosto

@@ -355,7 +355,8 @@ class TracimRequest(Request):
355 355
             wapi = WorkspaceApi(
356 356
                 current_user=user,
357 357
                 session=request.dbsession,
358
-                config=request.registry.settings['CFG']
358
+                config=request.registry.settings['CFG'],
359
+                show_deleted=True,
359 360
             )
360 361
             workspace = wapi.get_one(workspace_id)
361 362
         except NoResultFound as exc:
@@ -390,7 +391,8 @@ class TracimRequest(Request):
390 391
             wapi = WorkspaceApi(
391 392
                 current_user=user,
392 393
                 session=request.dbsession,
393
-                config=request.registry.settings['CFG']
394
+                config=request.registry.settings['CFG'],
395
+                show_deleted=True,
394 396
             )
395 397
             workspace = wapi.get_one(workspace_id)
396 398
         except JSONDecodeError as exc:

+ 7 - 0
backend/tracim_backend/models/context_models.py Näytä tiedosto

@@ -470,6 +470,13 @@ class WorkspaceInContext(object):
470 470
         return slugify(self.workspace.label)
471 471
 
472 472
     @property
473
+    def is_deleted(self) -> bool:
474
+        """
475
+        Is the workspace deleted ?
476
+        """
477
+        return self.workspace.is_deleted
478
+
479
+    @property
473 480
     def sidebar_entries(self) -> typing.List[WorkspaceMenuEntry]:
474 481
         """
475 482
         get sidebar entries, those depends on activated apps.

+ 1 - 1
backend/tracim_backend/tests/functional/test_user.py Näytä tiedosto

@@ -2382,6 +2382,7 @@ class TestUserWorkspaceEndpoint(FunctionalTest):
2382 2382
         assert workspace['workspace_id'] == 1
2383 2383
         assert workspace['label'] == 'Business'
2384 2384
         assert workspace['slug'] == 'business'
2385
+        assert workspace['is_deleted'] is False
2385 2386
         assert len(workspace['sidebar_entries']) == 5
2386 2387
 
2387 2388
         # TODO - G.M - 2018-08-02 - Better test for sidebar entry, make it
@@ -2421,7 +2422,6 @@ class TestUserWorkspaceEndpoint(FunctionalTest):
2421 2422
         assert sidebar_entry['hexcolor'] == "#ad4cf9"
2422 2423
         assert sidebar_entry['fa_icon'] == "comments-o"
2423 2424
 
2424
-
2425 2425
     def test_api__get_user_workspaces__err_403__unallowed_user(self):
2426 2426
         """
2427 2427
         Check obtain all workspaces reachables for one user

+ 727 - 0
backend/tracim_backend/tests/functional/test_workspaces.py Näytä tiedosto

@@ -8,9 +8,13 @@ from depot.io.utils import FileIntent
8 8
 
9 9
 from tracim_backend import models
10 10
 from tracim_backend.lib.core.content import ContentApi
11
+from tracim_backend.lib.core.group import GroupApi
12
+from tracim_backend.lib.core.user import UserApi
13
+from tracim_backend.lib.core.userworkspace import RoleApi
11 14
 from tracim_backend.lib.core.workspace import WorkspaceApi
12 15
 from tracim_backend.models import get_tm_session
13 16
 from tracim_backend.models.contents import CONTENT_TYPES
17
+from tracim_backend.models.data import UserRoleInWorkspace
14 18
 from tracim_backend.tests import FunctionalTest
15 19
 from tracim_backend.tests import set_html_document_slug_to_legacy
16 20
 from tracim_backend.fixtures.content import Content as ContentFixtures
@@ -41,6 +45,7 @@ class TestWorkspaceEndpoint(FunctionalTest):
41 45
         assert workspace['slug'] == 'business'
42 46
         assert workspace['label'] == 'Business'
43 47
         assert workspace['description'] == 'All importants documents'
48
+        assert workspace['is_deleted'] is False
44 49
         assert len(workspace['sidebar_entries']) == 5
45 50
 
46 51
         # TODO - G.M - 2018-08-02 - Better test for sidebar entry, make it
@@ -106,6 +111,7 @@ class TestWorkspaceEndpoint(FunctionalTest):
106 111
         assert workspace['slug'] == 'business'
107 112
         assert workspace['label'] == 'Business'
108 113
         assert workspace['description'] == 'All importants documents'
114
+        assert workspace['is_deleted'] is False
109 115
         assert len(workspace['sidebar_entries']) == 5
110 116
 
111 117
         # modify workspace
@@ -120,6 +126,7 @@ class TestWorkspaceEndpoint(FunctionalTest):
120 126
         assert workspace['slug'] == 'superworkspace'
121 127
         assert workspace['label'] == 'superworkspace'
122 128
         assert workspace['description'] == 'mysuperdescription'
129
+        assert workspace['is_deleted'] is False
123 130
         assert len(workspace['sidebar_entries']) == 5
124 131
 
125 132
         # after
@@ -133,6 +140,7 @@ class TestWorkspaceEndpoint(FunctionalTest):
133 140
         assert workspace['slug'] == 'superworkspace'
134 141
         assert workspace['label'] == 'superworkspace'
135 142
         assert workspace['description'] == 'mysuperdescription'
143
+        assert workspace['is_deleted'] is False
136 144
         assert len(workspace['sidebar_entries']) == 5
137 145
 
138 146
     def test_api__update_workspace__err_400__empty_label(self) -> None:
@@ -207,6 +215,610 @@ class TestWorkspaceEndpoint(FunctionalTest):
207 215
             params=params,
208 216
         )
209 217
 
218
+    def test_api__delete_workspace__ok_200__admin(self) -> None:
219
+        """
220
+        Test delete workspace as admin
221
+        """
222
+        self.testapp.authorization = (
223
+            'Basic',
224
+            (
225
+                'admin@admin.admin',
226
+                'admin@admin.admin'
227
+            )
228
+        )
229
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
230
+        admin = dbsession.query(models.User) \
231
+            .filter(models.User.email == 'admin@admin.admin') \
232
+            .one()
233
+        uapi = UserApi(
234
+            current_user=admin,
235
+            session=dbsession,
236
+            config=self.app_config,
237
+        )
238
+        gapi = GroupApi(
239
+            current_user=admin,
240
+            session=dbsession,
241
+            config=self.app_config,
242
+        )
243
+        groups = [gapi.get_one_with_name('administrators')]
244
+        user = uapi.create_user('test@test.test', password='test@test.test', do_save=True, do_notify=False, groups=groups)  # nopep8
245
+        workspace_api = WorkspaceApi(
246
+            current_user=admin,
247
+            session=dbsession,
248
+            config=self.app_config,
249
+            show_deleted=True,
250
+        )
251
+        workspace = workspace_api.create_workspace('test', save_now=True)  # nopep8
252
+        transaction.commit()
253
+        workspace_id = int(workspace.workspace_id)
254
+        self.testapp.authorization = (
255
+            'Basic',
256
+            (
257
+                'test@test.test',
258
+                'test@test.test'
259
+            )
260
+        )
261
+        # delete
262
+        res = self.testapp.put(
263
+            '/api/v2/workspaces/{}/delete'.format(workspace_id),
264
+            status=204
265
+        )
266
+        res = self.testapp.get(
267
+            '/api/v2/workspaces/{}'.format(workspace_id),
268
+            status=403
269
+        )
270
+        self.testapp.authorization = (
271
+            'Basic',
272
+            (
273
+                'admin@admin.admin',
274
+                'admin@admin.admin'
275
+            )
276
+        )
277
+        res = self.testapp.get(
278
+            '/api/v2/workspaces/{}'.format(workspace_id),
279
+            status=200
280
+        )
281
+        workspace = res.json_body
282
+        assert workspace['is_deleted'] is True
283
+
284
+    def test_api__delete_workspace__ok_200__manager_workspace_manager(self) -> None:
285
+        """
286
+        Test delete workspace as global manager and workspace manager
287
+        """
288
+        self.testapp.authorization = (
289
+            'Basic',
290
+            (
291
+                'admin@admin.admin',
292
+                'admin@admin.admin'
293
+            )
294
+        )
295
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
296
+        admin = dbsession.query(models.User) \
297
+            .filter(models.User.email == 'admin@admin.admin') \
298
+            .one()
299
+        uapi = UserApi(
300
+            current_user=admin,
301
+            session=dbsession,
302
+            config=self.app_config,
303
+        )
304
+        gapi = GroupApi(
305
+            current_user=admin,
306
+            session=dbsession,
307
+            config=self.app_config,
308
+        )
309
+        groups = [gapi.get_one_with_name('managers')]
310
+        user = uapi.create_user('test@test.test', password='test@test.test', do_save=True, do_notify=False, groups=groups)  # nopep8
311
+        workspace_api = WorkspaceApi(
312
+            current_user=admin,
313
+            session=dbsession,
314
+            config=self.app_config,
315
+            show_deleted=True,
316
+        )
317
+        workspace = workspace_api.create_workspace('test', save_now=True)  # nopep8
318
+        rapi = RoleApi(
319
+            current_user=admin,
320
+            session=dbsession,
321
+            config=self.app_config,
322
+        )
323
+        rapi.create_one(user, workspace, UserRoleInWorkspace.WORKSPACE_MANAGER, False)  # nopep8
324
+        transaction.commit()
325
+        workspace_id = int(workspace.workspace_id)
326
+        self.testapp.authorization = (
327
+            'Basic',
328
+            (
329
+                'test@test.test',
330
+                'test@test.test'
331
+            )
332
+        )
333
+        # delete
334
+        res = self.testapp.put(
335
+            '/api/v2/workspaces/{}/delete'.format(workspace_id),
336
+            status=204
337
+        )
338
+        res = self.testapp.get(
339
+            '/api/v2/workspaces/{}'.format(workspace_id),
340
+            status=200
341
+        )
342
+        workspace = res.json_body
343
+        assert workspace['is_deleted'] is True
344
+
345
+    def test_api__delete_workspace__err_403__user_workspace_manager(self) -> None:
346
+        """
347
+        Test delete workspace as simple user and workspace manager
348
+        """
349
+        self.testapp.authorization = (
350
+            'Basic',
351
+            (
352
+                'admin@admin.admin',
353
+                'admin@admin.admin'
354
+            )
355
+        )
356
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
357
+        admin = dbsession.query(models.User) \
358
+            .filter(models.User.email == 'admin@admin.admin') \
359
+            .one()
360
+        uapi = UserApi(
361
+            current_user=admin,
362
+            session=dbsession,
363
+            config=self.app_config,
364
+        )
365
+        gapi = GroupApi(
366
+            current_user=admin,
367
+            session=dbsession,
368
+            config=self.app_config,
369
+        )
370
+        groups = [gapi.get_one_with_name('users')]
371
+        user = uapi.create_user('test@test.test', password='test@test.test', do_save=True, do_notify=False, groups=groups)  # nopep8
372
+        workspace_api = WorkspaceApi(
373
+            current_user=admin,
374
+            session=dbsession,
375
+            config=self.app_config,
376
+            show_deleted=True,
377
+        )
378
+        workspace = workspace_api.create_workspace('test', save_now=True)  # nopep8
379
+        rapi = RoleApi(
380
+            current_user=admin,
381
+            session=dbsession,
382
+            config=self.app_config,
383
+        )
384
+        rapi.create_one(user, workspace, UserRoleInWorkspace.WORKSPACE_MANAGER, False)  # nopep8
385
+        transaction.commit()
386
+        workspace_id = int(workspace.workspace_id)
387
+        self.testapp.authorization = (
388
+            'Basic',
389
+            (
390
+                'test@test.test',
391
+                'test@test.test'
392
+            )
393
+        )
394
+        # delete
395
+        res = self.testapp.put(
396
+            '/api/v2/workspaces/{}/delete'.format(workspace_id),
397
+            status=403
398
+        )
399
+        res = self.testapp.get(
400
+            '/api/v2/workspaces/{}'.format(workspace_id),
401
+            status=200
402
+        )
403
+        workspace = res.json_body
404
+        assert workspace['is_deleted'] is False
405
+
406
+    def test_api__delete_workspace__err_403__manager_reader(self) -> None:
407
+        """
408
+        Test delete workspace as manager and reader of the workspace
409
+        """
410
+        self.testapp.authorization = (
411
+            'Basic',
412
+            (
413
+                'admin@admin.admin',
414
+                'admin@admin.admin'
415
+            )
416
+        )
417
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
418
+        admin = dbsession.query(models.User) \
419
+            .filter(models.User.email == 'admin@admin.admin') \
420
+            .one()
421
+        uapi = UserApi(
422
+            current_user=admin,
423
+            session=dbsession,
424
+            config=self.app_config,
425
+        )
426
+        gapi = GroupApi(
427
+            current_user=admin,
428
+            session=dbsession,
429
+            config=self.app_config,
430
+        )
431
+        groups = [gapi.get_one_with_name('managers')]
432
+        user = uapi.create_user('test@test.test', password='test@test.test', do_save=True, do_notify=False)  # nopep8
433
+        workspace_api = WorkspaceApi(
434
+            current_user=admin,
435
+            session=dbsession,
436
+            config=self.app_config,
437
+            show_deleted=True,
438
+        )
439
+        workspace = workspace_api.create_workspace('test', save_now=True)  # nopep8
440
+        rapi = RoleApi(
441
+            current_user=admin,
442
+            session=dbsession,
443
+            config=self.app_config,
444
+        )
445
+        rapi.create_one(user, workspace, UserRoleInWorkspace.READER, False)  # nopep8
446
+        transaction.commit()
447
+        workspace_id = int(workspace.workspace_id)
448
+        self.testapp.authorization = (
449
+            'Basic',
450
+            (
451
+                'test@test.test',
452
+                'test@test.test'
453
+            )
454
+        )
455
+        # delete
456
+        res = self.testapp.put(
457
+            '/api/v2/workspaces/{}/delete'.format(workspace_id),
458
+            status=403
459
+        )
460
+        res = self.testapp.get(
461
+            '/api/v2/workspaces/{}'.format(workspace_id),
462
+            status=200
463
+        )
464
+        workspace = res.json_body
465
+        assert workspace['is_deleted'] is False
466
+
467
+    def test_api__delete_workspace__err_400__manager(self) -> None:
468
+        """
469
+        Test delete workspace as global manager without having any role in the
470
+        workspace
471
+        """
472
+        self.testapp.authorization = (
473
+            'Basic',
474
+            (
475
+                'admin@admin.admin',
476
+                'admin@admin.admin'
477
+            )
478
+        )
479
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
480
+        admin = dbsession.query(models.User) \
481
+            .filter(models.User.email == 'admin@admin.admin') \
482
+            .one()
483
+        uapi = UserApi(
484
+            current_user=admin,
485
+            session=dbsession,
486
+            config=self.app_config,
487
+        )
488
+        user = uapi.create_user('test@test.test', password='test@test.test',
489
+                                do_save=True, do_notify=False)  # nopep8
490
+        workspace_api = WorkspaceApi(
491
+            current_user=admin,
492
+            session=dbsession,
493
+            config=self.app_config,
494
+            show_deleted=True,
495
+        )
496
+        workspace = workspace_api.create_workspace('test',
497
+                                                   save_now=True)  # nopep8
498
+        rapi = RoleApi(
499
+            current_user=admin,
500
+            session=dbsession,
501
+            config=self.app_config,
502
+        )
503
+        transaction.commit()
504
+        workspace_id = int(workspace.workspace_id)
505
+        self.testapp.authorization = (
506
+            'Basic',
507
+            (
508
+                'test@test.test',
509
+                'test@test.test'
510
+            )
511
+        )
512
+        # delete
513
+        res = self.testapp.put(
514
+            '/api/v2/workspaces/{}/delete'.format(workspace_id),
515
+            status=400
516
+        )
517
+
518
+    def test_api__undelete_workspace__ok_200__admin(self) -> None:
519
+        """
520
+        Test undelete workspace as admin
521
+        """
522
+        self.testapp.authorization = (
523
+            'Basic',
524
+            (
525
+                'admin@admin.admin',
526
+                'admin@admin.admin'
527
+            )
528
+        )
529
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
530
+        admin = dbsession.query(models.User) \
531
+            .filter(models.User.email == 'admin@admin.admin') \
532
+            .one()
533
+        uapi = UserApi(
534
+            current_user=admin,
535
+            session=dbsession,
536
+            config=self.app_config,
537
+        )
538
+        gapi = GroupApi(
539
+            current_user=admin,
540
+            session=dbsession,
541
+            config=self.app_config,
542
+        )
543
+        groups = [gapi.get_one_with_name('administrators')]
544
+        user = uapi.create_user('test@test.test', password='test@test.test', do_save=True, do_notify=False, groups=groups)  # nopep8
545
+        workspace_api = WorkspaceApi(
546
+            current_user=admin,
547
+            session=dbsession,
548
+            config=self.app_config,
549
+            show_deleted=True,
550
+        )
551
+        workspace = workspace_api.create_workspace('test', save_now=True)  # nopep8
552
+        workspace_api.delete(workspace, flush=True)
553
+        transaction.commit()
554
+        workspace_id = int(workspace.workspace_id)
555
+        self.testapp.authorization = (
556
+            'Basic',
557
+            (
558
+                'test@test.test',
559
+                'test@test.test'
560
+            )
561
+        )
562
+        # delete
563
+        res = self.testapp.put(
564
+            '/api/v2/workspaces/{}/undelete'.format(workspace_id),
565
+            status=204
566
+        )
567
+        res = self.testapp.get(
568
+            '/api/v2/workspaces/{}'.format(workspace_id),
569
+            status=403
570
+        )
571
+        self.testapp.authorization = (
572
+            'Basic',
573
+            (
574
+                'admin@admin.admin',
575
+                'admin@admin.admin'
576
+            )
577
+        )
578
+        res = self.testapp.get(
579
+            '/api/v2/workspaces/{}'.format(workspace_id),
580
+            status=200
581
+        )
582
+        workspace = res.json_body
583
+        assert workspace['is_deleted'] is False
584
+
585
+    def test_api__undelete_workspace__ok_200__manager_workspace_manager(self) -> None:
586
+        """
587
+        Test undelete workspace as global manager and workspace manager
588
+        """
589
+        self.testapp.authorization = (
590
+            'Basic',
591
+            (
592
+                'admin@admin.admin',
593
+                'admin@admin.admin'
594
+            )
595
+        )
596
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
597
+        admin = dbsession.query(models.User) \
598
+            .filter(models.User.email == 'admin@admin.admin') \
599
+            .one()
600
+        uapi = UserApi(
601
+            current_user=admin,
602
+            session=dbsession,
603
+            config=self.app_config,
604
+        )
605
+        gapi = GroupApi(
606
+            current_user=admin,
607
+            session=dbsession,
608
+            config=self.app_config,
609
+        )
610
+        groups = [gapi.get_one_with_name('managers')]
611
+        user = uapi.create_user('test@test.test', password='test@test.test', do_save=True, do_notify=False, groups=groups)  # nopep8
612
+        workspace_api = WorkspaceApi(
613
+            current_user=admin,
614
+            session=dbsession,
615
+            config=self.app_config,
616
+            show_deleted=True,
617
+        )
618
+        workspace = workspace_api.create_workspace('test', save_now=True)  # nopep8
619
+        workspace_api.delete(workspace, flush=True)
620
+        rapi = RoleApi(
621
+            current_user=admin,
622
+            session=dbsession,
623
+            config=self.app_config,
624
+        )
625
+        rapi.create_one(user, workspace, UserRoleInWorkspace.WORKSPACE_MANAGER, False)  # nopep8
626
+        transaction.commit()
627
+        workspace_id = int(workspace.workspace_id)
628
+        self.testapp.authorization = (
629
+            'Basic',
630
+            (
631
+                'test@test.test',
632
+                'test@test.test'
633
+            )
634
+        )
635
+        # delete
636
+        res = self.testapp.put(
637
+            '/api/v2/workspaces/{}/undelete'.format(workspace_id),
638
+            status=204
639
+        )
640
+        res = self.testapp.get(
641
+            '/api/v2/workspaces/{}'.format(workspace_id),
642
+            status=200
643
+        )
644
+        workspace = res.json_body
645
+        assert workspace['is_deleted'] is False
646
+
647
+    def test_api__undelete_workspace__err_403__user_workspace_manager(self) -> None:
648
+        """
649
+        Test undelete workspace as simple user and workspace manager
650
+        """
651
+        self.testapp.authorization = (
652
+            'Basic',
653
+            (
654
+                'admin@admin.admin',
655
+                'admin@admin.admin'
656
+            )
657
+        )
658
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
659
+        admin = dbsession.query(models.User) \
660
+            .filter(models.User.email == 'admin@admin.admin') \
661
+            .one()
662
+        uapi = UserApi(
663
+            current_user=admin,
664
+            session=dbsession,
665
+            config=self.app_config,
666
+        )
667
+        gapi = GroupApi(
668
+            current_user=admin,
669
+            session=dbsession,
670
+            config=self.app_config,
671
+        )
672
+        groups = [gapi.get_one_with_name('users')]
673
+        user = uapi.create_user('test@test.test', password='test@test.test', do_save=True, do_notify=False, groups=groups)  # nopep8
674
+        workspace_api = WorkspaceApi(
675
+            current_user=admin,
676
+            session=dbsession,
677
+            config=self.app_config,
678
+            show_deleted=True,
679
+        )
680
+        workspace = workspace_api.create_workspace('test', save_now=True)  # nopep8
681
+        workspace_api.delete(workspace, flush=True)
682
+        rapi = RoleApi(
683
+            current_user=admin,
684
+            session=dbsession,
685
+            config=self.app_config,
686
+        )
687
+        rapi.create_one(user, workspace, UserRoleInWorkspace.WORKSPACE_MANAGER, False)  # nopep8
688
+        transaction.commit()
689
+        workspace_id = int(workspace.workspace_id)
690
+        self.testapp.authorization = (
691
+            'Basic',
692
+            (
693
+                'test@test.test',
694
+                'test@test.test'
695
+            )
696
+        )
697
+        # delete
698
+        res = self.testapp.put(
699
+            '/api/v2/workspaces/{}/undelete'.format(workspace_id),
700
+            status=403
701
+        )
702
+        res = self.testapp.get(
703
+            '/api/v2/workspaces/{}'.format(workspace_id),
704
+            status=200
705
+        )
706
+        workspace = res.json_body
707
+        assert workspace['is_deleted'] is True
708
+
709
+    def test_api__undelete_workspace__err_403__manager_reader(self) -> None:
710
+        """
711
+        Test undelete workspace as manager and reader of the workspace
712
+        """
713
+        self.testapp.authorization = (
714
+            'Basic',
715
+            (
716
+                'admin@admin.admin',
717
+                'admin@admin.admin'
718
+            )
719
+        )
720
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
721
+        admin = dbsession.query(models.User) \
722
+            .filter(models.User.email == 'admin@admin.admin') \
723
+            .one()
724
+        uapi = UserApi(
725
+            current_user=admin,
726
+            session=dbsession,
727
+            config=self.app_config,
728
+        )
729
+        gapi = GroupApi(
730
+            current_user=admin,
731
+            session=dbsession,
732
+            config=self.app_config,
733
+        )
734
+        groups = [gapi.get_one_with_name('managers')]
735
+        user = uapi.create_user('test@test.test', password='test@test.test', do_save=True, do_notify=False)  # nopep8
736
+        workspace_api = WorkspaceApi(
737
+            current_user=admin,
738
+            session=dbsession,
739
+            config=self.app_config,
740
+            show_deleted=True,
741
+        )
742
+        workspace = workspace_api.create_workspace('test', save_now=True)  # nopep8
743
+        workspace_api.delete(workspace, flush=True)
744
+        rapi = RoleApi(
745
+            current_user=admin,
746
+            session=dbsession,
747
+            config=self.app_config,
748
+        )
749
+        rapi.create_one(user, workspace, UserRoleInWorkspace.READER, False)  # nopep8
750
+        transaction.commit()
751
+        workspace_id = int(workspace.workspace_id)
752
+        self.testapp.authorization = (
753
+            'Basic',
754
+            (
755
+                'test@test.test',
756
+                'test@test.test'
757
+            )
758
+        )
759
+        # delete
760
+        res = self.testapp.put(
761
+            '/api/v2/workspaces/{}/undelete'.format(workspace_id),
762
+            status=403
763
+        )
764
+        res = self.testapp.get(
765
+            '/api/v2/workspaces/{}'.format(workspace_id),
766
+            status=200
767
+        )
768
+        workspace = res.json_body
769
+        assert workspace['is_deleted'] is True
770
+
771
+    def test_api__undelete_workspace__err_400__manager(self) -> None:
772
+        """
773
+        Test delete workspace as global manager without having any role in the
774
+        workspace
775
+        """
776
+        self.testapp.authorization = (
777
+            'Basic',
778
+            (
779
+                'admin@admin.admin',
780
+                'admin@admin.admin'
781
+            )
782
+        )
783
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
784
+        admin = dbsession.query(models.User) \
785
+            .filter(models.User.email == 'admin@admin.admin') \
786
+            .one()
787
+        uapi = UserApi(
788
+            current_user=admin,
789
+            session=dbsession,
790
+            config=self.app_config,
791
+        )
792
+        user = uapi.create_user('test@test.test', password='test@test.test',
793
+                                do_save=True, do_notify=False)  # nopep8
794
+        workspace_api = WorkspaceApi(
795
+            current_user=admin,
796
+            session=dbsession,
797
+            config=self.app_config,
798
+            show_deleted=True,
799
+        )
800
+        workspace = workspace_api.create_workspace('test', save_now=True)  # nopep8
801
+        workspace_api.delete(workspace, flush=True)
802
+        rapi = RoleApi(
803
+            current_user=admin,
804
+            session=dbsession,
805
+            config=self.app_config,
806
+        )
807
+        transaction.commit()
808
+        workspace_id = int(workspace.workspace_id)
809
+        self.testapp.authorization = (
810
+            'Basic',
811
+            (
812
+                'test@test.test',
813
+                'test@test.test'
814
+            )
815
+        )
816
+        # delete
817
+        res = self.testapp.put(
818
+            '/api/v2/workspaces/{}/undelete'.format(workspace_id),
819
+            status=400
820
+        )
821
+
210 822
     def test_api__get_workspace__err_400__unallowed_user(self) -> None:
211 823
         """
212 824
         Check obtain workspace unreachable for user
@@ -599,6 +1211,121 @@ class TestWorkspaceMembersEndpoint(FunctionalTest):
599 1211
         assert user_role['user_id'] == 1
600 1212
         assert user_role['workspace_id'] == 1
601 1213
 
1214
+    def test_api__delete_workspace_member_role__ok_200__nominal_case(self):
1215
+        """
1216
+        Delete worskpace member role
1217
+        """
1218
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
1219
+        admin = dbsession.query(models.User) \
1220
+            .filter(models.User.email == 'admin@admin.admin') \
1221
+            .one()
1222
+        uapi = UserApi(
1223
+            current_user=admin,
1224
+            session=dbsession,
1225
+            config=self.app_config,
1226
+        )
1227
+        gapi = GroupApi(
1228
+            current_user=admin,
1229
+            session=dbsession,
1230
+            config=self.app_config,
1231
+        )
1232
+        groups = [gapi.get_one_with_name('managers')]
1233
+        user = uapi.create_user('test@test.test', password='test@test.test', do_save=True, do_notify=False, groups=groups)  # nopep8
1234
+        workspace_api = WorkspaceApi(
1235
+            current_user=admin,
1236
+            session=dbsession,
1237
+            config=self.app_config,
1238
+            show_deleted=True,
1239
+        )
1240
+        workspace = workspace_api.create_workspace('test', save_now=True)  # nopep8
1241
+        rapi = RoleApi(
1242
+            current_user=admin,
1243
+            session=dbsession,
1244
+            config=self.app_config,
1245
+        )
1246
+        rapi.create_one(user, workspace, UserRoleInWorkspace.WORKSPACE_MANAGER, False)  # nopep8
1247
+        transaction.commit()
1248
+
1249
+        self.testapp.authorization = (
1250
+            'Basic',
1251
+            (
1252
+                'admin@admin.admin',
1253
+                'admin@admin.admin'
1254
+            )
1255
+        )
1256
+        res = self.testapp.delete(
1257
+            '/api/v2/workspaces/{workspace_id}/members/{user_id}'.format(
1258
+                workspace_id=workspace.workspace_id,
1259
+                user_id=user.user_id,
1260
+            ),
1261
+            status=204,
1262
+        )
1263
+        # after
1264
+        roles = self.testapp.get('/api/v2/workspaces/1/members', status=200).json_body   # nopep8
1265
+        for role in roles:
1266
+            assert role['user_id'] != user.user_id
1267
+
1268
+    def test_api__delete_workspace_member_role__err_400__simple_user(self):
1269
+        """
1270
+        Delete worskpace member role
1271
+        """
1272
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
1273
+        admin = dbsession.query(models.User) \
1274
+            .filter(models.User.email == 'admin@admin.admin') \
1275
+            .one()
1276
+        uapi = UserApi(
1277
+            current_user=admin,
1278
+            session=dbsession,
1279
+            config=self.app_config,
1280
+        )
1281
+        gapi = GroupApi(
1282
+            current_user=admin,
1283
+            session=dbsession,
1284
+            config=self.app_config,
1285
+        )
1286
+        groups = [gapi.get_one_with_name('users')]
1287
+        user2 = uapi.create_user('test2@test2.test2', password='test2@test2.test2', do_save=True, do_notify=False, groups=groups)  # nopep8
1288
+        groups = [gapi.get_one_with_name('managers')]
1289
+        user = uapi.create_user('test@test.test', password='test@test.test', do_save=True, do_notify=False, groups=groups)  # nopep8
1290
+        workspace_api = WorkspaceApi(
1291
+            current_user=admin,
1292
+            session=dbsession,
1293
+            config=self.app_config,
1294
+            show_deleted=True,
1295
+        )
1296
+        workspace = workspace_api.create_workspace('test', save_now=True)  # nopep8
1297
+        rapi = RoleApi(
1298
+            current_user=admin,
1299
+            session=dbsession,
1300
+            config=self.app_config,
1301
+        )
1302
+        rapi.create_one(user, workspace, UserRoleInWorkspace.WORKSPACE_MANAGER, False)  # nopep8
1303
+        rapi.create_one(user2, workspace, UserRoleInWorkspace.READER, False)  # nopep8
1304
+        transaction.commit()
1305
+
1306
+        self.testapp.authorization = (
1307
+            'Basic',
1308
+            (
1309
+                'test2@test2.test2',
1310
+                'test2@test2.test2'
1311
+            )
1312
+        )
1313
+        res = self.testapp.delete(
1314
+            '/api/v2/workspaces/{workspace_id}/members/{user_id}'.format(
1315
+                workspace_id=workspace.workspace_id,
1316
+                user_id=user.user_id,
1317
+            ),
1318
+            status=403,
1319
+        )
1320
+        # after
1321
+        roles = self.testapp.get(
1322
+            '/api/v2/workspaces/{workspace_id}/members'.format(
1323
+                workspace_id=workspace.workspace_id
1324
+            ),
1325
+            status=200
1326
+        ).json_body
1327
+        assert len([role for role in roles if role['user_id'] == user.user_id]) == 1  # nopep8
1328
+
602 1329
 
603 1330
 class TestUserInvitationWithMailActivatedSync(FunctionalTest):
604 1331
 

+ 1 - 0
backend/tracim_backend/views/core_api/schemas.py Näytä tiedosto

@@ -510,6 +510,7 @@ class WorkspaceDigestSchema(marshmallow.Schema):
510 510
         WorkspaceMenuEntrySchema,
511 511
         many=True,
512 512
     )
513
+    is_deleted = marshmallow.fields.Bool(example=False, default=False)
513 514
 
514 515
     class Meta:
515 516
         description = 'Digest of workspace informations'

+ 76 - 0
backend/tracim_backend/views/core_api/workspace_controller.py Näytä tiedosto

@@ -18,6 +18,7 @@ from tracim_backend.lib.core.workspace import WorkspaceApi
18 18
 from tracim_backend.lib.core.content import ContentApi
19 19
 from tracim_backend.lib.core.userworkspace import RoleApi
20 20
 from tracim_backend.lib.utils.authorization import require_workspace_role
21
+from tracim_backend.lib.utils.authorization import require_profile_or_other_profile_with_workspace_role
21 22
 from tracim_backend.lib.utils.authorization import require_profile
22 23
 from tracim_backend.models import Group
23 24
 from tracim_backend.lib.utils.authorization import require_candidate_workspace_role
@@ -122,6 +123,51 @@ class WorkspaceController(Controller):
122 123
         return wapi.get_workspace_with_context(workspace)
123 124
 
124 125
     @hapic.with_api_doc(tags=[SWAGGER_TAG_WORKSPACE_ENDPOINTS])
126
+    @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST)
127
+    @require_profile_or_other_profile_with_workspace_role(
128
+        Group.TIM_ADMIN,
129
+        Group.TIM_MANAGER,
130
+        UserRoleInWorkspace.WORKSPACE_MANAGER,
131
+    )
132
+    @hapic.input_path(WorkspaceIdPathSchema())
133
+    @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
134
+    def delete_workspace(self, context, request: TracimRequest, hapic_data=None):  # nopep8
135
+        """
136
+        delete workspace
137
+        """
138
+        app_config = request.registry.settings['CFG']
139
+        wapi = WorkspaceApi(
140
+            current_user=request.current_user,  # User
141
+            session=request.dbsession,
142
+            config=app_config,
143
+        )
144
+        wapi.delete(request.current_workspace, flush=True)
145
+        return
146
+
147
+    @hapic.with_api_doc(tags=[SWAGGER_TAG_WORKSPACE_ENDPOINTS])
148
+    @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST)
149
+    @require_profile_or_other_profile_with_workspace_role(
150
+        Group.TIM_ADMIN,
151
+        Group.TIM_MANAGER,
152
+        UserRoleInWorkspace.WORKSPACE_MANAGER,
153
+    )
154
+    @hapic.input_path(WorkspaceIdPathSchema())
155
+    @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
156
+    def undelete_workspace(self, context, request: TracimRequest, hapic_data=None):  # nopep8
157
+        """
158
+        restore deleted workspace
159
+        """
160
+        app_config = request.registry.settings['CFG']
161
+        wapi = WorkspaceApi(
162
+            current_user=request.current_user,  # User
163
+            session=request.dbsession,
164
+            config=app_config,
165
+            show_deleted=True,
166
+        )
167
+        wapi.undelete(request.current_workspace, flush=True)
168
+        return
169
+
170
+    @hapic.with_api_doc(tags=[SWAGGER_TAG_WORKSPACE_ENDPOINTS])
125 171
     @require_workspace_role(UserRoleInWorkspace.READER)
126 172
     @hapic.input_path(WorkspaceIdPathSchema())
127 173
     @hapic.output_body(WorkspaceMemberSchema(many=True))
@@ -180,6 +226,28 @@ class WorkspaceController(Controller):
180 226
         return rapi.get_user_role_workspace_with_context(role)
181 227
 
182 228
     @hapic.with_api_doc(tags=[SWAGGER_TAG_WORKSPACE_ENDPOINTS])
229
+    @require_workspace_role(UserRoleInWorkspace.WORKSPACE_MANAGER)
230
+    @hapic.input_path(WorkspaceAndUserIdPathSchema())
231
+    @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
232
+    def delete_workspaces_members_role(
233
+            self,
234
+            context,
235
+            request: TracimRequest,
236
+            hapic_data=None
237
+    ) -> None:
238
+        app_config = request.registry.settings['CFG']
239
+        rapi = RoleApi(
240
+            current_user=request.current_user,
241
+            session=request.dbsession,
242
+            config=app_config,
243
+        )
244
+        rapi.delete_one(
245
+            user_id=hapic_data.path.user_id,
246
+            workspace_id=hapic_data.path.workspace_id,
247
+        )
248
+        return
249
+
250
+    @hapic.with_api_doc(tags=[SWAGGER_TAG_WORKSPACE_ENDPOINTS])
183 251
     @hapic.handle_exception(UserCreationFailed, HTTPStatus.BAD_REQUEST)
184 252
     @require_workspace_role(UserRoleInWorkspace.WORKSPACE_MANAGER)
185 253
     @hapic.input_path(WorkspaceIdPathSchema())
@@ -576,6 +644,11 @@ class WorkspaceController(Controller):
576 644
         # Create workspace
577 645
         configurator.add_route('create_workspace', '/workspaces', request_method='POST')  # nopep8
578 646
         configurator.add_view(self.create_workspace, route_name='create_workspace')  # nopep8
647
+        # Delete/Undelete workpace
648
+        configurator.add_route('delete_workspace', '/workspaces/{workspace_id}/delete', request_method='PUT')  # nopep8
649
+        configurator.add_view(self.delete_workspace, route_name='delete_workspace')  # nopep8
650
+        configurator.add_route('undelete_workspace', '/workspaces/{workspace_id}/undelete', request_method='PUT')  # nopep8
651
+        configurator.add_view(self.undelete_workspace, route_name='undelete_workspace')  # nopep8
579 652
         # Update Workspace
580 653
         configurator.add_route('update_workspace', '/workspaces/{workspace_id}', request_method='PUT')  # nopep8
581 654
         configurator.add_view(self.update_workspace, route_name='update_workspace')  # nopep8
@@ -588,6 +661,9 @@ class WorkspaceController(Controller):
588 661
         # Create Workspace Members roles
589 662
         configurator.add_route('create_workspace_member', '/workspaces/{workspace_id}/members', request_method='POST')  # nopep8
590 663
         configurator.add_view(self.create_workspaces_members_role, route_name='create_workspace_member')  # nopep8
664
+        # Delete Workspace Members roles
665
+        configurator.add_route('delete_workspace_member', '/workspaces/{workspace_id}/members/{user_id}', request_method='DELETE')  # nopep8
666
+        configurator.add_view(self.delete_workspaces_members_role, route_name='delete_workspace_member')  # nopep8
591 667
         # Workspace Content
592 668
         configurator.add_route('workspace_content', '/workspaces/{workspace_id}/contents', request_method='GET')  # nopep8
593 669
         configurator.add_view(self.workspace_content, route_name='workspace_content')  # nopep8