diff --git a/backend/clubs/middleware.py b/backend/clubs/middleware.py new file mode 100644 index 000000000..1cb9beeb8 --- /dev/null +++ b/backend/clubs/middleware.py @@ -0,0 +1,27 @@ +from django.contrib.auth.models import User +from django.urls import resolve +from django.utils.functional import SimpleLazyObject + + +class LimitedPermissionsMiddleware: + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + current_url = getattr(resolve(request.path_info), "url_name", None) + + if ( + request.user.is_authenticated + and request.user.is_superuser + and request.session.get("limited_permissions", False) + and "limited-permissions" not in current_url + ): + limited_user = SimpleLazyObject( + lambda: User.objects.get(pk=request.user.pk) + ) + limited_user.is_superuser = False + limited_user.is_staff = False + request.user = limited_user + + response = self.get_response(request) + return response diff --git a/backend/clubs/urls.py b/backend/clubs/urls.py index 4a1006cad..839e0d966 100644 --- a/backend/clubs/urls.py +++ b/backend/clubs/urls.py @@ -24,6 +24,7 @@ FavoriteCalendarAPIView, FavoriteEventsAPIView, FavoriteViewSet, + LimitedPermissionsViewSet, MajorViewSet, MassInviteAPIView, MeetingZoomAPIView, @@ -134,6 +135,9 @@ ) router.register(r"booths", ClubBoothsViewSet, basename="club-booth") +router.register( + r"limitedpermissions", LimitedPermissionsViewSet, basename="limited-permissions" +) urlpatterns = [ path(r"settings/", UserUpdateAPIView.as_view(), name="settings-detail"), diff --git a/backend/clubs/views.py b/backend/clubs/views.py index c71dd1c65..a670bf122 100644 --- a/backend/clubs/views.py +++ b/backend/clubs/views.py @@ -7426,6 +7426,55 @@ def get_queryset(self): return ClubApprovalResponseTemplate.objects.all().order_by("-created_at") +class LimitedPermissionsViewSet(viewsets.ViewSet): + """ + Allows superusers to view the site from a student perspective. + """ + + permission_classes = [IsSuperuser] + + def list(self, request): + """ + Check whether limited permissions are enabled for the user's session. + --- + summary: Check Limited Permissions + responses: + "200": + content: + application/json: + schema: + type: object + properties: + limited_permissions: + type: boolean + --- + """ + return Response( + {"limited_permissions": request.session.get("limited_permissions", False)} + ) + + @action(detail=False, methods=["post"]) + def toggle(self, request, *args, **kwargs): + """ + Toggle limited permissions for the user's session. + --- + summary: Toggle Limited Permissions + responses: + "200": + content: + application/json: + schema: + type: object + properties: + limited_permissions: + type: boolean + --- + """ + new_state = not request.session.get("limited_permissions", False) + request.session["limited_permissions"] = new_state + return Response({"limited_permissions": new_state}, status=status.HTTP_200_OK) + + class ScriptExecutionView(APIView): """ View and execute Django management scripts using these endpoints. diff --git a/backend/pennclubs/settings/base.py b/backend/pennclubs/settings/base.py index 4c645ea94..ab5479481 100644 --- a/backend/pennclubs/settings/base.py +++ b/backend/pennclubs/settings/base.py @@ -63,6 +63,7 @@ "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", "simple_history.middleware.HistoryRequestMiddleware", + "clubs.middleware.LimitedPermissionsMiddleware", ] ROOT_URLCONF = "pennclubs.urls" diff --git a/backend/tests/clubs/test_views.py b/backend/tests/clubs/test_views.py index f005efd00..b76c7760b 100644 --- a/backend/tests/clubs/test_views.py +++ b/backend/tests/clubs/test_views.py @@ -2923,3 +2923,30 @@ def test_club_approval_response_templates(self): content_type="application/json", ) self.assertEqual(resp.status_code, 403) + + def test_limited_permissions(self): + """ + Test limited permissions (i.e. authenticated student view) for superusers + """ + + # Log in as superuser + self.client.force_login(self.user5) + + resp = self.client.get(reverse("limited-permissions-list")) + + self.assertEqual(resp.status_code, 200) + self.assertEqual(resp.json()["limited_permissions"], False) + + # Toggle limited permissions + resp = self.client.post(reverse("limited-permissions-toggle")) + self.assertEqual(resp.status_code, 200) + self.assertEqual(resp.json()["limited_permissions"], True) + + # Accessing a superuser-only route should now fail + resp = self.client.get(reverse("templates-list")) + self.assertEqual(resp.status_code, 403) + + # After toggling limited permissions, it should work again + self.client.post(reverse("limited-permissions-toggle")) + resp = self.client.get(reverse("templates-list")) + self.assertEqual(resp.status_code, 200)