Обеспечение прав доступа через связанную модель в Django Rest Framework
Я работаю над созданием разрешений для API, построенного с помощью Django REST Framework. Допустим, у меня есть следующие модели:
from django.db import models
class Study(models.Model):
pass
class Result(models.Model):
study = models.ForeignKey(Study)
value = models.IntegerField(null=False)
У меня есть базовые сериализаторы и представления для обеих этих моделей. Я буду использовать разрешения на каждый объект для предоставления пользователям доступа к одному или нескольким исследованиям. Я хочу, чтобы пользователи могли просматривать Result
только те Study
, на которые у них есть права. Я могу предложить два способа сделать это, но ни один из них не кажется идеальным:
- Keep per-object permissions on
Result
s in sync withStudy
. This is just a non-starter since we wantStudy
to always be the source of truth. - Write a custom permissions class which checks permissions on the related
Study
when a user tries to access aResult
. This actually isn't too bad, but I couldn't find examples of others doing it this way and it got me thinking that I may be thinking about this fundamentally wrong.
Есть ли уже существующие решения для этого? Или же необходимо создать собственный класс разрешений? Если да, то есть ли у вас примеры других людей, которые реализовали этот способ?
Я следовал 2-му методу, который вы упомянули, используя BasePermission, например:
class ReadResult(permissions.BasePermission):
def has_permission(self, request, view):
if request.user.is_authenticated:
return True
return False
def has_object_permission(self, request, view, obj):
# your conditions here return true or false
# returning false will raise permission denied error
return True
Третье решение - использование метода "to_representation()" в вашем сериализаторе. Если пользователь аутентифицирован, значит, вы уже знаете его/ее. В этом случае легко проверить, имеет ли он/она доступ к ресурсу.
class ResultSerializer():
def to_representation(self, instance):
if instance.study_id == self.user.id:
return {"id":instance.id, "xxxx":instance.xxx}
else:
return {"detail":"There is not any result"}
Как вы указали, вы можете сделать пользовательское разрешение в соответствии со вторым способом: И включить разрешение в ваше представление:
from rest_framework import permissions
class ResultOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS and obj.study.exists() :
# read only requests
return True
else:
return obj.study == request.user.some_request_study_paramter
И включить,
class ReviewDetail(viewsets.ViewSet):
permission_classes =[ResultOrReadOnly]
Я бы создал новое поле enrolled_users
в Study
модели, чтобы указать, какой пользователь имеет доступ к определенному Study
объекту.
from django.db import models
from django.conf import settings
class Study(models.Model):
enrolled_users = models.ManyToManyField(
settings.AUTH_USER_MODEL,
related_name="studies"
)
# other fields
class Result(models.Model):
study = models.ForeignKey(Study)
value = models.IntegerField(null=False)
Тогда в DRF будет очень легко фильтровать набор запросов в представлениях
# views.py
from .models import Study, Result
from rest_framework.permissions import IsAuthenticated
class StudyModelViewSet(viewsets.ModelViewSet):
permission_classes = (IsAuthenticated,)
def get_queryset(self):
return Study.objects.filter(enrolled_users=self.request.user)
class ResultModelViewSet(viewsets.ModelViewSet):
permission_classes = (IsAuthenticated,)
def get_queryset(self):
return Result.objects.filter(study__enrolled_users=self.request.user)
Примечания
- Это будет обрабатывать запрос разрешения объекта (Detail View) также .
- Это вернет код состояния не HTTP 403, а HTTP 404 .
- Здесь я использовал класс
ModelViewSet
, но, вы можете использовать любые представления, но фильтр играет роль .