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
|
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 rest_framework import permissions
|
||||||
|
|
||||||
from Palto.Palto import models
|
from Palto.Palto import models
|
||||||
|
|
||||||
|
|
||||||
class UserPermission(permissions.BasePermission):
|
def permission_from_helper_class(model: Type[models.ModelPermissionHelper]) -> Type[permissions.BasePermission]:
|
||||||
# TODO: has_permission check for authentication
|
"""
|
||||||
|
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:
|
class Permission(permissions.BasePermission):
|
||||||
if request.method in permissions.SAFE_METHODS:
|
def has_permission(self, request, view) -> bool:
|
||||||
# for reading, allow everybody
|
if request.method in permissions.SAFE_METHODS:
|
||||||
return True
|
# for reading, allow everybody
|
||||||
|
return True
|
||||||
|
|
||||||
if models.User.can_user_create(request.user):
|
if model.can_user_create(request.user):
|
||||||
# for writing, only allowed users
|
# for writing, only allowed users
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def has_object_permission(self, request, view, obj: models.User) -> bool:
|
def has_object_permission(self, request, view, obj: models.User) -> bool:
|
||||||
if request.method in permissions.SAFE_METHODS:
|
if request.method in permissions.SAFE_METHODS:
|
||||||
# for reading, only allow if the user can see the object
|
# for reading, only allow if the user can see the object
|
||||||
return obj in models.User.all_visible_by_user(request.user)
|
return obj in model.all_visible_by_user(request.user)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# for writing, only allow if the user can edit the object
|
# for writing, only allow if the user can edit the object
|
||||||
return obj in models.User.all_editable_by_user(request.user)
|
return obj in model.all_editable_by_user(request.user)
|
||||||
|
|
||||||
|
return Permission
|
||||||
|
|
||||||
|
|
||||||
class DepartmentPermission(permissions.BasePermission):
|
UserPermission = permission_from_helper_class(models.User)
|
||||||
def has_permission(self, request, view) -> bool:
|
DepartmentPermission = permission_from_helper_class(models.Department)
|
||||||
if request.method in permissions.SAFE_METHODS:
|
StudentGroupPermission = permission_from_helper_class(models.StudentGroup)
|
||||||
# for reading, allow everybody
|
TeachingUnitPermission = permission_from_helper_class(models.TeachingUnit)
|
||||||
return True
|
StudentCardPermission = permission_from_helper_class(models.StudentCard)
|
||||||
|
TeachingSessionPermission = permission_from_helper_class(models.TeachingSession)
|
||||||
if models.Department.can_user_create(request.user):
|
AttendancePermission = permission_from_helper_class(models.Attendance)
|
||||||
# for writing, only allowed users
|
AbsencePermission = permission_from_helper_class(models.Absence)
|
||||||
return True
|
AbsenceAttachmentPermission = permission_from_helper_class(models.AbsenceAttachment)
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ Everything to test the API v1 is described here.
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework import test
|
from rest_framework import test
|
||||||
|
|
||||||
from Palto.Palto import factories
|
from Palto.Palto import factories, models
|
||||||
from Palto.Palto.api.v1 import serializers
|
from Palto.Palto.api.v1 import serializers
|
||||||
|
|
||||||
|
|
||||||
|
@ -39,6 +39,7 @@ class UserApiTestCase(test.APITestCase):
|
||||||
# check for a get request
|
# check for a get request
|
||||||
response = self.client.get("/api/v1/users/")
|
response = self.client.get("/api/v1/users/")
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(response.json()["count"], models.User.objects.count())
|
||||||
|
|
||||||
# check for a post request
|
# check for a post request
|
||||||
response = self.client.post("/api/v1/users/", data=self.USER_CREATION_DATA)
|
response = self.client.post("/api/v1/users/", data=self.USER_CREATION_DATA)
|
||||||
|
@ -93,7 +94,82 @@ class UserApiTestCase(test.APITestCase):
|
||||||
|
|
||||||
|
|
||||||
class DepartmentApiTestCase(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):
|
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.
|
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 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 permissions
|
||||||
from . import serializers
|
from . import serializers
|
||||||
from ... import models
|
from ... import models
|
||||||
|
|
||||||
|
|
||||||
class UserViewSet(viewsets.ModelViewSet):
|
def view_from_helper_class(
|
||||||
serializer_class = serializers.UserSerializer
|
model_class: Type[models.ModelPermissionHelper],
|
||||||
permission_classes = [IsAuthenticated, permissions.UserPermission]
|
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):
|
class ViewSet(viewsets.ModelViewSet):
|
||||||
return models.User.all_visible_by_user(self.request.user)
|
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):
|
UserViewSet = view_from_helper_class(
|
||||||
serializer_class = serializers.DepartmentSerializer
|
model_class=models.User,
|
||||||
permission_classes = [permissions.DepartmentPermission]
|
serializer_class=serializers.UserSerializer,
|
||||||
|
permission_classes=[IsAuthenticated, permissions.UserPermission]
|
||||||
def get_queryset(self):
|
)
|
||||||
return models.Department.all_visible_by_user(self.request.user)
|
DepartmentViewSet = view_from_helper_class(
|
||||||
|
model_class=models.Department,
|
||||||
|
serializer_class=serializers.DepartmentSerializer,
|
||||||
class StudentGroupViewSet(viewsets.ModelViewSet):
|
permission_classes=[IsAuthenticated, permissions.DepartmentPermission]
|
||||||
serializer_class = serializers.StudentGroupSerializer
|
)
|
||||||
permission_classes = [IsAuthenticated, permissions.StudentGroupPermission]
|
StudentGroupViewSet = view_from_helper_class(
|
||||||
|
model_class=models.StudentGroup,
|
||||||
def get_queryset(self):
|
serializer_class=serializers.StudentGroupSerializer,
|
||||||
return models.StudentGroup.all_visible_by_user(self.request.user)
|
permission_classes=[IsAuthenticated, permissions.StudentGroupPermission]
|
||||||
|
)
|
||||||
|
TeachingUnitViewSet = view_from_helper_class(
|
||||||
class TeachingUnitViewSet(viewsets.ModelViewSet):
|
model_class=models.TeachingUnit,
|
||||||
serializer_class = serializers.TeachingUnitSerializer
|
serializer_class=serializers.TeachingUnitSerializer,
|
||||||
permission_classes = [IsAuthenticated, permissions.TeachingUnitPermission]
|
permission_classes=[IsAuthenticated, permissions.TeachingUnitPermission]
|
||||||
|
)
|
||||||
def get_queryset(self):
|
StudentCardViewSet = view_from_helper_class(
|
||||||
return models.TeachingUnit.all_visible_by_user(self.request.user)
|
model_class=models.StudentCard,
|
||||||
|
serializer_class=serializers.StudentCardSerializer,
|
||||||
|
permission_classes=[IsAuthenticated, permissions.StudentCardPermission]
|
||||||
class StudentCardViewSet(viewsets.ModelViewSet):
|
)
|
||||||
serializer_class = serializers.StudentCardSerializer
|
TeachingSessionViewSet = view_from_helper_class(
|
||||||
permission_classes = [IsAuthenticated, permissions.StudentCardPermission]
|
model_class=models.TeachingSession,
|
||||||
|
serializer_class=serializers.TeachingSessionSerializer,
|
||||||
def get_queryset(self):
|
permission_classes=[IsAuthenticated, permissions.TeachingSessionPermission]
|
||||||
return models.StudentCard.all_visible_by_user(self.request.user)
|
)
|
||||||
|
AttendanceViewSet = view_from_helper_class(
|
||||||
|
model_class=models.Attendance,
|
||||||
class TeachingSessionViewSet(viewsets.ModelViewSet):
|
serializer_class=serializers.AttendanceSerializer,
|
||||||
serializer_class = serializers.TeachingSessionSerializer
|
permission_classes=[IsAuthenticated, permissions.AttendancePermission]
|
||||||
permission_classes = [IsAuthenticated, permissions.TeachingSessionPermission]
|
)
|
||||||
|
AbsenceViewSet = view_from_helper_class(
|
||||||
def get_queryset(self):
|
model_class=models.Absence,
|
||||||
return models.TeachingSession.all_visible_by_user(self.request.user)
|
serializer_class=serializers.AbsenceSerializer,
|
||||||
|
permission_classes=[IsAuthenticated, permissions.AbsencePermission]
|
||||||
|
)
|
||||||
class AttendanceViewSet(viewsets.ModelViewSet):
|
AbsenceAttachmentViewSet = view_from_helper_class(
|
||||||
serializer_class = serializers.AttendanceSerializer
|
model_class=models.AbsenceAttachment,
|
||||||
permission_classes = [IsAuthenticated, permissions.AttendancePermission]
|
serializer_class=serializers.AbsenceAttachmentSerializer,
|
||||||
|
permission_classes=[IsAuthenticated, permissions.AbsenceAttachmentPermission]
|
||||||
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)
|
|
||||||
|
|
Loading…
Reference in a new issue