Что нужно знать, чтобы управлять пользователями в Django Admin

Spread the love

Перевод статьи Haki Benita: What You Need to Know to Manage Users in Django Admin

Управление пользователями в админке Django — сложная тема. Если вы используете слишком много разрешений, вы можете утонуть в повседневные операциях по предоставлению прав. Если вы разрешаете свободно предоставлять права пользователем без надзора, то вы подвергаете свою систему риску.

Django предоставляет хорошую среду аутентификации с тесной интеграцией с Django Admin. Django Admin из коробки не устанавливает каких либо ограничений для администраторов. Это может привести к опасным сценариям, которые могут поставить под угрозу вашу систему.

Знаете ли вы, что сотрудники, которые управляют другими пользователями в Django Admin, могут редактировать свои собственные разрешения? Знаете ли вы, что они также могут стать суперпользователями? В админке Django нет ничего, что могло бы этому помешать, так что это решать вам!

К концу этого урока вы узнаете, как:

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

Модель Permissions (Разрешения)

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

Django поставляется со встроенной системой аутентификации. Система аутентификации включает пользователей, группы и разрешения.

При создании модели Django автоматически создает четыре разрешения по умолчанию для следующих действий:

  1. add: Пользователи с этим разрешением могут добавить экземпляр модели.
  2. delete: Пользователи с этим разрешением могут удалить экземпляр модели.
  3. change: Пользователи с этим разрешением могут обновить экземпляр модели.
  4. view: Пользователи с этим разрешением могут просматривать экземпляры этой модели. Это разрешение было очень ожидаемым, и, наконец, оно было добавлено в Django 2.1.

Имена разрешений следуют особому соглашению об именах: <app>.<action>_<modelname>.

Давайте рассмотрим это подробнее:

  • <app> это имя приложения. Например модель User импортируется из auth (django.contrib.auth).
  • <action> является одним из действий указаных выше (adddeletechange, или 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 admin «из коробки» использует модель permissions:

  • Если у пользователя нет прав доступа к модели, он не сможет увидеть ее или получить доступ к ней через панель администратора.
  • Если у пользователя есть права на просмотр и изменение модели, он сможет просматривать и обновлять экземпляры, но не сможет добавлять новые или удалять существующие.

При наличии соответствующих разрешений пользователи с правами администратора менее склонны совершать ошибки, и злоумышленникам будет труднее причинить вред.

Реализация пользовательских бизнес-ролей в Django Admin

Одним из наиболее уязвимых мест в каждом приложении является система аутентификации. В приложениях Django это модель User. Итак, чтобы лучше защитить ваше приложение, вы должны начать с модели User.

Во-первых, вам нужно взять под контроль страницу администратора модели User. Django уже поставляется с очень хорошей страницей администратора для управления пользователями. Чтобы воспользоваться этой замечательной работой, не нужно создавать новую страницу а нужно расширить уже встроенную модель User в Django admin.

Setup: Пользовательская регистрация модели User

Чтобы расширить страницу администратора для модели 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 bare boned user admin

Запрет на обновление полей

Необслуживаемые формы администратора — главный кандидат на ужасные ошибки. Пользователь может легко обновить экземпляр модели через 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

Давайте разберем что тут было сделано:

  • Чтобы внести коррективы в форму, нужно переопределить get_form(). Эта функция используется Django для создания формы изменения по умолчанию для модели.
  • Чтобы условно отключить поле, сначала нужно получить форму по умолчанию, созданную Django, а затем, если пользователь не является суперпользователем, отключить поле имени пользователя.

Теперь, когда не-суперпользователь пытается отредактировать пользователя, поле 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

В дополнение к предыдущему примеру тут сделаны следующие дополнения:

  1. Инициализирован пустой набор disabled_fields, который будет содержать поля для отключения. set — это структура данных, которая содержит уникальные значения. В этом случае имеет смысл использовать set, потому что вам нужно отключить поле только один раз. Оператор |= используется для выполнения обновления на месте OR (ИЛИ). Для получения дополнительной информации о set, почитайте это Sets in Python.
  2. Затем, если пользователь является суперпользователем, было добавлено в set два поля (username из предыдущего примера и is_superuser). Они будут препятствовать тому, чтобы не-суперпользователи становились суперпользователями.
  3. Наконец, далее перебираются все поля в set, и помечаются как отключенные.

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 является экземпляром объекта, с которым вы сейчас работаете:

  • Когда obj имеет значение None, форма используется для создания нового пользователя.
  • Когда obj не None, форма используется для редактирования существующего пользователя.

Чтобы проверить, работает ли пользователь, выполняющий запрос, с самим собой, нужно сравнить 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 — это экземпляр, с которым вы сейчас работаете:

  • Когда obj имеет значение None, пользователь запрашивает представление списка.
  • Когда obj не None, пользователь запрашивает представление изменения конкретного экземпляра.

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

  • Предотвращение изменений в рабочее время
  • Реализация разрешений на уровне объектов

Ограничение доступа к настраиваемым действиям

Пользовательские 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:

  • Вы защитили себя от повышения разрешений, запретив пользователям редактировать свои собственные разрешения.
  • Вы сохранили чистоту и удобство обслуживания, заставляя пользователей управлять разрешениями только с помощью групп.
  • Вы предотвратили утечку разрешений через настраиваемые действия, явно применяя необходимые разрешения.
Была ли вам полезна эта статья?
[14 / 5]

Spread the love
Подписаться
Уведомление о
guest
0 Комментарий
Oldest
Newest Most Voted
Inline Feedbacks
View all comments