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 10, 2024
1 parent 777c3a3 commit 3743bf3
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="User having this value as true explictly is treated as guest user.Despite of having all the permission, if this value is set to true, the user is deprived of all the non-guest user permission.",
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="User having this value as true explictly is treated as guest user.Despite of having all the permission, if this value is set to true, the user is deprived of all the non-guest user permission.",
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="User having this value as true explictly is treated as guest user.Despite of having all the \
permission, if this value is set to true, the user is deprived of all the non-guest user permission.",
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 3743bf3

Please sign in to comment.