From f570ff3cd5d26313c9d4da41da36207053930a29 Mon Sep 17 00:00:00 2001
From: Yohann D'ANELLO <yohann.danello@gmail.com>
Date: Wed, 23 Dec 2020 18:21:59 +0100
Subject: [PATCH] Check that permissions are working when accessing to API
 pages

Signed-off-by: Yohann D'ANELLO <yohann.danello@gmail.com>
---
 apps/activity/tests/test_activities.py  | 17 +++++-
 apps/api/tests.py                       | 77 ++++++++++++++++++++++++-
 apps/member/tests/test_memberships.py   | 14 ++++-
 apps/note/api/views.py                  |  9 ++-
 apps/note/tests/test_transactions.py    | 29 +++++++++-
 apps/permission/decorators.py           |  6 +-
 apps/treasury/tests/test_treasury.py    | 24 +++++++-
 apps/wei/tests/test_wei_registration.py | 31 +++++++++-
 8 files changed, 193 insertions(+), 14 deletions(-)

diff --git a/apps/activity/tests/test_activities.py b/apps/activity/tests/test_activities.py
index 366f9926..15635a6b 100644
--- a/apps/activity/tests/test_activities.py
+++ b/apps/activity/tests/test_activities.py
@@ -210,9 +210,24 @@ class TestActivityAPI(TestAPI):
 
     def test_activity_api(self):
         """
-        Load API pages for the activity app and test all filters
+        Load Activity API page and test all filters and permissions
         """
         self.check_viewset(ActivityViewSet, "/api/activity/activity/")
+
+    def test_activity_type_api(self):
+        """
+        Load ActivityType API page and test all filters and permissions
+        """
         self.check_viewset(ActivityTypeViewSet, "/api/activity/type/")
+
+    def test_entry_api(self):
+        """
+        Load Entry API page and test all filters and permissions
+        """
         self.check_viewset(EntryViewSet, "/api/activity/entry/")
+
+    def test_guest_api(self):
+        """
+        Load Guest API page and test all filters and permissions
+        """
         self.check_viewset(GuestViewSet, "/api/activity/guest/")
diff --git a/apps/api/tests.py b/apps/api/tests.py
index 88290a4f..6d2b09d1 100644
--- a/apps/api/tests.py
+++ b/apps/api/tests.py
@@ -2,14 +2,18 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
 
 import json
-from datetime import datetime
+from datetime import datetime, date
 from urllib.parse import quote_plus
 from warnings import warn
 
 from django.contrib.auth.models import User
+from django.contrib.contenttypes.models import ContentType
+from django.db.models.fields.files import ImageFieldFile
 from django.test import TestCase
 from django_filters.rest_framework import DjangoFilterBackend, OrderingFilter
+from member.models import Membership, Club
 from note.models import NoteClub, NoteUser, Alias, Note
+from permission.models import PermissionMask, Permission, Role
 from phonenumbers import PhoneNumber
 from rest_framework.filters import SearchFilter
 
@@ -103,6 +107,77 @@ class TestAPI(TestCase):
                                                             f"{model._meta.verbose_name} does not work. "
                                                             f"Given parameter: {value}")
 
