В уроке 8 по созданию API на Django REST Framework, мы рассмотрим использование прав доступа групп пользователей и аутентификацию по сессиям.

Предыдущая часть и урок по DRF правам доступа: Django REST Framework создание API: Урок 7, часть 1, права доступа пользователей

Аутентификация по сессии - предназначена в основном для тестировании API в браузере, Вы передаете пароль и логин единожды, получая уникальный идентификатор, который перемещается в заголовок Cookies. 

Такой вид аутентификации вызывает некоторые сложности, например:

  • Вам нужно использовать одну сессию в нескольких доменах. Во многих случаях это будет невозможно сделать;
  • Если у вас несколько клиентов в т.ч. настольные и мобильные приложения. Сессии могут хранить не только данные сессии, но и об приложении. Они могут быть не нужны и с ними будет сложнее работать;
  • Токены являются лучшим способом защиты от CSRF атак.

Перейдем к практической работе.

Давайте из предыдущего урока дадим одно разрешение пользователю на просмотр статей в админ-панеле:

Авторизуемся в админ-панели через другой браузер, или приватные вкладки, получив новую сессию, проверим, как выглядит данное разрешение на просмотр статей:

И все, что я могу делать в административной панели, так это просматривать статьи, так как я указал именно такое разрешение в правах пользователя. И чтобы данные разрешения работали также в API, как и в админ панели, нам необходимо добавить следующее:

Как пример я воспользуюсь представлением детальной страницы с возможностью обновления и удаления ArticleRetrieveUpdateDestroyAPIView

modules/blog/views/articles.py

class ArticleRetrieveUpdateDestroyAPIView(generics.RetrieveUpdateDestroyAPIView):
    """
    Представление для получения, обновления, редактирования одной статьи (GET, PUT, DELETE)
    """
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    lookup_field = 'slug'
    permission_classes = [permissions.DjangoModelPermissions]
    authentication_classes = [authentication.SessionAuthentication]

Пояснение:

  • Я добавил DjangoModelPermissions, для того, чтобы работали наши разрешения заданные пользователю на основе Django.
  • Также добавил SessionAuthentication, для правильной работы аутентификации по сессиям для проверки API в браузере.

Необязательно для тех, кто делает все в файле views.py.

Добавлю импорты в __init__.py:

from modules.blog.views.articles import *

__all__ = '__all__'

И добавлю обработку представлений в urls.py

from django.urls import path
from modules.blog import views


urlpatterns = [
    path('articles/', views.ArticleListAPIView.as_view(), name='api_articles_list'),
    path('articles/create/', views.ArticleCreateAPIView.as_view(), name='api_articles_create'),
    path('articles/<str:slug>/', views.ArticleRetrieveUpdateDestroyAPIView.as_view(), name='api_articles_detail_update_delete'),
]

И давайте попробуем перейти на страницу детальной статьи с нашего пользователя, у которого есть право только просматривать статьи: http://127.0.0.1:8000/api/articles/test-article-4/

Кроме, как просматривать статью мы ничего не можем, из-за выданных разрешений. Но если мы зайдем на эту же страницу с суперпользователя, то увидим, что мы можем: удалить и обновить статью, так как эти права по умолчанию доступны суперпользователю.

Давайте также рассмотрим пример с группой и ее разрешениями, я создам группу Admin, и выдам ее пользователю, права группы Admin: добавление статьи, удаление статьи, изменение статьи.

Проверим сначала в админ-панели с пользователя с группой Admin:

Теперь проверим на той же странице наши возможности через API: http://127.0.0.1:8000/api/articles/test-article-4/

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

Теперь рассмотрим возможность создания кастомного разрешения. Для этого нам необходимо улучшить нашу модель Article, добавив в нее FK ссылающийся на пользователя, добавившего статью.

Для тех, кто забыл, как мы делали модель, прошу перейти в этот урок: Django REST Framework создание API: Урок: 2, добавление модуля «Блог» и моделей 

modules/blog/models/articles.py

from django.db import models
from django.core.validators import FileExtensionValidator
from django.contrib.auth.models import User

