Skip to content

Commit

Permalink
Add guest user permission for post apis.
Browse files Browse the repository at this point in the history
  • Loading branch information
Rup-Narayan-Rajbanshi committed Jul 12, 2024
1 parent 777c3a3 commit 82b123b
Show file tree
Hide file tree
Showing 18 changed files with 207 additions and 36 deletions.
9 changes: 5 additions & 4 deletions api/drf_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -855,7 +856,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)
Expand All @@ -864,7 +865,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)
Expand Down Expand Up @@ -900,7 +901,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"
)
Expand Down Expand Up @@ -1331,7 +1332,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
Expand Down
31 changes: 31 additions & 0 deletions api/migrations/0211_profile_limit_access_to_guest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Generated by Django 4.2.13 on 2024-07-01 09:17

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=False,
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",
),
),
migrations.AlterField(
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",
),
),
]
6 changes: 6 additions & 0 deletions api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1841,6 +1841,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")
Expand Down
2 changes: 2 additions & 0 deletions api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1701,6 +1701,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
Expand All @@ -1712,6 +1713,7 @@ class Meta:
"is_per_admin_for_regions",
"is_per_admin_for_countries",
"user_countries_regions",
"limit_access_to_guest",
)

@staticmethod
Expand Down
81 changes: 80 additions & 1 deletion api/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand Down
3 changes: 2 additions & 1 deletion api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion deployments/drf_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -442,7 +443,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]


Expand Down
11 changes: 11 additions & 0 deletions deployments/factories/user.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
import factory
from django.contrib.auth import get_user_model

from api.models import Profile


class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = get_user_model()

username = factory.Sequence(lambda n: "user_%d" % n)
email = factory.Sequence(lambda n: "user_%[email protected]" % 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
19 changes: 10 additions & 9 deletions dref/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
DrefShareUserSerializer,
MiniDrefSerializer,
)
from main.permissions import DenyGuestUserMutationPermission


def filter_dref_queryset_by_user_access(user, queryset):
Expand All @@ -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):
Expand All @@ -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()
Expand All @@ -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):
Expand Down Expand Up @@ -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()
Expand All @@ -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
Expand All @@ -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()
Expand All @@ -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):
Expand All @@ -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
Expand Down Expand Up @@ -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):
Expand Down
5 changes: 3 additions & 2 deletions flash_update/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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)
Expand Down
Loading

0 comments on commit 82b123b

Please sign in to comment.