+            self.check_permissions(url, obj)
+
+    def check_permissions(self, url, obj):
+        """
+        Check that permissions are working
+        """
+        # Drop rights
+        self.user.is_superuser = False
+        self.user.save()
+        sess = self.client.session
+        sess["permission_mask"] = 0
+        sess.save()
+
+        # Delete user permissions
+        for m in Membership.objects.filter(user=self.user).all():
+            m.roles.clear()
+            m.save()
+
+        # Create a new role, which will have the checking permission
+        role = Role.objects.get_or_create(name="β-tester")[0]
+        role.permissions.clear()
+        role.save()
+        membership = Membership.objects.get_or_create(user=self.user, club=Club.objects.get(name="BDE"))[0]
+        membership.roles.set([role])
+        membership.save()
+
+        # Ensure that the access to the object is forbidden without permission
+        resp = self.client.get(url + f"{obj.pk}/")
+        self.assertEqual(resp.status_code, 404, f"Mysterious access to {url}{obj.pk}/ for {obj}")
+
+        obj.refresh_from_db()
+
+        # There are problems with polymorphism
+        if isinstance(obj, Note) and hasattr(obj, "note_ptr"):
+            obj = obj.note_ptr
+
+        mask = PermissionMask.objects.get(rank=0)
+
+        for field in obj._meta.fields:
+            # Build permission query
+            value = self.get_value(obj, field.name)
+            if isinstance(value, date) or isinstance(value, datetime):
+                value = value.isoformat()
+            elif isinstance(value, ImageFieldFile):
+                value = value.name
+            query = json.dumps({field.name: value})
+
+            # Create sample permission
+            permission = Permission.objects.get_or_create(
+                model=ContentType.objects.get_for_model(obj._meta.model),
+                query=query,
+                mask=mask,
+                type="view",
+                permanent=False,
+                description=f"Can view {obj._meta.verbose_name}",
+            )[0]
+            role.permissions.set([permission])
+            role.save()
+
+            # Check that the access is possible
+            resp = self.client.get(url + f"{obj.pk}/")
+            self.assertEqual(resp.status_code, 200, f"Permission {permission.query} is not working "
+                                                    f"for the model {obj._meta.verbose_name}")
+
+        # Restore rights
+        self.user.is_superuser = True
+        self.user.save()
+        sess = self.client.session
+        sess["permission_mask"] = 42
+        sess.save()
+
     @staticmethod
     def get_value(obj, key: str):
         """
diff --git a/apps/member/tests/test_memberships.py b/apps/member/tests/test_memberships.py
index 39ee98d8..1bbae1c7 100644
--- a/apps/member/tests/test_memberships.py
+++ b/apps/member/tests/test_memberships.py
@@ -432,10 +432,20 @@ class TestMemberAPI(TestAPI):
         self.membership.roles.add(Role.objects.get(name="Bureau de club"))
         self.membership.save()
 
-    def test_member_api(self):
+    def test_club_api(self):
         """
-        Load API pages for the member app and test all filters
+        Load Club API page and test all filters and permissions
         """
         self.check_viewset(ClubViewSet, "/api/members/club/")
+
+    def test_profile_api(self):
+        """
+        Load Profile API page and test all filters and permissions
+        """
         self.check_viewset(ProfileViewSet, "/api/members/profile/")
+
+    def test_membership_api(self):
+        """
+        Load Membership API page and test all filters and permissions
+        """
         self.check_viewset(MembershipViewSet, "/api/members/membership/")
diff --git a/apps/note/api/views.py b/apps/note/api/views.py
index 4ac6ffbf..517450c7 100644
--- a/apps/note/api/views.py
+++ b/apps/note/api/views.py
@@ -15,7 +15,7 @@ from permission.backends import PermissionBackend
 
 from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer,\
     TemplateCategorySerializer, TransactionTemplateSerializer, TransactionPolymorphicSerializer
-from ..models.notes import Note, Alias
+from ..models.notes import Note, Alias, NoteUser, NoteClub, NoteSpecial
 from ..models.transactions import TransactionTemplate, Transaction, TemplateCategory
 
 
@@ -40,7 +40,12 @@ class NotePolymorphicViewSet(ReadProtectedModelViewSet):
         Parse query and apply filters.
         :return: The filtered set of requested notes
         """
-        queryset = super().get_queryset().distinct()
+        user = self.request.user
+        get_current_session().setdefault("permission_mask", 42)
+        queryset = self.queryset.filter(PermissionBackend.filter_queryset(user, Note, "view")
+                                        | PermissionBackend.filter_queryset(user, NoteUser, "view")
+                                        | PermissionBackend.filter_queryset(user, NoteClub, "view")
+                                        | PermissionBackend.filter_queryset(user, NoteSpecial, "view")).distinct()
 
         alias = self.request.query_params.get("alias", ".*")
         queryset = queryset.filter(
diff --git a/apps/note/tests/test_transactions.py b/apps/note/tests/test_transactions.py
index 7e999cca..0626c453 100644
--- a/apps/note/tests/test_transactions.py
+++ b/apps/note/tests/test_transactions.py
@@ -399,13 +399,38 @@ class TestNoteAPI(TestAPI):
             description="Test template",
         )
 
-    def test_note_api(self):
+    def test_alias_api(self):
         """
-        Load API pages for the note app and test all filters
+        Load Alias API page and test all filters and permissions
         """
         self.check_viewset(AliasViewSet, "/api/note/alias/")
+
+    def test_consumer_api(self):
+        """
+        Load Consumer API page and test all filters and permissions
+        """
         self.check_viewset(ConsumerViewSet, "/api/note/consumer/")
+
+    def test_note_api(self):
+        """
+        Load Note API page and test all filters and permissions
+        """
         self.check_viewset(NotePolymorphicViewSet, "/api/note/note/")
+
+    def test_template_category_api(self):
+        """
+        Load TemplateCategory API page and test all filters and permissions
+        """
         self.check_viewset(TemplateCategoryViewSet, "/api/note/transaction/category/")
