Урок 4: Аутентификация и Разрешения
В настоящее время наш API не имеет ограничений на то, кто может редактировать или удалять фрагменты кода. Мы хотели бы иметь более продвинутое поведение, чтобы убедиться, что:
- Фрагменты кода всегда ассоциированы с создателем.
- Только аутентифицированные пользователи могут создавать фрагменты.
- Только создатель фрагмента может обновлять или удалять его.
- Неаутентифицированные запросы должны иметь полный доступ для чтения.
Добавление информации в нашу модель
Мы внесём несколько изменений в наш Snippet класс модели. Сначала добавим несколько полей. Одно из этих полей будет использоваться для представления пользователя, который создал фрагмент кода. Другое поле будет использоваться для хранения форматированного HTML-представления кода.
Добавьте следующие два поля к Snippet модели в models.py.
owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE)
highlighted = models.TextField()
Мы также должны убедиться, что при сохранении модели мы заполняем поле форматированного кода, используя библиотеку форматирования кода pygments.
Нам потребуются дополнительные импорты:
from pygments.lexers import get_lexer_by_name from pygments.formatters.html import HtmlFormatter from pygments import highlight
И теперь мы можем добавить метод .save() в наш класс модели:
def save(self, *args, **kwargs):
"""
Use the `pygments` library to create a highlighted HTML
representation of the code snippet.
"""
lexer = get_lexer_by_name(self.language)
linenos = 'table' if self.linenos else False
options = {'title': self.title} if self.title else {}
formatter = HtmlFormatter(style=self.style, linenos=linenos,
full=True, **options)
self.highlighted = highlight(self.code, lexer, formatter)
super().save(*args, **kwargs)
После этого нам нужно обновить таблицы нашей базы данных. Обычно мы бы создали миграцию базы данных для этого, но для целей данного урока давайте просто удалим базу данных и начнём заново.
rm -f db.sqlite3 rm -r snippets/migrations python manage.py makemigrations snippets python manage.py migrate
Вы также можете создать несколько разных пользователей для тестирования API. Самый быстрый способ сделать это — с помощью команды createsuperuser.
python manage.py createsuperuser
Добавление конечных точек для наших моделей пользователей
Теперь, когда у нас есть пользователи, с которыми можно работать, нам следует добавить представления этих пользователей в наш API. Создание нового сериализатора просто. В serializers.py добавьте:
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())
class Meta:
model = User
fields = ['id', 'username', 'snippets']
Поскольку 'snippets' является обратной связью с моделью User, она не будет включена по умолчанию при использовании класса ModelSerializer, поэтому нам нужно было добавить явное поле для неё.
Мы также добавим несколько представлений в views.py. Мы хотели бы использовать только представления для чтения для представлений пользователей, поэтому мы будем использовать обобщённые классовые представления ListAPIView и RetrieveAPIView.
from django.contrib.auth.models import User
class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetail(generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
Убедитесь, что также импортирован класс UserSerializer
from snippets.serializers import UserSerializer
Наконец, нам нужно добавить эти представления в API, ссылаясь на них из конфигурации URL. Добавьте следующее в шаблоны в snippets/urls.py.
path('users/', views.UserList.as_view()),
path('users/<int:pk>/', views.UserDetail.as_view()),
Связывание фрагментов кода с пользователями
В настоящее время, если мы создали фрагмент кода, не будет возможности связать пользователя, создавшего фрагмент, с экземпляром фрагмента. Пользователь не передаётся в составе сериализованного представления, а вместо этого является свойством входящего запроса.
Мы решаем эту проблему путём переопределения метода .perform_create() в наших представлениях фрагментов, что позволяет нам изменить способ управления сохранением экземпляра и обработать любую информацию, которая неявна в входящем запросе или запрошенном URL.
В классе представления SnippetList добавьте следующий метод:
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
Метод create() нашего сериализатора теперь будет получать дополнительное поле 'owner', а также валидированные данные из запроса.
Обновление нашего сериализатора
Теперь, когда фрагменты кода связаны с пользователем, который их создал, давайте обновим наш SnippetSerializer для отражения этого. Добавьте следующее поле в определение сериализатора в serializers.py:
owner = serializers.ReadOnlyField(source='owner.username')
Примечание: Убедитесь, что вы также добавили 'owner', в список полей во внутреннем классе Meta.
Это поле делает кое-что довольно интересное. Аргумент source управляет тем, какой атрибут используется для заполнения поля и может указывать на любой атрибут сериализованного экземпляра. Он также может принимать указанную выше точечную нотацию, в этом случае он будет переходить по заданным атрибутам аналогично тому, как это используется в языке шаблонов Django.
Добавленное поле — это бестиповой класс ReadOnlyField, в отличие от других типизированных полей, таких как CharField, BooleanField и т. д. Бестиповой ReadOnlyField всегда только для чтения и будет использоваться для сериализованных представлений, но не будет использоваться для обновления экземпляров моделей при их десериализации. Мы также могли бы использовать CharField(read_only=True) здесь.
Добавление необходимых разрешений в представления
Теперь, когда фрагменты кода связаны с пользователями, мы хотим убедиться, что только аутентифицированные пользователи могут создавать, обновлять и удалять фрагменты кода.
REST framework включает ряд классов разрешений, которые мы можем использовать для ограничения доступа к конкретному представлению. В этом случае нам нужен класс IsAuthenticatedOrReadOnly, который гарантирует, что аутентифицированные запросы получат доступ для чтения и записи, а неаутентифицированные — только для чтения.
Сначала добавьте следующий импорт в модуль представлений
from rest_framework import permissions
Затем добавьте следующее свойство в оба класса представлений SnippetList и SnippetDetail.
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
Добавление входа в браузерный API
Если вы откроете браузер и перейдёте к браузерному API в данный момент, вы обнаружите, что больше не можете создавать новые фрагменты кода. Для этого нам необходимо войти в систему как пользователь.
Мы можем добавить представление входа для использования с браузерным API, отредактировав конфигурацию URL в файле проекта urls.py.
Добавьте следующий импорт в начало файла:
from django.urls import path, include
И в конце файла добавьте шаблон для включения представлений входа и выхода для браузерного API.
urlpatterns += [
path('api-auth/', include('rest_framework.urls')),
]
Часть 'api-auth/' шаблона может быть любым URL, который вам нужен.
Теперь, если вы откроете браузер и обновите страницу, вы увидите ссылку «Вход» в правом верхнем углу страницы. Если вы войдёте в систему как один из созданных ранее пользователей, вы сможете снова создавать фрагменты кода.
После создания нескольких фрагментов кода перейдите к конечной точке '/users/', и обратите внимание, что представление включает список идентификаторов фрагментов, связанных с каждым пользователем, в поле 'snippets' каждого пользователя.
Разрешения на уровне объектов
На самом деле мы хотели бы, чтобы все фрагменты кода были видимы всем, но также убедиться, что только пользователь, который создал фрагмент кода, может его обновлять или удалять.
Для этого нам нужно создать настраиваемое разрешение.
В приложении snippets создайте новый файл permissions.py
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
Custom permission to only allow owners of an object to edit it.
"""
def has_object_permission(self, request, view, obj):
# Read permissions are allowed to any request,
# so we'll always allow GET, HEAD or OPTIONS requests.
if request.method in permissions.SAFE_METHODS:
return True
# Write permissions are only allowed to the owner of the snippet.
return obj.owner == request.user
Теперь мы можем добавить это настраиваемое разрешение к конечной точке экземпляра фрагмента, отредактировав свойство permission_classes в классе представления SnippetDetail:
permission_classes = [permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly]
Убедитесь, что также импортирован класс IsOwnerOrReadOnly.
from snippets.permissions import IsOwnerOrReadOnly
Теперь, если вы снова откроете браузер, вы обнаружите, что действия «УДАЛИТЬ» и «ОБНОВИТЬ» появляются только на конечной точке экземпляра фрагмента, если вы вошли в систему как тот же пользователь, который создал фрагмент кода.
Аутентификация с API
Поскольку у нас теперь есть набор разрешений в API, нам необходимо аутентифицировать наши запросы, если мы хотим редактировать какие-либо фрагменты. Мы не настраивали никаких классов аутентификации, поэтому по умолчанию применяются SessionAuthentication и BasicAuthentication.
При взаимодействии с API через веб-браузер мы можем войти в систему, и сеанс браузера затем предоставит необходимые данные для аутентификации запросов.
Если мы взаимодействуем с API программно, нам нужно явно предоставить данные для аутентификации в каждом запросе.
Если мы попытаемся создать фрагмент без аутентификации, мы получим ошибку:
http POST http://127.0.0.1:8000/snippets/ code="print(123)"
{
"detail": "Authentication credentials were not provided."
}
Мы можем сделать успешный запрос, включив имя пользователя и пароль одного из созданных ранее пользователей.
http -a admin:password123 POST http://127.0.0.1:8000/snippets/ code="print(789)"
{
"id": 1,
"owner": "admin",
"title": "foo",
"code": "print(789)",
"linenos": false,
"language": "python",
"style": "friendly"
}
Резюме
Теперь у нас есть довольно тонко настроенный набор разрешений в нашем веб-API и конечные точки для пользователей системы и для фрагментов кода, которые они создали.
В части 5 учебника мы рассмотрим, как связать всё это воедино, создав HTML-конечную точку для наших форматированных фрагментов кода и улучшить согласованность нашего API, используя гиперссылки для связей в системе.
Copyright © 2011–present Encode OSS Ltd.
Licensed under the BSD License.
https://www.django-rest-framework.org/tutorial/4-authentication-and-permissions/