Как создать систему пользователей на основе прав доступа в Django?

Я хочу построить REST API, в котором пользователь может выполнять операции над объектами, основываясь на своих правах. Рассмотрим запись, которая представляет автомобиль - она содержит номер лицензии, тип автомобиля и дополнительную информацию. Также рассмотрим следующую систему пользователей:

  • Owners - Кто владеет объектом car. Может изменять и удалять его.
  • Редакторы - Кто может изменять только свойства объекта.
  • Просмотрщики - Могут только просматривать свойства объекта.

Каждая запись может содержать несколько владельцев/редакторов/просмотрщиков (пользователь, создавший объект, автоматически должен быть владельцем). Также владельцы могут добавлять или удалять редакторов/просмотрщиков.

Итак, в случае GET-запроса я хочу иметь возможность вернуть все объекты, на которые у пользователя есть разрешения, разделенные на эти три категории.

Итак, в моем приложении api у меня есть следующий код:

Файл models.py содержит:

class CarRecord(models.Model):
    type = models.CharField(max_length=50)
    license = models.CharField(max_length=50)

Файл serializers.py содержит:

class CarRecordSerializer(serializers.ModelSerializer):
    type = models.CharField(max_length=50)
    license = models.CharField(max_length=50)

    class Meta:
        model = CarRecord
        fields = ('__all__')

В view.py у меня есть:

class CarRecordViews(APIView):
    def get(self, request):
        if not request.user.is_authenticated:
            user = authenticate(username=request.data.username, password=request.data.password)
             if user is not None:
                return Response(data={"error": "invalid username/password"}, status=status.HTTP_401_UNAUTHORIZED)
    # return all records of cars that user some type of permission for
            

Теперь я хочу получить все записи user, которые у него есть разрешения на запрос (вместе с их типом разрешения). Я думал добавить три дополнительных поля под CarRecord - каждое из них представляет собой список пользователей, которые содержат данный тип разрешения. Но я не уверен, что это "путь Django". Поэтому хотел сначала посоветоваться с SO.

Вот решение, которое я могу считать правильным

class CarRecord(models.Model):
    date_created = models.DateTimeField(auto_now_add=True)
    type = models.CharField(max_length=50)
    license = models.CharField(max_length=50)
    owners = models.ManyToManyField(User, related_name='car_owners')
    creator = models.ForeignKey(User,on_delete=models.DO_NOTHING)

class CarRecordSerializer(serializers.ModelSerializer):
    creator = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(), required=False)
    owners_details = UserSerializer(source='owners', many=True, read_only=True)

    class Meta:
        model = CarRecord
        fields = '__all__'
    
    def create(self, validated_data):
        try:
            new_owners = validated_data.pop('owners')
        except:
            new_owners = None
        car_record = super().create(validated_data)
        if new_owners:
            for new_owner in new_owners:
                car_record.owners.add(new_owner)
        return car_record

В views.py

from rest_frameword import generics
from rest_framework import permissions

class CustomCarRecordPermissions(permissions.BasePermission):
    def has_object_permission(self, request, view, obj):
        if request.method == 'GET':
            return True
        elif request.method == 'PUT' or request.method == 'PATCH':
            return request.user == obj.creator or request.user in obj.owners.all()
        elif request.method == 'DELETE':
            return request.user == obj.creator
        return False

 class CarRecordListCreate(generics.ListCreateAPIView):
     permission_classes = (IsAuthenticated, )
     serializer_class = CarRecordSerializer
     queryset = CarRecord.objects.all()
     
     def post(self, request, *args, **kwargs):
         request.data['creator'] = request.user.id
         return super().create(request, *args, **kwargs)

class CarRecordDetailView(generics.RetrieveUpdateDestroyAPIView):
    permission_classes = (CustomCarRecordPermissions, )
    serializer_class = CarRecordSerializer
    lookup_field = 'pk'
    queryset = CarRecord.objects.all()
    
    

модели является самоочевидным; В сериализаторах CarRecord мы устанавливаем создателя как требуемое поле False и связанное поле первичного ключа, чтобы мы могли предоставить запрос идентификатора пользователя перед созданием, как показано в views.py post method.

В представлении Detail мы устанавливаем наше пользовательское разрешение; если запрос GET, мы разрешаем разрешения. Но если запрос PUT или PATCH, то разрешены владельцы и создатель. Но если это запрос на удаление, то разрешено только создателю.

Я думаю, что пакет django-rest-framework-guardian подходит сюда. Этот пакет основан на django-guardian.