class Article(models.Model):
    """
    Модель "Статьи"
    """
    title = models.CharField(verbose_name='Заголовок', max_length=255)
    slug = models.SlugField(verbose_name='URL', max_length=255, blank=True)
    short_description = models.TextField(max_length=300, verbose_name='Краткое описание')
    full_description = models.TextField(verbose_name='Полное описание')
    created_at = models.DateTimeField(verbose_name='Дата добавления', auto_now_add=True, db_index=True)
    updated_at = models.DateTimeField(verbose_name='Дата обновления', auto_now=True, db_index=True)
    thumbnail = models.ImageField(
        verbose_name='Превью поста',
        blank=True,
        upload_to='images/thumbnails/',
        validators=[FileExtensionValidator(
            allowed_extensions=('png', 'jpg', 'webp', 'jpeg', 'gif'))
        ]
    )
    is_published = models.BooleanField(default=True, verbose_name='Опубликовано')
    is_fixed = models.BooleanField(default=False, verbose_name='Зафиксировано')
    # Связи по ключам
    category = models.ForeignKey(to='Category', on_delete=models.PROTECT, null=True, verbose_name='Категория')
    created_by = models.ForeignKey(User, verbose_name='Добавил', on_delete=models.PROTECT)
    
    class Meta:
        ordering = ('-is_fixed', '-created_at',)
        verbose_name = 'Статья'
        verbose_name_plural = 'Статьи'
        db_table = 'app_articles'

    def __str__(self):
        return self.title

Пояснение:

  • Добавил поле created_by, указав на модель User
  • Модель User импортирую из django.contrib.auth.models

Проведем миграции:

(venv) PS C:\Users\Razilator\Desktop\Projects\App\backend> py manage.py makemigrations
It is impossible to add a non-nullable field 'created_by' to article without specifying a default. This is because the database needs something to populate existing rows.
Please select a fix:
 1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
 2) Quit and manually define a default value in models.py.
Select an option: 1
Please enter the default value as valid Python.
The datetime and django.utils.timezone modules are available, so it is possible to provide e.g. timezone.now as a value.
Type 'exit' to exit this prompt
>>> 1
Migrations for 'blog':
  modules\blog\migrations\0002_article_created_by.py
    - Add field created_by to article
(venv) PS C:\Users\Razilator\Desktop\Projects\App\backend> py manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, blog, contenttypes, sessions
Running migrations:
  Applying blog.0002_article_created_by... OK
(venv) PS C:\Users\Razilator\Desktop\Projects\App\backend> 

Для фиксации проблемы, я выбрал первый вариант и поставил 1, чтобы за пользователя использовался в статьях пользователь с ID - 1.

Создам файл permissions.py в нашем модуле "Blog", пример есть в документации: Permissions - Django REST framework

С следующим содержимым:

modules/blog/permissions.py

from rest_framework import permissions

class IsCreatedByOrReadOnly(permissions.BasePermission):
    """
    Разрешение на уровне объекта, чтобы разрешить его редактирование только владельцам объекта.
    Предполагается, что экземпляр модели имеет атрибут created_by связанный с моделью User.
    """

    def has_object_permission(self, request, view, obj):
        # Разрешение на чтение разрешено для любого запроса, поэтому мы всегда будем разрешать запросы GET, HEAD или OPTIONS.
        if request.method in permissions.SAFE_METHODS:
            return True

        # Экземпляр должен иметь атрибут с именем `created_by`
        return obj.created_by == request.user

Пояснение:

  • В данном случае, мы создали разрешение для автора статьи на уровне объекта, т.е, если мы отключим ему разрешение на редактирование статей, и уберем такое право из его группы, но он будет автором статьи, то он сможет редактировать только свою собственную статью. 

Давайте проверим это в деле, в наше представление добавим кастомное разрешение:

modules/blog/views/articles.py

from modules.blog.models import Article
from modules.blog.serializers import ArticleSerializer
from rest_framework import generics, permissions, authentication
from modules.blog.permissions import IsCreatedByOrReadOnly

class ArticleRetrieveUpdateDestroyAPIView(generics.RetrieveUpdateDestroyAPIView):
    """
    Представление для получения, обновления, редактирования одной статьи (GET, PUT, DELETE)
    """
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    lookup_field = 'slug'
    permission_classes = [IsCreatedByOrReadOnly]
    authentication_classes = [authentication.SessionAuthentication]

Уберем все разрешения, группу, и установим для записи 4 автора, на котором и будем проверять кастомное разрешение:

И проверим страницу с суперпользователя: http://127.0.0.1:8000/api/articles/test-article-4:

И теперь проверим эту же страницу, являясь автором статьи:

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

Теги записи: Django, Django REST Framework, API, DRF,
Комментарии к статье 0
Комментариев нет
Форма добавления комментария (необходима регистрация)