Перевод статьи Haki Benita: What You Need to Know to Manage Users in Django Admin
Управление пользователями в админке Django — сложная тема. Если вы используете слишком много разрешений, вы можете утонуть в повседневные операциях по предоставлению прав. Если вы разрешаете свободно предоставлять права пользователем без надзора, то вы подвергаете свою систему риску.
Django предоставляет хорошую среду аутентификации с тесной интеграцией с Django Admin. Django Admin из коробки не устанавливает каких либо ограничений для администраторов. Это может привести к опасным сценариям, которые могут поставить под угрозу вашу систему.
Знаете ли вы, что сотрудники, которые управляют другими пользователями в Django Admin, могут редактировать свои собственные разрешения? Знаете ли вы, что они также могут стать суперпользователями? В админке Django нет ничего, что могло бы этому помешать, так что это решать вам!
К концу этого урока вы узнаете, как:
Если вы не устанавливаете разрешения, то вы подвергаете свою систему риску вторжения, утечки данных и человеческих ошибок. Если вы злоупотребляете разрешениями или используете их слишком часто, вы рискуете создать систему в которой вы вынуждены будете управлять права в повседневных операциях.
Django поставляется со встроенной системой аутентификации. Система аутентификации включает пользователей, группы и разрешения.
При создании модели Django автоматически создает четыре разрешения по умолчанию для следующих действий:
add
: Пользователи с этим разрешением могут добавить экземпляр модели.delete
: Пользователи с этим разрешением могут удалить экземпляр модели.change
: Пользователи с этим разрешением могут обновить экземпляр модели.view
: Пользователи с этим разрешением могут просматривать экземпляры этой модели. Это разрешение было очень ожидаемым, и, наконец, оно было добавлено в Django 2.1.Имена разрешений следуют особому соглашению об именах: <app>.<action>_<modelname>
.
Давайте рассмотрим это подробнее:
<app>
это имя приложения. Например модель User импортируется из auth (django.contrib.auth). <action>
является одним из действий указаных выше (add
, delete
, change
, или view
).<modelname>
это название модели, все строчные буквы.Знание этого соглашения об именах поможет вам легче управлять разрешениями. Например, имя разрешения на изменение пользователя — auth.change_user.
Модель permissions предоставляется пользователям или группам. Чтобы проверить, есть ли у пользователя определенные разрешения, вы можете сделать следующее:
>>> from django.contrib.auth.models import User >>> u = User.objects.create_user(username='haki') >>> u.has_perm('auth.change_user') False
Стоит отметить, что .has_perm() всегда будет возвращать True для активного суперпользователя, даже если разрешение на самом деле не существует:
>>> from django.contrib.auth.models import User >>> superuser = User.objects.create_superuser( ... username='superhaki', ... email='me@hakibenita.com', ... password='secret', ) >>> superuser.has_perm('does.not.exist') True
Как вы можете видеть, когда вы проверяете разрешения для суперпользователя, разрешения на самом деле не проверяются.
Модели Django сами обычно не используют разрешения. По умолчанию единственные права доступа — это права для Django Admin.
Причина, по которой модели обычно не используют разрешения, заключается в том, что, как правило, модель не знает о пользователе, выполняющем действие. В приложениях Django объект пользователя обычно находиться в запросе. Вот почему в большинстве случаев разрешения применяются на уровне представления (вьюх).
Например, чтобы запретить пользователю без разрешений на просмотр в модели User доступ к представлению, отображающему информацию о пользователе, выполните следующие действия:
from django.core.exceptions import PermissionDenied def users_list_view(request): if not request.user.has_perm('auth.view_user'): raise PermissionDenied()
Если пользователь, сделавший запрос, вошел в систему и прошел проверку подлинности, то request.user будет содержать экземпляр User. Если пользователь не вошел в систему, то request.user будет экземпляром AnonymousUser. Это специальный объект, используемый Django для обозначения неаутентифицированного пользователя. Использование has_perm в AnonymousUser всегда возвращает False.
Если пользователь, отправляющий запрос, не имеет разрешения view_user, вы должны создать исключение PermissionDenied, и клиенту должен вернуться ответ со статусом 403.
Чтобы упростить применение разрешений в представлениях, Django предоставляет сокращенный декоратор под названием permission_required
, который делает то же самое:
from django.contrib.auth.decorators import permission_required @permission_required('auth.view_user') def users_list_view(request): pass
Чтобы применить разрешения в шаблонах, вы можете получить доступ к текущим пользовательским разрешениям через специальную переменную шаблона, которая называется perms. Например, если вы хотите, чтобы кнопка удаления отображалась только пользователям с разрешением на удаление, выполните следующие действия:
{% if perms.auth.delete_user %} <button>Delete user!</button> {% endif %}
Некоторые популярные сторонние приложения, такие как Django rest framework, также предоставляют полезную интеграцию с разрешениями модели Django.
Django admin имеет очень тесную интеграцию со встроенной системой аутентификации, в частности, с моделью permissions. Django admin «из коробки» использует модель permissions:
При наличии соответствующих разрешений пользователи с правами администратора менее склонны совершать ошибки, и злоумышленникам будет труднее причинить вред.
Одним из наиболее уязвимых мест в каждом приложении является система аутентификации. В приложениях Django это модель User. Итак, чтобы лучше защитить ваше приложение, вы должны начать с модели User.
Во-первых, вам нужно взять под контроль страницу администратора модели User. Django уже поставляется с очень хорошей страницей администратора для управления пользователями. Чтобы воспользоваться этой замечательной работой, не нужно создавать новую страницу а нужно расширить уже встроенную модель User в Django admin.
Чтобы расширить страницу администратора для модели User, вам нужно отменить текущую регистрацию администратора модели, предоставленного Django, и зарегистрировать собственную:
from django.contrib import admin from django.contrib.auth.models import User from django.contrib.auth.admin import UserAdmin # Unregister the provided model admin admin.site.unregister(User) # Register out own model admin, based on the default UserAdmin @admin.register(User) class CustomUserAdmin(UserAdmin): pass
CustomUserAdmin расширяет UserAdmin в Django. На этом этапе, если вы войдете в систему Django Admin по адресу http://127.0.0.1:8000/admin/auth/user, вы должны увидеть, что страница администратора пользователей не изменилась:
Необслуживаемые формы администратора — главный кандидат на ужасные ошибки. Пользователь может легко обновить экземпляр модели через Django Admin, чего приложение не ожидает. В большинстве случаев пользователь даже не заметит, что что-то не так. Такие ошибки обычно очень трудно отследить и исправить.
Чтобы предотвратить такие ошибки, вы можете запретить администраторам изменять определенные поля в модели.
Если вы не хотите, чтобы какой-либо пользователь, включая суперпользователей, обновлял поле, вы можете пометить такое поле как поле только для чтения. Например, поле date_joined устанавливается при регистрации пользователя. Эта информация никогда не должна изменяться любым пользователем, поэтому вы можете пометить его только для чтения:
from django.contrib import admin from django.contrib.auth.models import User from django.contrib.auth.admin import UserAdmin @admin.register(User) class CustomUserAdmin(UserAdmin): readonly_fields = [ 'date_joined', ]
Когда поле добавлено в readonly_fields, оно не будет редактироваться в форме изменения по умолчанию. Когда поле помечено как доступное только для чтения, Django будет отображать элемент ввода как отключенный.
Но что, если вы хотите запретить обновление поля только некоторым пользователям?
Иногда полезно обновить поля прямо в админке. Но что если вы не хотите, чтобы какой-либо пользователь делал это: вы хотите, чтобы это могли делать только суперпользователи.
Допустим, вы хотите запретить пользователям, не являющимся суперпользователями, изменять имя пользователя. Для этого вам нужно изменить форму изменений, сгенерированную Django, и отключить поле имени пользователя на основе текущего пользователя:
from django.contrib import admin from django.contrib.auth.models import User from django.contrib.auth.admin import UserAdmin @admin.register(User) class CustomUserAdmin(UserAdmin): def get_form(self, request, obj=None, **kwargs): form = super().get_form(request, obj, **kwargs) is_superuser = request.user.is_superuser if not is_superuser: form.base_fields['username'].disabled = True return form
Давайте разберем что тут было сделано:
Теперь, когда не-суперпользователь пытается отредактировать пользователя, поле username будет отключено. Любая попытка изменить username через Django Admin потерпит неудачу. Когда суперпользователь пытается отредактировать пользователя, поле username будет редактируемым и будет вести себя как положено.
Суперпользователь — это очень строгое разрешение, которое не должно предоставляться легко. Однако любой пользователь с разрешением на изменение модели User может сделать любого пользователя суперпользователем, в том числе и себя. Это противоречит всей цели системы разрешений, поэтому вам нужно закрыть эту дыру.
На основании предыдущего примера, чтобы не допустить превращения не суперпользователей в суперпользователей, добавьте следующее ограничение:
from typing import Set from django.contrib import admin from django.contrib.auth.models import User from django.contrib.auth.admin import UserAdmin @admin.register(User) class CustomUserAdmin(UserAdmin): def get_form(self, request, obj=None, **kwargs): form = super().get_form(request, obj, **kwargs) is_superuser = request.user.is_superuser disabled_fields = set() # type: Set[str] if not is_superuser: disabled_fields |= { 'username', 'is_superuser', } for f in disabled_fields: if f in form.base_fields: form.base_fields[f].disabled = True return form
В дополнение к предыдущему примеру тут сделаны следующие дополнения:
Django User Admin Двухступенчатая форма
Когда вы создаете нового пользователя в Django admin, вы проходите двухэтапную форму. В первой форме вы вводите имя пользователя и пароль. Во второй форме вы обновляете остальные поля.
Этот двухэтапный процесс уникален для модели User. Чтобы приспособиться к этому уникальному процессу, вы должны убедиться, что поле существует, прежде чем попытаться его отключить. В противном случае вы можете получить KeyError.
Способ управления разрешениями очень специфичен для каждой команды, продукта и компании. Я обнаружил, что легче управлять разрешениями в группах. В своих собственных проектах я создаю группы поддержки, редакторов контента, аналитиков и так далее. Я обнаружил, что управление разрешениями на уровне пользователя может стать настоящей проблемой. Когда добавляются новые модели или меняются бизнес-требования, утомительно обновлять каждого отдельного пользователя.
Чтобы управлять разрешениями только с помощью групп, необходимо запретить пользователям предоставлять разрешения конкретным пользователям. и разрешить объединение пользователей в группы. Для нужно отключить поле user_permissions для всех не суперпользователей:
from typing import Set from django.contrib import admin from django.contrib.auth.models import User from django.contrib.auth.admin import UserAdmin @admin.register(User) class CustomUserAdmin(UserAdmin): def get_form(self, request, obj=None, **kwargs): form = super().get_form(request, obj, **kwargs) is_superuser = request.user.is_superuser disabled_fields = set() # type: Set[str] if not is_superuser: disabled_fields |= { 'username', 'is_superuser', 'user_permissions', } for f in disabled_fields: if f in form.base_fields: form.base_fields[f].disabled = True return form
Тут был использован тот же метод, что и в предыдущих разделах, для реализации других бизнес-правил. В следующих разделах мы внедрим более сложные бизнес-правила для защиты вашей системы.
Пользователи с большими правами часто являются слабым местом. Они обладают серьезными разрешениями, и потенциальный ущерб, который они могут нанести, значителен. Чтобы предотвратить повышение разрешений в случае вторжения, вы можете запретить пользователям редактировать свои собственные разрешения:
from typing import Set from django.contrib import admin from django.contrib.auth.models import User from django.contrib.auth.admin import UserAdmin @admin.register(User) class CustomUserAdmin(UserAdmin): def get_form(self, request, obj=None, **kwargs): form = super().get_form(request, obj, **kwargs) is_superuser = request.user.is_superuser disabled_fields = set() # type: Set[str] if not is_superuser: disabled_fields |= { 'username', 'is_superuser', 'user_permissions', } # Prevent non-superusers from editing their own permissions if ( not is_superuser and obj is not None and obj == request.user ): disabled_fields |= { 'is_staff', 'is_superuser', 'groups', 'user_permissions', } for f in disabled_fields: if f in form.base_fields: form.base_fields[f].disabled = True return form
Аргумент obj является экземпляром объекта, с которым вы сейчас работаете:
Чтобы проверить, работает ли пользователь, выполняющий запрос, с самим собой, нужно сравнить request.user с obj. Поскольку это пользователь admin, obj является экземпляром User или None. Когда пользователь, делающий запрос, request.user, равен obj, это означает, что пользователь обновляет самого себя. В этом случае мы отключаем все конфиденциальные поля, которые можно использовать для получения разрешений.
Возможность настроить форму на основе объекта очень полезна. Это может быть использовано для реализации сложных ролей.
Иногда бывает полезно полностью переопределить разрешения в админке Django. Распространенный сценарий — это когда вы используете разрешения в других местах и не хотите, чтобы штатные пользователи вносили изменения в администраторе.
Django использует хуки для четырех встроенных разрешений. Внутренние, хуки используют права текущего пользователя для принятия решения. Вы можете переопределить эти хуки и принять другое решение.
Чтобы запретить сотрудникам удалять экземпляр модели, независимо от их прав доступа, вы можете сделать следующее:
from django.contrib import admin from django.contrib.auth.models import User from django.contrib.auth.admin import UserAdmin @admin.register(User) class CustomUserAdmin(UserAdmin): def has_delete_permission(self, request, obj=None): return False
Как и в случае с get_form(), obj — это экземпляр, с которым вы сейчас работаете:
Наличие экземпляра объекта в этом хуке очень полезно для реализации разрешений уровня объекта для различных типов действий. Вот другие варианты использования:
Пользовательские admin действия (action) требуют особого внимания. Django не имеет полной интеграции с ними, поэтому не может ограничить доступ к ним по умолчанию. Пользовательские действия будут доступны любому администратору с любым разрешением на модель.
Чтобы проиллюстрировать это, добавьте действие администратора, которое помечает нескольких пользователей как активных:
from django.contrib import admin from django.contrib.auth.models import User from django.contrib.auth.admin import UserAdmin @admin.register(User) class CustomUserAdmin(UserAdmin): actions = [ 'activate_users', ] def activate_users(self, request, queryset): cnt = queryset.filter(is_active=False).update(is_active=True) self.message_user(request, 'Activated {} users.'.format(cnt)) activate_users.short_description = 'Activate Users' # type: ignore
Используя это действие, штатный пользователь может пометить одного или нескольких пользователей и активировать их всех сразу. Это полезно в случаях, например, если у вас была ошибка в процессе регистрации, и вам нужно было массово активировать пользователей.
Это действие обновляет информацию о пользователях, поэтому вы хотите, чтобы ее могли использовать только пользователи с разрешениями на изменение.
Django admin использует внутреннюю функцию для получения действий. Чтобы скрыть activate_users() от пользователей без разрешения на изменение, переопределите get_actions():
from django.contrib import admin from django.contrib.auth.models import User from django.contrib.auth.admin import UserAdmin @admin.register(User) class CustomUserAdmin(UserAdmin): actions = [ 'activate_users', ] def activate_users(self, request, queryset): assert request.user.has_perm('auth.change_user') cnt = queryset.filter(is_active=False).update(is_active=True) self.message_user(request, 'Activated {} users.'.format(cnt)) activate_users.short_description = 'Activate Users' # type: ignore def get_actions(self, request): actions = super().get_actions(request) if not request.user.has_perm('auth.change_user'): del actions['activate_users'] return actions
get_actions() возвращает OrderedDict. Ключ — это имя действия, а значение — это функция действия. Чтобы скорректировать возвращаемое значение, нужно переопределить функцию, выбираете исходное значение и, в зависимости от прав пользователя, удалить настраиваемое действие activate_users
из dict.
Для сотрудников, у которых нет прав на change_user(), действие activate_users не будет отображаться в раскрывающемся списке действий.
Django admin — отличный инструмент для управления проектом Django. Многие команды полагаются на это, чтобы оставаться продуктивным в управлении повседневными операциями. Если вы используете Django admin для выполнения операций над моделями, важно знать о разрешениях. Методы, описанные в этой статье, полезны для любой модели, а не только для модели User.
В этом руководстве вы защитили свою систему, внеся следующие изменения в Django Admin:
Краткий перевод: https://vuejs.org/guide/components/v-model.html Основное использование v-model используется для реализации двусторонней привязки в компоненте. Начиная с Vue…
Сегодня мы рады объявить о выпуске Vue 3.4 «🏀 Slam Dunk»! Этот выпуск включает в…
Vue.js — это универсальный и адаптируемый фреймворк. Благодаря своей отличительной архитектуре и системе реактивности Vue…
Недавно, у меня истек сертификат и пришлось заказывать новый и затем устанавливать на хостинг с…
Каким бы ни было ваше мнение о JavaScript, но всем известно, что работа с датами…
Все, кто следит за последними событиями в мире адаптивного дизайна, согласятся, что введение контейнерных запросов…