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/")