factorised permissions and views

This commit is contained in:
faraphel 2023-12-04 19:49:54 +01:00
parent a731a03133
commit 93036887c4
3 changed files with 182 additions and 261 deletions

View file

@ -4,206 +4,49 @@ Permissions for the Palto project's API v1.
A permission describe which user is allowed to see and modify which objet with the API
"""
from typing import Type
from rest_framework import permissions
from Palto.Palto import models
class UserPermission(permissions.BasePermission):
# TODO: has_permission check for authentication
def permission_from_helper_class(model: Type[models.ModelPermissionHelper]) -> Type[permissions.BasePermission]:
"""
Create a permission class from a model if it implements ModelPermissionHelper.
This make creating permission easier to understand and less redundant.
"""
def has_permission(self, request, view) -> bool:
if request.method in permissions.SAFE_METHODS:
# for reading, allow everybody
return True
class Permission(permissions.BasePermission):
def has_permission(self, request, view) -> bool:
if request.method in permissions.SAFE_METHODS:
# for reading, allow everybody
return True
if models.User.can_user_create(request.user):
# for writing, only allowed users
return True
if model.can_user_create(request.user):
# for writing, only allowed users
return True
return False
return False
def has_object_permission(self, request, view, obj: models.User) -> bool:
if request.method in permissions.SAFE_METHODS:
# for reading, only allow if the user can see the object
return obj in models.User.all_visible_by_user(request.user)
def has_object_permission(self, request, view, obj: models.User) -> bool:
if request.method in permissions.SAFE_METHODS:
# for reading, only allow if the user can see the object
return obj in model.all_visible_by_user(request.user)
else:
# for writing, only allow if the user can edit the object
return obj in models.User.all_editable_by_user(request.user)
else:
# for writing, only allow if the user can edit the object
return obj in model.all_editable_by_user(request.user)
return Permission
class DepartmentPermission(permissions.BasePermission):
def has_permission(self, request, view) -> bool:
if request.method in permissions.SAFE_METHODS:
# for reading, allow everybody
return True
if models.Department.can_user_create(request.user):
# for writing, only allowed users
return True
return False
def has_object_permission(self, request, view, obj: models.User) -> bool:
if request.method in permissions.SAFE_METHODS:
# for reading, only allow if the user can see the object
return obj in models.Department.all_visible_by_user(request.user)
else:
# for writing, only allow if the user can edit the object
return obj in models.Department.all_editable_by_user(request.user)
class StudentGroupPermission(permissions.BasePermission):
def has_permission(self, request, view) -> bool:
if request.method in permissions.SAFE_METHODS:
# for reading, allow everybody
return True
if models.StudentGroup.can_user_create(request.user):
# for writing, only allowed users
return True
return False
def has_object_permission(self, request, view, obj: models.User) -> bool:
if request.method in permissions.SAFE_METHODS:
# for reading, only allow if the user can see the object
return obj in models.StudentGroup.all_visible_by_user(request.user)
else:
# for writing, only allow if the user can edit the object
return obj in models.StudentGroup.all_editable_by_user(request.user)
class TeachingUnitPermission(permissions.BasePermission):
def has_permission(self, request, view) -> bool:
if request.method in permissions.SAFE_METHODS:
# for reading, allow everybody
return True
if models.TeachingUnit.can_user_create(request.user):
# for writing, only allowed users
return True
return False
def has_object_permission(self, request, view, obj: models.User) -> bool:
if request.method in permissions.SAFE_METHODS:
# for reading, only allow if the user can see the object
return obj in models.TeachingUnit.all_visible_by_user(request.user)
else:
# for writing, only allow if the user can edit the object
return obj in models.TeachingUnit.all_editable_by_user(request.user)
class StudentCardPermission(permissions.BasePermission):
def has_permission(self, request, view) -> bool:
if request.method in permissions.SAFE_METHODS:
# for reading, allow everybody
return True
if models.StudentCard.can_user_create(request.user):
# for writing, only allowed users
return True
return False
def has_object_permission(self, request, view, obj: models.User) -> bool:
if request.method in permissions.SAFE_METHODS:
# for reading, only allow if the user can see the object
return obj in models.StudentCard.all_visible_by_user(request.user)
else:
# for writing, only allow if the user can edit the object
return obj in models.StudentCard.all_editable_by_user(request.user)
class TeachingSessionPermission(permissions.BasePermission):
def has_permission(self, request, view) -> bool:
if request.method in permissions.SAFE_METHODS:
# for reading, allow everybody
return True
if models.TeachingSession.can_user_create(request.user):
# for writing, only allowed users
return True
return False
def has_object_permission(self, request, view, obj: models.User) -> bool:
if request.method in permissions.SAFE_METHODS:
# for reading, only allow if the user can see the object
return obj in models.TeachingSession.all_visible_by_user(request.user)
else:
# for writing, only allow if the user can edit the object
return obj in models.TeachingSession.all_editable_by_user(request.user)
class AttendancePermission(permissions.BasePermission):
def has_permission(self, request, view) -> bool:
if request.method in permissions.SAFE_METHODS:
# for reading, allow everybody
return True
if models.Attendance.can_user_create(request.user):
# for writing, only allowed users
return True
return False
def has_object_permission(self, request, view, obj: models.User) -> bool:
if request.method in permissions.SAFE_METHODS:
# for reading, only allow if the user can see the object
return obj in models.Attendance.all_visible_by_user(request.user)
else:
# for writing, only allow if the user can edit the object
return obj in models.Attendance.all_editable_by_user(request.user)
class AbsencePermission(permissions.BasePermission):
def has_permission(self, request, view) -> bool:
if request.method in permissions.SAFE_METHODS:
# for reading, allow everybody
return True
if models.Absence.can_user_create(request.user):
# for writing, only allowed users
return True
return False
def has_object_permission(self, request, view, obj: models.User) -> bool:
if request.method in permissions.SAFE_METHODS:
# for reading, only allow if the user can see the object
return obj in models.Absence.all_visible_by_user(request.user)
else:
# for writing, only allow if the user can edit the object
return obj in models.Absence.all_editable_by_user(request.user)
class AbsenceAttachmentPermission(permissions.BasePermission):
def has_permission(self, request, view) -> bool:
if request.method in permissions.SAFE_METHODS:
# for reading, allow everybody
return True
if models.AbsenceAttachment.can_user_create(request.user):
# for writing, only allowed users
return True
return False
def has_object_permission(self, request, view, obj: models.User) -> bool:
if request.method in permissions.SAFE_METHODS:
# for reading, only allow if the user can see the object
return obj in models.AbsenceAttachment.all_visible_by_user(request.user)
else:
# for writing, only allow if the user can edit the object
return obj in models.AbsenceAttachment.all_editable_by_user(request.user)
UserPermission = permission_from_helper_class(models.User)
DepartmentPermission = permission_from_helper_class(models.Department)
StudentGroupPermission = permission_from_helper_class(models.StudentGroup)
TeachingUnitPermission = permission_from_helper_class(models.TeachingUnit)
StudentCardPermission = permission_from_helper_class(models.StudentCard)
TeachingSessionPermission = permission_from_helper_class(models.TeachingSession)
AttendancePermission = permission_from_helper_class(models.Attendance)
AbsencePermission = permission_from_helper_class(models.Absence)
AbsenceAttachmentPermission = permission_from_helper_class(models.AbsenceAttachment)

View file

@ -7,7 +7,7 @@ Everything to test the API v1 is described here.
from rest_framework import status
from rest_framework import test
from Palto.Palto import factories
from Palto.Palto import factories, models
from Palto.Palto.api.v1 import serializers
@ -39,6 +39,7 @@ class UserApiTestCase(test.APITestCase):
# check for a get request
response = self.client.get("/api/v1/users/")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.json()["count"], models.User.objects.count())
# check for a post request
response = self.client.post("/api/v1/users/", data=self.USER_CREATION_DATA)
@ -93,7 +94,82 @@ class UserApiTestCase(test.APITestCase):
class DepartmentApiTestCase(test.APITestCase):
pass
# fake department creation test
DEPARTMENT_CREATION_DATA: dict = {
"name": "UFR des Sciences",
"email": "ufr.sciences@university.fr",
}
def setUp(self):
self.user_admin = factories.FakeUserFactory(is_superuser=True)
self.user_other = factories.FakeUserFactory()
def test_permission_admin(self):
""" Test the API permission for an administrator """
# TODO: use reverse to get the url ?
self.client.force_login(self.user_admin)
# check for a get request
response = self.client.get("/api/v1/departments/")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.json()["count"], models.Department.objects.count())
# check for a post request
response = self.client.post("/api/v1/departments/", data=self.DEPARTMENT_CREATION_DATA)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
def test_permission_anonymous(self):
""" Test the API permission for an anonymous user """
# TODO: use reverse to get the url ?
# TODO: use api endpoint as class attribute ?
self.client.logout()
# check for a get request
response = self.client.get("/api/v1/departments/")
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
# check for a post request
response = self.client.post("/api/v1/departments/", data=self.DEPARTMENT_CREATION_DATA)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
def test_permission_unrelated(self):
""" Test the API permission for an unrelated user """
# TODO: use reverse to get the url ?
self.client.force_login(self.user_other)
# check for a get request and that he can't see anything
response = self.client.get("/api/v1/departments/")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.json()["count"], 0)
# check for a post request
response = self.client.post("/api/v1/departments/", data=self.DEPARTMENT_CREATION_DATA)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
def test_permission_related(self):
""" Test the API permission for a related user """
# TODO: use reverse to get the url ?
student1, student2 = factories.FakeUserFactory(), factories.FakeUserFactory()
department = factories.FakeDepartmentFactory(students=(student1, student2))
self.client.force_login(student1)
"""
TODO: this test require to show the field students before creating it.
# check for a get request and that he can see the other student
response = self.client.get("/api/v1/departments/")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertIn(serializers.UserSerializer(student2).data, response.json()["results"])
"""
# check for a post request
response = self.client.post("/api/v1/departments/", data=self.DEPARTMENT_CREATION_DATA)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
class StudentGroupApiTestCase(test.APITestCase):

View file

@ -3,82 +3,84 @@ Views for the Palto project's API v1.
An API view describe which models should display which files to user with which permissions.
"""
from typing import Type
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from rest_framework.permissions import IsAuthenticated, BasePermission
from rest_framework.serializers import BaseSerializer
from . import permissions
from . import serializers
from ... import models
class UserViewSet(viewsets.ModelViewSet):
serializer_class = serializers.UserSerializer
permission_classes = [IsAuthenticated, permissions.UserPermission]
def view_from_helper_class(
model_class: Type[models.ModelPermissionHelper],
serializer_class: Type[BaseSerializer],
permission_classes: list[Type[BasePermission]],
) -> Type[viewsets.ModelViewSet]:
"""
Create a view class from a model if it implements ModelPermissionHelper.
This make creating view easier to understand and less redundant.
"""
def get_queryset(self):
return models.User.all_visible_by_user(self.request.user)
class ViewSet(viewsets.ModelViewSet):
nonlocal serializer_class, permission_classes, model_class
def get_serializer_class(self):
return serializer_class
def get_permissions(self):
return [permission() for permission in permission_classes]
def get_queryset(self):
return model_class.all_visible_by_user(self.request.user)
return ViewSet
class DepartmentViewSet(viewsets.ModelViewSet):
serializer_class = serializers.DepartmentSerializer
permission_classes = [permissions.DepartmentPermission]
def get_queryset(self):
return models.Department.all_visible_by_user(self.request.user)
class StudentGroupViewSet(viewsets.ModelViewSet):
serializer_class = serializers.StudentGroupSerializer
permission_classes = [IsAuthenticated, permissions.StudentGroupPermission]
def get_queryset(self):
return models.StudentGroup.all_visible_by_user(self.request.user)
class TeachingUnitViewSet(viewsets.ModelViewSet):
serializer_class = serializers.TeachingUnitSerializer
permission_classes = [IsAuthenticated, permissions.TeachingUnitPermission]
def get_queryset(self):
return models.TeachingUnit.all_visible_by_user(self.request.user)
class StudentCardViewSet(viewsets.ModelViewSet):
serializer_class = serializers.StudentCardSerializer
permission_classes = [IsAuthenticated, permissions.StudentCardPermission]
def get_queryset(self):
return models.StudentCard.all_visible_by_user(self.request.user)
class TeachingSessionViewSet(viewsets.ModelViewSet):
serializer_class = serializers.TeachingSessionSerializer
permission_classes = [IsAuthenticated, permissions.TeachingSessionPermission]
def get_queryset(self):
return models.TeachingSession.all_visible_by_user(self.request.user)
class AttendanceViewSet(viewsets.ModelViewSet):
serializer_class = serializers.AttendanceSerializer
permission_classes = [IsAuthenticated, permissions.AttendancePermission]
def get_queryset(self):
return models.Attendance.all_visible_by_user(self.request.user)
class AbsenceViewSet(viewsets.ModelViewSet):
serializer_class = serializers.AbsenceSerializer
permission_classes = [IsAuthenticated, permissions.AbsencePermission]
def get_queryset(self):
return models.Absence.all_visible_by_user(self.request.user)
class AbsenceAttachmentViewSet(viewsets.ModelViewSet):
serializer_class = serializers.AbsenceAttachmentSerializer
permission_classes = [IsAuthenticated, permissions.AbsenceAttachmentPermission]
def get_queryset(self):
return models.AbsenceAttachment.all_visible_by_user(self.request.user)
UserViewSet = view_from_helper_class(
model_class=models.User,
serializer_class=serializers.UserSerializer,
permission_classes=[IsAuthenticated, permissions.UserPermission]
)
DepartmentViewSet = view_from_helper_class(
model_class=models.Department,
serializer_class=serializers.DepartmentSerializer,
permission_classes=[IsAuthenticated, permissions.DepartmentPermission]
)
StudentGroupViewSet = view_from_helper_class(
model_class=models.StudentGroup,
serializer_class=serializers.StudentGroupSerializer,
permission_classes=[IsAuthenticated, permissions.StudentGroupPermission]
)
TeachingUnitViewSet = view_from_helper_class(
model_class=models.TeachingUnit,
serializer_class=serializers.TeachingUnitSerializer,
permission_classes=[IsAuthenticated, permissions.TeachingUnitPermission]
)
StudentCardViewSet = view_from_helper_class(
model_class=models.StudentCard,
serializer_class=serializers.StudentCardSerializer,
permission_classes=[IsAuthenticated, permissions.StudentCardPermission]
)
TeachingSessionViewSet = view_from_helper_class(
model_class=models.TeachingSession,
serializer_class=serializers.TeachingSessionSerializer,
permission_classes=[IsAuthenticated, permissions.TeachingSessionPermission]
)
AttendanceViewSet = view_from_helper_class(
model_class=models.Attendance,
serializer_class=serializers.AttendanceSerializer,
permission_classes=[IsAuthenticated, permissions.AttendancePermission]
)
AbsenceViewSet = view_from_helper_class(
model_class=models.Absence,
serializer_class=serializers.AbsenceSerializer,
permission_classes=[IsAuthenticated, permissions.AbsencePermission]
)
AbsenceAttachmentViewSet = view_from_helper_class(
model_class=models.AbsenceAttachment,
serializer_class=serializers.AbsenceAttachmentSerializer,
permission_classes=[IsAuthenticated, permissions.AbsenceAttachmentPermission]
)