Как комбинировать / смешивать разрешения уровня объекта и уровня пользователя в DRF?

#python #django #django-rest-framework #django-permissions

#python #django #django-rest-framework #django-разрешения

Вопрос:

В настоящее время я работаю над проектом DRF, в котором пользователи-администраторы, пользователи-учителя и пользователи-владельцы должны иметь доступ к подробному просмотру объектов. В основном все типы пользователей, кроме тех, кто не является владельцем объекта, преподавателем или пользователем-администратором. Я могу реализовать отдельные разрешения для каждого, но когда мне нужно объединить эти разрешения в представлении, я натыкаюсь на препятствие, потому что разрешения на уровне пользователя проверяются перед разрешениями на уровне объекта. Таким образом, я не могу использовать логические операнды для их объединения, и мне приходится писать эти уродливые классы разрешений для моих представлений.

Мой вопрос:

Как я могу реализовать эти разрешения в моем подробном представлении более чистым способом или, альтернативно, есть более чистый способ получить мой результат?

Как вы увидите, я нарушаю DRY, потому что у меня есть пермь IsAdminOrOwner и IsAdminOrTeacherOrOwner. На мой взгляд, вы также заметите, что я перезаписываю get_permissions(), чтобы иметь соответствующие классы разрешений для соответствующих методов запроса. Любые комментарии по этой и другим реализациям приветствуются, я хочу критику, чтобы я мог ее улучшить.

Здесь следует permissions.py:

 from rest_framework import permissions
from rest_framework.permissions import IsAdminUser

class IsOwner(permissions.BasePermission):

    def has_permission(self, request, view):
        return True

    def has_object_permission(self, request, view, obj):
        return request.user == obj

class IsTeacher(permissions.BasePermission):

    def has_permission(self, request, view):
        return request.user.groups.filter(
            name='teacher_group').exists()

class IsAdminOrOwner(permissions.BasePermission):

    def has_object_permission(self, *args):
        is_owner = IsOwner().has_object_permission(*args)

        #convert tuple to list
        new_args = list(args)
        #remove object for non-object permission args
        new_args.pop()
        is_admin = IsAdminUser().has_permission(*new_args)

        return is_owner or is_admin

class IsAdminOrTeacherOrOwner(permissions.BasePermission):

    def has_object_permission(self, *args):
        is_owner = IsOwner().has_object_permission(*args)

        #convert tuple to list
        new_args = list(args)
        #remove object for non-object permission args
        new_args.pop()
        is_admin = IsAdminUser().has_permission(*new_args)
        is_teacher = IsTeacher().has_permission(*new_args)

        return is_admin or is_teacher or is_owner
 

И здесь следует моему мнению:

 class UserRetrieveUpdateView(APIView):
    serializer_class = UserSerializer

    def get_permissions(self):
        #the = is essential, because with each view
        #it resets the permission classes
        #if we did not implement it, the permissions
        #would have added up incorrectly
        self.permission_classes = [IsAuthenticated]

        if self.request.method == 'GET':
            self.permission_classes.append(
                IsAdminOrTeacherOrOwner)

        elif self.request.method == 'PUT':
            self.permission_classes.append(IsOwner)

        return super().get_permissions()

    def get_object(self, pk):
        try:
            user = User.objects.get(pk=pk)
            self.check_object_permissions(self.request, user)
            return user
        except User.DoesNotExist:
            raise Http404

    def get(self, request, pk, format=None):
        user = self.get_object(pk)
        serializer = self.serializer_class(user)
        return Response(serializer.data, status.HTTP_200_OK)

    def put(self, request, pk, format=None):
        user = self.get_object(pk)
        #we use partial to update only certain values
        serializer = self.serializer_class(user,
            data=request.data, partial=True)

        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data,
                status.HTTP_200_OK)

        return Response(status=status.HTTP_400_BAD_REQUEST)
 

Комментарии:

1. Совет по упрощению кода: поскольку вы просто используете get put операцию and , почему бы вам не использовать RetrieveUpdateAPIView вместо APIView . Таким образом get_object , вам не понадобится переопределять get put метод. Все это уже есть в RetrieveUpdateAPIView

2. Спасибо за совет. Я обязательно рассмотрю это. Что касается разрешений, знаете ли вы лучший способ?

Ответ №1:

Разрешения в DRF можно комбинировать с помощью побитового оператора OR . Например, вы могли бы сделать это:

 permission_classes = (IsAdmin | IsOwner | IsTeacher)
 

Таким образом, вам не нужно определять отдельные классы для их объединения.

Комментарии:

1. Привет, во-первых, спасибо за ответ! Однако я осознаю тот факт, что можно использовать побитовые операторы, но поскольку isOwner — это разрешение на уровне объекта, а остальные разрешения — на уровне пользователя, побитовая обработка с ними не работает, мои тесты терпят неудачу: (

2. @DJN Я не понимаю, как это имеет значение. (IsAdminUser | IsOwner) должно быть достигнуто то же самое, что и у IsAdminOrOwner вас.

3. Нет, это не так, потому что IsAdminUser проверяется перед разрешением isOwner, поскольку пермь isOwner является уровнем объекта, а IsAdminUser — нет.

4. Но это именно то, что вы IsAdminOrOwner делаете. На самом деле не имеет значения, какой из них тестируется первым, поскольку это or . В конце концов, вы сделали return is_owner or is_admin это, если is_owner значение равно false, а is_admin значение true, тогда оно все равно пройдет. В любом случае, я действительно не вижу никакой разницы между этими двумя

5. @Ken4scholars Я опаздываю на вечеринку, но в DRF есть ошибка, которая является незаметной и вызывает опасения по поводу безопасности. Существует много проблем, подобных этой: github.com/encode/django-rest-framework/issues/7117