factorised permissions and views
This commit is contained in:
parent
a731a03133
commit
93036887c4
3 changed files with 182 additions and 261 deletions
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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]
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue