diff --git a/api/drf_views.py b/api/drf_views.py index f75c0e274..9cb166695 100644 --- a/api/drf_views.py +++ b/api/drf_views.py @@ -59,6 +59,7 @@ from deployments.models import Personnel from main.enums import GlobalEnumSerializer, get_enum_values from main.filters import NullsLastOrderingFilter +from main.permissions import DenyGuestUserMutationPermission from main.utils import is_tableau from per.models import Overview from per.serializers import CountryLatestOverviewSerializer @@ -870,7 +871,7 @@ def get_serializer_class(self): class ProfileViewset(viewsets.ModelViewSet): serializer_class = ProfileSerializer authentication_classes = (TokenAuthentication,) - permission_classes = (IsAuthenticated,) + permission_classes = (IsAuthenticated, DenyGuestUserMutationPermission) def get_queryset(self): return Profile.objects.filter(user=self.request.user) @@ -879,7 +880,7 @@ def get_queryset(self): class UserViewset(viewsets.ModelViewSet): serializer_class = UserSerializer authentication_classes = (TokenAuthentication,) - permission_classes = (IsAuthenticated,) + permission_classes = (IsAuthenticated, DenyGuestUserMutationPermission) def get_queryset(self): return User.objects.filter(pk=self.request.user.pk) @@ -915,7 +916,7 @@ class FieldReportViewset(ReadOnlyVisibilityViewsetMixin, viewsets.ModelViewSet): ) # for /docs ordering_fields = ("summary", "event", "dtype", "created_at", "updated_at") filterset_class = FieldReportFilter - authentication_class = [IsAuthenticated] + permission_classes = [IsAuthenticated, DenyGuestUserMutationPermission] queryset = FieldReport.objects.select_related("dtype", "event").prefetch_related( "actions_taken", "actions_taken__actions", "countries", "districts", "regions" ) @@ -1346,7 +1347,7 @@ def get(self, _): class ExportViewSet(viewsets.ModelViewSet): serializer_class = ExportSerializer - permission_classes = [IsAuthenticated] + permission_classes = [IsAuthenticated, DenyGuestUserMutationPermission] def get_queryset(self): user = self.request.user diff --git a/api/migrations/0211_profile_limit_access_to_guest.py b/api/migrations/0211_profile_limit_access_to_guest.py new file mode 100644 index 000000000..b01836046 --- /dev/null +++ b/api/migrations/0211_profile_limit_access_to_guest.py @@ -0,0 +1,22 @@ +# Generated by Django 4.2.13 on 2024-07-12 06:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("api", "0210_profile_accepted_montandon_license_terms"), + ] + + operations = [ + migrations.AddField( + model_name="profile", + name="limit_access_to_guest", + field=models.BooleanField( + default=True, + help_text="If this value is set to true, the user is treated as a guest user regardless of any other permissions they may have, thereby depriving them of all non-guest user permissions.", + verbose_name="limit access to guest user permissions", + ), + ), + ] diff --git a/api/models.py b/api/models.py index 576266cc8..a26a01b30 100644 --- a/api/models.py +++ b/api/models.py @@ -1850,6 +1850,12 @@ class OrgTypes(models.TextChoices): phone_number = models.CharField(verbose_name=_("phone number"), blank=True, null=True, max_length=100) last_frontend_login = models.DateTimeField(verbose_name=_("last frontend login"), null=True, blank=True) accepted_montandon_license_terms = models.BooleanField(verbose_name=_("has accepted montandon license terms?"), default=False) + limit_access_to_guest = models.BooleanField( + help_text="If this value is set to true, the user is treated as a guest user regardless of any other permissions \ + they may have, thereby depriving them of all non-guest user permissions.", + verbose_name=_("limit access to guest user permissions"), + default=True, + ) class Meta: verbose_name = _("user profile") diff --git a/api/serializers.py b/api/serializers.py index e0f9a1b05..58fa6e67e 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -1703,6 +1703,7 @@ class UserMeSerializer(UserSerializer): is_per_admin_for_regions = serializers.SerializerMethodField() is_per_admin_for_countries = serializers.SerializerMethodField() user_countries_regions = serializers.SerializerMethodField() + limit_access_to_guest = serializers.BooleanField(read_only=True, source="profile.limit_access_to_guest") class Meta: model = User @@ -1714,6 +1715,7 @@ class Meta: "is_per_admin_for_regions", "is_per_admin_for_countries", "user_countries_regions", + "limit_access_to_guest", ) @staticmethod diff --git a/api/test_views.py b/api/test_views.py index a4bf987ee..fbc217b2f 100644 --- a/api/test_views.py +++ b/api/test_views.py @@ -10,9 +10,88 @@ EventFeaturedDocumentFactory, EventLinkFactory, ) +from api.models import Profile +from deployments.factories.user import UserFactory from main.test_case import APITestCase, SnapshotTestCase +class GuestUserPermissionTest(APITestCase): + def setUp(self): + # Create guest user + self.guest_user = User.objects.create(username="guest") + guest_profile = Profile.objects.get(user=self.guest_user) + guest_profile.limit_access_to_guest = True + guest_profile.save() + + # Create go user + self.go_user = User.objects.create(username="go-user") + go_user_profile = Profile.objects.get(user=self.go_user) + go_user_profile.limit_access_to_guest = False + go_user_profile.save() + + def test_guest_user_permission(self): + body = {} + guest_apis = [ + "/api/v2/add_subscription/", + "/api/v2/del_subscription/", + "/api/v2/external-token/", + ] + + go_apis = [ + "/api/v2/dref/", + "/api/v2/dref-final-report/", + "/api/v2/dref-final-report/{id}/publish/", + "/api/v2/dref-op-update/", + "/api/v2/dref-op-update/{id}/publish/", + "/api/v2/dref-share/", + "/api/v2/dref/{id}/publish/", + "/api/v2/flash-update/", + "/api/v2/flash-update-file/multiple/", + "/api/v2/local-units/", + "/api/v2/local-units/{id}/validate/", + "/api/v2/pdf-export/", + "/api/v2/per-assessment/", + "/api/v2/per-document-upload/", + "/api/v2/per-file/multiple/", + "/api/v2/per-prioritization/", + "/api/v2/per-work-plan/", + "/api/v2/project/", + "/api/v2/dref-files/", + "/api/v2/dref-files/multiple/", + "/api/v2/field-report/", + "/api/v2/flash-update-file/", + "/api/v2/per-file/", + "/api/v2/share-flash-update/", + "/api/v2/add_cronjob_log/", + "/api/v2/profile/", + "/api/v2/subscription/", + "/api/v2/user/", + ] + + go_apis_req_additional_perm = [ + "/api/v2/ops-learning/", + "/api/v2/per-overview/", + "/api/v2/user/{id}/accepted_license_terms/", + "/api/v2/language/{id}/bulk-action/", + ] + + self.authenticate(user=self.guest_user) + for api_url in go_apis + go_apis_req_additional_perm: + response = self.client.post(api_url, json=body).json() + self.assertIn(response["error_code"], [401, 403]) + + for api_url in guest_apis: + response = self.client.post(api_url, json=body).json() + error_code = response.get("error_code", None) + self.assertNotIn(error_code, [403, 401]) + + self.authenticate(user=self.go_user) + for api_url in go_apis: + response = self.client.post(api_url, json=body).json() + error_code = response.get("error_code", None) + self.assertNotIn(error_code, [403, 401]) + + class AuthTokenTest(APITestCase): def setUp(self): user = User.objects.create(username="jo") @@ -78,7 +157,7 @@ class FieldReportTest(APITestCase): fixtures = ["DisasterTypes", "Actions"] def test_create_and_update(self): - user = User.objects.create(username="jo") + user = UserFactory(username="jo") region = models.Region.objects.create(name=1) country1 = models.Country.objects.create(name="abc", region=region) country2 = models.Country.objects.create(name="xyz") diff --git a/api/views.py b/api/views.py index 78d7e6db0..5fb1d214d 100644 --- a/api/views.py +++ b/api/views.py @@ -43,6 +43,7 @@ Statuses, ) from flash_update.models import FlashUpdate +from main.permissions import DenyGuestUserMutationPermission from notifications.models import Subscription, SurgeAlert from notifications.notification import send_notification from registrations.models import Pending, Recovery @@ -976,7 +977,7 @@ def post(self, request): class AddCronJobLog(APIView): authentication_classes = (authentication.TokenAuthentication,) - permissions_classes = (permissions.IsAuthenticated,) + permission_classes = [permissions.IsAuthenticated, DenyGuestUserMutationPermission] def post(self, request): errors, created = CronJob.sync_cron(request.data) diff --git a/deployments/drf_views.py b/deployments/drf_views.py index f04268e49..c4abd3563 100644 --- a/deployments/drf_views.py +++ b/deployments/drf_views.py @@ -23,6 +23,7 @@ from api.models import Country, Region from api.view_filters import ListFilter from api.visibility_class import ReadOnlyVisibilityViewsetMixin +from main.permissions import DenyGuestUserMutationPermission from main.serializers import CsvListMixin from main.utils import is_tableau @@ -455,7 +456,7 @@ def get_permissions(self): if self.action in ["list", "retrieve"]: permission_classes = [] else: - permission_classes = [IsAuthenticated] + permission_classes = [IsAuthenticated, DenyGuestUserMutationPermission] return [permission() for permission in permission_classes] diff --git a/deployments/factories/user.py b/deployments/factories/user.py index 8ccecf722..9da30434c 100644 --- a/deployments/factories/user.py +++ b/deployments/factories/user.py @@ -1,6 +1,8 @@ import factory from django.contrib.auth import get_user_model +from api.models import Profile + class UserFactory(factory.django.DjangoModelFactory): class Meta: @@ -8,3 +10,12 @@ class Meta: username = factory.Sequence(lambda n: "user_%d" % n) email = factory.Sequence(lambda n: "user_%d@ifrc.org" % n) + + @factory.post_generation + def create_profile(obj, create, extracted, **kwargs): + if create: + profile = Profile.objects.get(user=obj) + profile.limit_access_to_guest = False + profile.save(update_fields=["limit_access_to_guest"]) + # Set new profile to the user object + obj.profile = profile diff --git a/dref/views.py b/dref/views.py index 56bd5888c..ffb538c92 100644 --- a/dref/views.py +++ b/dref/views.py @@ -35,6 +35,7 @@ DrefShareUserSerializer, MiniDrefSerializer, ) +from main.permissions import DenyGuestUserMutationPermission def filter_dref_queryset_by_user_access(user, queryset): @@ -58,7 +59,7 @@ def filter_dref_queryset_by_user_access(user, queryset): class DrefViewSet(RevisionMixin, viewsets.ModelViewSet): serializer_class = DrefSerializer - permission_classes = [permissions.IsAuthenticated] + permission_classes = [permissions.IsAuthenticated, DenyGuestUserMutationPermission] filterset_class = DrefFilter def get_queryset(self): @@ -75,7 +76,7 @@ def get_queryset(self): url_path="publish", methods=["post"], serializer_class=DrefSerializer, - permission_classes=[permissions.IsAuthenticated, PublishDrefPermission], + permission_classes=[permissions.IsAuthenticated, PublishDrefPermission, DenyGuestUserMutationPermission], ) def get_published(self, request, pk=None, version=None): dref = self.get_object() @@ -88,7 +89,7 @@ def get_published(self, request, pk=None, version=None): class DrefOperationalUpdateViewSet(RevisionMixin, viewsets.ModelViewSet): serializer_class = DrefOperationalUpdateSerializer - permission_classes = [permissions.IsAuthenticated] + permission_classes = [permissions.IsAuthenticated, DenyGuestUserMutationPermission] filterset_class = DrefOperationalUpdateFilter def get_queryset(self): @@ -122,7 +123,7 @@ def get_queryset(self): url_path="publish", methods=["post"], serializer_class=DrefOperationalUpdateSerializer, - permission_classes=[permissions.IsAuthenticated, PublishDrefPermission], + permission_classes=[permissions.IsAuthenticated, PublishDrefPermission, DenyGuestUserMutationPermission], ) def get_published(self, request, pk=None, version=None): operational_update = self.get_object() @@ -135,7 +136,7 @@ def get_published(self, request, pk=None, version=None): class DrefFinalReportViewSet(RevisionMixin, viewsets.ModelViewSet): serializer_class = DrefFinalReportSerializer - permission_classes = [permissions.IsAuthenticated] + permission_classes = [permissions.IsAuthenticated, DenyGuestUserMutationPermission] def get_queryset(self): user = self.request.user @@ -154,7 +155,7 @@ def get_queryset(self): url_path="publish", methods=["post"], serializer_class=DrefFinalReportSerializer, - permission_classes=[permissions.IsAuthenticated, PublishDrefPermission], + permission_classes=[permissions.IsAuthenticated, PublishDrefPermission, DenyGuestUserMutationPermission], ) def get_published(self, request, pk=None, version=None): field_report = self.get_object() @@ -171,7 +172,7 @@ def get_published(self, request, pk=None, version=None): class DrefFileViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, viewsets.GenericViewSet): - permission_class = [permissions.IsAuthenticated] + permission_classes = [permissions.IsAuthenticated, DenyGuestUserMutationPermission] serializer_class = DrefFileSerializer def get_queryset(self): @@ -184,7 +185,7 @@ def get_queryset(self): detail=False, url_path="multiple", methods=["POST"], - permission_classes=[permissions.IsAuthenticated], + permission_classes=[permissions.IsAuthenticated, DenyGuestUserMutationPermission], ) def multiple_file(self, request, pk=None, version=None): # converts querydict to original dict @@ -225,7 +226,7 @@ def get_queryset(self): class DrefShareView(views.APIView): - permission_classes = [permissions.IsAuthenticated] + permission_classes = [permissions.IsAuthenticated, DenyGuestUserMutationPermission] @extend_schema(request=AddDrefUserSerializer, responses=None) def post(self, request): diff --git a/flash_update/test_views.py b/flash_update/test_views.py index 15e914fd4..61393cca7 100644 --- a/flash_update/test_views.py +++ b/flash_update/test_views.py @@ -5,6 +5,7 @@ from django.contrib.auth.models import User import api.models as models +from deployments.factories.user import UserFactory from flash_update.factories import ( DonorFactory, DonorGroupFactory, @@ -127,7 +128,7 @@ def test_create_and_update(self, send_flash_update_email): self.assertEqual(updated.actions_taken_flash.count(), 2) def test_patch(self): - user = User.objects.create(username="test_abc") + user = UserFactory(username="test_abc") self.client.force_authenticate(user=user) with self.capture_on_commit_callbacks(execute=True): response1 = self.client.post("/api/v2/flash-update/", self.body, format="json").json() @@ -203,7 +204,7 @@ def test_validate_country_district(self): self.assert_400(response) def test_upload_file(self): - user = User.objects.create(username="flash_user") + user = UserFactory(username="flash_user") url = "/api/v2/flash-update-file/" data = {"file": open(self.file, "rb"), "caption": "test file"} self.client.force_authenticate(user=user) diff --git a/flash_update/views.py b/flash_update/views.py index cfad401fa..ebfe29207 100644 --- a/flash_update/views.py +++ b/flash_update/views.py @@ -14,6 +14,7 @@ from rest_framework.response import Response from api.serializers import ActionSerializer +from main.permissions import DenyGuestUserMutationPermission from .filter_set import FlashUpdateFilter from .models import ( @@ -38,7 +39,7 @@ class FlashUpdateViewSet(viewsets.ModelViewSet): serializer_class = FlashUpdateSerializer - permission_classes = [permissions.IsAuthenticated] + permission_classes = [permissions.IsAuthenticated, DenyGuestUserMutationPermission] filterset_class = FlashUpdateFilter def get_queryset(self): @@ -68,7 +69,7 @@ def get_queryset(self): class FlashUpdateFileViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, viewsets.GenericViewSet): - permission_class = [permissions.IsAuthenticated] + permission_classes = [permissions.IsAuthenticated, DenyGuestUserMutationPermission] serializer_class = FlashGraphicMapSerializer def get_queryset(self): @@ -79,7 +80,7 @@ def get_queryset(self): detail=False, url_path="multiple", methods=["POST"], - permission_classes=[permissions.IsAuthenticated], + permission_classes=[permissions.IsAuthenticated, DenyGuestUserMutationPermission], ) def multiple_file(self, request, pk=None, version=None): files = [files[0] for files in dict((request.data).lists()).values()] @@ -112,7 +113,7 @@ class DonorsViewSet(viewsets.ReadOnlyModelViewSet): class ShareFlashUpdateViewSet(mixins.CreateModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet): queryset = FlashUpdateShare.objects.all() serializer_class = ShareFlashUpdateSerializer - permission_class = [permissions.IsAuthenticated] + permission_classes = [permissions.IsAuthenticated, DenyGuestUserMutationPermission] class ExportFlashUpdateView(views.APIView): diff --git a/lang/tests.py b/lang/tests.py index d62b8dff0..76eb309eb 100644 --- a/lang/tests.py +++ b/lang/tests.py @@ -6,6 +6,7 @@ from django.core import management from django.test import override_settings +from deployments.factories.user import UserFactory from lang.translation import IfrcTranslator from main.test_case import APITestCase @@ -185,7 +186,7 @@ def test_user_me(self): ) def test_lang_api_permissions(self): - user = User.objects.create_user( + user = UserFactory( username="user@test.com", first_name="User", last_name="Toot", diff --git a/lang/views.py b/lang/views.py index 03e26f8fd..e34e7c801 100644 --- a/lang/views.py +++ b/lang/views.py @@ -9,6 +9,8 @@ from rest_framework.authentication import TokenAuthentication from rest_framework.decorators import action as djaction +from main.permissions import DenyGuestUserMutationPermission + from .models import String from .permissions import LangStringPermission from .serializers import ( @@ -24,7 +26,7 @@ class LanguageViewSet(viewsets.ViewSet): # TODO: Cache retrive response to file authentication_classes = (TokenAuthentication,) - permission_classes = (LangStringPermission,) + permission_classes = (LangStringPermission, DenyGuestUserMutationPermission) lookup_url_kwarg = "pk" @extend_schema(request=None, responses=LanguageListSerializer) diff --git a/local_units/views.py b/local_units/views.py index 7affa78a4..3a6dc0805 100644 --- a/local_units/views.py +++ b/local_units/views.py @@ -33,6 +33,7 @@ PrivateLocalUnitDetailSerializer, PrivateLocalUnitSerializer, ) +from main.permissions import DenyGuestUserMutationPermission class PrivateLocalUnitViewSet(viewsets.ModelViewSet): @@ -47,7 +48,7 @@ class PrivateLocalUnitViewSet(viewsets.ModelViewSet): "local_branch_name", "english_branch_name", ) - permission_classes = [permissions.IsAuthenticated, IsAuthenticatedForLocalUnit] + permission_classes = [permissions.IsAuthenticated, IsAuthenticatedForLocalUnit, DenyGuestUserMutationPermission] def get_serializer_class(self): if self.action == "list": @@ -63,7 +64,7 @@ def destroy(self, request, *args, **kwargs): url_path="validate", methods=["post"], serializer_class=PrivateLocalUnitSerializer, - permission_classes=[permissions.IsAuthenticated, ValidateLocalUnitPermission], + permission_classes=[permissions.IsAuthenticated, ValidateLocalUnitPermission, DenyGuestUserMutationPermission], ) def get_validate(self, request, pk=None, version=None): local_unit = self.get_object() diff --git a/main/permissions.py b/main/permissions.py index f0ae7c110..bae369460 100644 --- a/main/permissions.py +++ b/main/permissions.py @@ -10,3 +10,31 @@ def has_permission(self, request, view): def has_object_permission(self, request, view, obj): return self.has_permission(request, view) + + +class DenyGuestUserMutationPermission(permissions.BasePermission): + """ + Custom permission to deny mutation actions for logged-in guest users. + + This permission class allows all safe (read-only) operations but restricts + any mutation (write, update, delete) operations if the user is a guest. + """ + + def _has_permission(self, request, view): + # Allow all safe methods (GET, HEAD, OPTIONS) which are non-mutating. + if request.method in permissions.SAFE_METHODS: + return True + + # For mutation methods (POST, PUT, DELETE, etc.): + # Check if the user is authenticated. + if not bool(request.user and request.user.is_authenticated): + # Deny access if the user is not authenticated. + return False + + return not request.user.profile.limit_access_to_guest + + def has_permission(self, request, view): + return self._has_permission(request, view) + + def has_object_permission(self, request, view, obj): + return self._has_permission(request, view) diff --git a/notifications/drf_views.py b/notifications/drf_views.py index 2447338cb..662468068 100644 --- a/notifications/drf_views.py +++ b/notifications/drf_views.py @@ -8,6 +8,7 @@ from deployments.models import MolnixTag from main.filters import CharInFilter +from main.permissions import DenyGuestUserMutationPermission from .models import Subscription, SurgeAlert from .serializers import ( # UnauthenticatedSurgeAlertSerializer, @@ -87,7 +88,7 @@ def get_serializer_class(self): class SubscriptionViewset(viewsets.ModelViewSet): serializer_class = SubscriptionSerializer authentication_classes = (TokenAuthentication,) - permission_classes = (IsAuthenticated,) + permission_classes = (IsAuthenticated, DenyGuestUserMutationPermission) search_fields = ("user__username", "rtype") # for /docs def get_queryset(self): diff --git a/per/drf_views.py b/per/drf_views.py index 94199914f..851fe4016 100644 --- a/per/drf_views.py +++ b/per/drf_views.py @@ -19,6 +19,7 @@ from api.models import Country from deployments.models import SectorTag +from main.permissions import DenyGuestUserMutationPermission from main.utils import SpreadSheetContentNegotiation from per.filter_set import ( PerDocumentFilter, @@ -234,7 +235,7 @@ def get_queryset(self): class PerOverviewViewSet(viewsets.ModelViewSet): serializer_class = PerOverviewSerializer - permission_classes = [IsAuthenticated, PerPermission] + permission_classes = [IsAuthenticated, PerPermission, DenyGuestUserMutationPermission] filterset_class = PerOverviewFilter ordering_fields = "__all__" get_request_user_regions = RegionRestrictedAdmin.get_request_user_regions @@ -506,7 +507,7 @@ def get(self, request, pk, format=None): class NewPerWorkPlanViewSet(viewsets.ModelViewSet): - permission_classes = (IsAuthenticated, PerGeneralPermission) + permission_classes = (IsAuthenticated, PerGeneralPermission, DenyGuestUserMutationPermission) queryset = PerWorkPlan.objects.all() serializer_class = PerWorkPlanSerializer filterset_class = PerWorkPlanFilter @@ -523,7 +524,7 @@ class FormPrioritizationViewSet(viewsets.ModelViewSet): serializer_class = FormPrioritizationSerializer queryset = FormPrioritization.objects.all() filterset_class = PerPrioritizationFilter - permission_classes = (IsAuthenticated, PerGeneralPermission) + permission_classes = (IsAuthenticated, PerGeneralPermission, DenyGuestUserMutationPermission) ordering_fields = "__all__" @@ -574,7 +575,7 @@ def get_queryset(self): class FormAssessmentViewSet(viewsets.ModelViewSet): serializer_class = PerAssessmentSerializer - permission_classes = [permissions.IsAuthenticated, PerGeneralPermission] + permission_classes = [permissions.IsAuthenticated, PerGeneralPermission, DenyGuestUserMutationPermission] ordering_fields = "__all__" def get_queryset(self): @@ -590,7 +591,7 @@ def get_queryset(self): class PerFileViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, viewsets.GenericViewSet): - permission_class = [permissions.IsAuthenticated] + permission_classes = [permissions.IsAuthenticated, DenyGuestUserMutationPermission] serializer_class = PerFileSerializer def get_queryset(self): @@ -603,7 +604,7 @@ def get_queryset(self): detail=False, url_path="multiple", methods=["POST"], - permission_classes=[permissions.IsAuthenticated], + permission_classes=[permissions.IsAuthenticated, DenyGuestUserMutationPermission], ) def multiple_file(self, request, pk=None, version=None): # converts querydict to original dict @@ -707,7 +708,7 @@ class OpsLearningViewset(viewsets.ModelViewSet): """ queryset = OpsLearning.objects.all() - permission_classes = [OpsLearningPermission] + permission_classes = [DenyGuestUserMutationPermission, OpsLearningPermission] filterset_class = OpsLearningFilter search_fields = ( "learning", @@ -809,7 +810,7 @@ class PerDocumentUploadViewSet(viewsets.ModelViewSet): queryset = PerDocumentUpload.objects.all() serializer_class = PerDocumentUploadSerializer filterset_class = PerDocumentFilter - permission_classes = [permissions.IsAuthenticated, PerDocumentUploadPermission] + permission_classes = [permissions.IsAuthenticated, PerDocumentUploadPermission, DenyGuestUserMutationPermission] def get_queryset(self): queryset = super().get_queryset() diff --git a/registrations/views.py b/registrations/views.py index 751aff0e0..0a7586145 100644 --- a/registrations/views.py +++ b/registrations/views.py @@ -147,7 +147,9 @@ def get(self, request): class UserExternalTokenViewset(viewsets.ModelViewSet): serializer_class = UserExternalTokenSerializer - permission_classes = [permissions.IsAuthenticated] + permission_classes = [ + permissions.IsAuthenticated, + ] def get_queryset(self): return UserExternalToken.objects.filter(user=self.request.user)