django-guardian - это реализация объектных разрешений для Django, предоставляющая дополнительный бэкенд аутентификации.

Нет никаких изменений на вашем models.py

Вам следует изменить serializers.py и views.py. Например, ваш сериализатор должен выглядеть следующим образом

from rest_framework_guardian.serializers import ObjectPermissionsAssignmentMixin    

class CarRecordSerializer(ObjectPermissionsAssignmentMixin, serializers.ModelSerializer):
    type = models.CharField(max_length=50)
    license = models.CharField(max_length=50)

    class Meta:
        model = CarRecord
        fields = ('__all__')

    def get_permissions_map(self, created):
        current_user = self.context['request'].user
        readers = Group.objects.get(name='readers')
        editors = Group.objects.get(name='editors')
        owners = Group.objects.get(name='owners')

        return {
          'view_car_record': [current_user, readers, owners],
          'change_car_record': [current_user, editors],
          'delete_car_record': [current_user, owners]
        }

и ваши представления должны выглядеть следующим образом:

from rest_framework_guardian import filters

class CarRecordModelViewSet(ModelViewSet):
    queryset = CarRecord.objects.all()
    serializer_class = CarRecordSerializer
    filter_backends = [filters.ObjectPermissionsFilter]
      

Редактируйте settings.py следующим образом:

INSTALLED_APPS = [
    'rest_framework',
    'guardian',
]

AUTHENTICATION_BACKENDS = [
    "django.contrib.auth.backends.ModelBackend",
    "guardian.backends.ObjectPermissionBackend",
]

Вы также можете определить бэкенды фильтров глобально в настройках:

REST_FRAMEWORK = {    
    "DEFAULT_FILTER_BACKENDS": [
        "django_filters.rest_framework.DjangoFilterBackend",
        "rest_framework_guardian.filters.ObjectPermissionsFilter",
    ],
}

Не забудьте! Если вы определите ObjectPermissionsFilter в settings.py, все ваши представления будут затронуты этим фильтром.

Если вы хотите ограничить post запрос для каждого пользователя, вы должны реализовать класс пользовательских разрешений, например, так:

from rest_framework import permissions


class CustomObjectPermissions(permissions.DjangoObjectPermissions):
    """
    Similar to `DjangoObjectPermissions`, but adding 'view' permissions.
    """
    perms_map = {
        'GET': ['%(app_label)s.view_%(model_name)s'],
        'OPTIONS': ['%(app_label)s.view_%(model_name)s'],
        'HEAD': ['%(app_label)s.view_%(model_name)s'],
        'POST': ['%(app_label)s.add_%(model_name)s'],
        'PUT': ['%(app_label)s.change_%(model_name)s'],
        'PATCH': ['%(app_label)s.change_%(model_name)s'],
        'DELETE': ['%(app_label)s.delete_%(model_name)s'],
    }

Пройдите по этой ссылке, чтобы получить подробную информацию для CustomObjectPermissions

Вы можете написать разрешение class car owner user.

Ваша модель.

class CarRecord(models.Model):
    date_created = models.DateTimeField()
    type = models.CharField(max_length=50)
    license = models.CharField(max_length=50)
    owners = models.ManyToManyField(User, related_name='car_owners', verbose_name=('owners'), default=[]))
    creator = models.ForeignKey(User,on_delete=models.DO_NOTHING)

Класс разрешения permission.py

from rest_framework.permissions import BasePermission,
from cars.models import CarRecord

class isCarAccess(BaseCommand):
    def has_permission(self, request, view):
        if request.method == 'OPTIONS':
            return True
        check_user = CarRecord.objects.filter(owners__in=[request.user])
        return request.user is not None and request.user.is_authenticated and check_user

этот класс разрешения будет проверять, существует ли пользователь, аутентифицирован ли он, а также принадлежит ли пользователь к записи карты или нет.

И вы можете передать это разрешение в вашем представлении.

from .permission import  isCarAccess
from .models import  CarRecord
class CarRecordViews(APIView):

    permission_classes = [isCarAccess]

    def get(self, request):
         car_record = CarRecord.objects.filter(owners__in=[request.user])
         # return all records of cars that user some type of permission for

и ваш settings.py

REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": (  
       "oauth2_provider.contrib.rest_framework.OAuth2Authentication",
        "rest_framework.authentication.SessionAuthentication",
        "rest_framework.authentication.TokenAuthentication",
    ),
}   
            

Надеюсь, это поможет вам.

Вернуться на верх