+
+    def test_transaction_template_api(self):
+        """
+        Load TemplateTemplate API page and test all filters and permissions
+        """
         self.check_viewset(TransactionTemplateViewSet, "/api/note/transaction/template/")
+
+    def test_transaction_api(self):
+        """
+        Load Transaction API page and test all filters and permissions
+        """
         self.check_viewset(TransactionViewSet, "/api/note/transaction/transaction/")
diff --git a/apps/permission/decorators.py b/apps/permission/decorators.py
index 8ab35697..11edac43 100644
--- a/apps/permission/decorators.py
+++ b/apps/permission/decorators.py
@@ -1,6 +1,6 @@
 # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
 # SPDX-License-Identifier: GPL-3.0-or-later
-
+import sys
 from functools import lru_cache
 from time import time
 
@@ -38,6 +38,10 @@ def memoize(f):
 
         nonlocal last_collect
 
+        if "test" in sys.argv:
+            # In a test environment, don't memoize permissions
+            return f(*args, **kwargs)
+
         if time() - last_collect > 60:
             # Clear cache
             collect()
diff --git a/apps/treasury/tests/test_treasury.py b/apps/treasury/tests/test_treasury.py
index fba9c447..e51054b6 100644
--- a/apps/treasury/tests/test_treasury.py
+++ b/apps/treasury/tests/test_treasury.py
@@ -453,12 +453,32 @@ class TestTreasuryAPI(TestAPI):
         self.kfet_membership._soge = True
         self.kfet_membership.save()
 
-    def test_treasury_api(self):
+    def test_invoice_api(self):
         """
-        Load API pages for the treasury app and test all filters
+        Load Invoice API page and test all filters and permissions
         """
         self.check_viewset(InvoiceViewSet, "/api/treasury/invoice/")
+
+    def test_product_api(self):
+        """
+        Load Product API page and test all filters and permissions
+        """
         self.check_viewset(ProductViewSet, "/api/treasury/product/")
+
+    def test_remittance_api(self):
+        """
+        Load Remittance API page and test all filters and permissions
+        """
         self.check_viewset(RemittanceViewSet, "/api/treasury/remittance/")
+
+    def test_remittance_type_api(self):
+        """
+        Load RemittanceType API page and test all filters and permissions
+        """
         self.check_viewset(RemittanceTypeViewSet, "/api/treasury/remittance_type/")
+
+    def test_sogecredit_api(self):
+        """
+        Load SogeCredit API page and test all filters and permissions
+        """
         self.check_viewset(SogeCreditViewSet, "/api/treasury/soge_credit/")
diff --git a/apps/wei/tests/test_wei_registration.py b/apps/wei/tests/test_wei_registration.py
index 07564bf6..92ceb289 100644
--- a/apps/wei/tests/test_wei_registration.py
+++ b/apps/wei/tests/test_wei_registration.py
@@ -527,7 +527,7 @@ class TestWEIRegistration(TestCase):
         sess["permission_mask"] = 0
         sess.save()
         response = self.client.get(reverse("wei:wei_update_registration", kwargs=dict(pk=self.registration.pk)))
-        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.status_code, 403)
         sess["permission_mask"] = 42
         sess.save()
 
@@ -869,13 +869,38 @@ class TestWeiAPI(TestAPI):
         self.membership.roles.add(WEIRole.objects.last())
         self.membership.save()
 
-    def test_wei_api(self):
+    def test_weiclub_api(self):
         """
-        Load API pages for the treasury app and test all filters
+        Load WEI API page and test all filters and permissions
         """
         self.check_viewset(WEIClubViewSet, "/api/wei/club/")
+
+    def test_wei_bus_api(self):
+        """
+        Load Bus API page and test all filters and permissions
+        """
         self.check_viewset(BusViewSet, "/api/wei/bus/")
+
+    def test_wei_team_api(self):
+        """
+        Load BusTeam API page and test all filters and permissions
+        """
         self.check_viewset(BusTeamViewSet, "/api/wei/team/")
+
+    def test_weirole_api(self):
+        """
+        Load WEIRole API page and test all filters and permissions
+        """
         self.check_viewset(WEIRoleViewSet, "/api/wei/role/")
+
+    def test_weiregistration_api(self):
+        """
+        Load WEIRegistration API page and test all filters and permissions
+        """
         self.check_viewset(WEIRegistrationViewSet, "/api/wei/registration/")
+
+    def test_weimembership_api(self):
+        """
+        Load WEIMembership API page and test all filters and permissions
+        """
         self.check_viewset(WEIMembershipViewSet, "/api/wei/membership/")
-- 
GitLab