Django — проектирование и внедрение системы разрешений

Django

задний план

Система разрешений неизбежна в фоновом режиме В этой статье представлена ​​наша схема реализации системы разрешений.

Прежде чем поделиться, давайте кратко представим наш платформенный бизнес. Мы являемся отделом качества, и наша платформа объединяет несколько бизнес-подразделений, поэтому нам необходимо достичь:

  • Многопользовательский
  • многопроектный
  • 3 роли

Разные пользователи имеют роли в проектах разных отделов, и каждая роль имеет разные права доступа к разным интерфейсам, например:

  • Только администратор может удалить данные
  • Все пользователи имеют права просмотра данных
  • Только операторы могут изменять данные

Выше приведены требования упрощенной разрешительной системы.Поговорим о плане внедрения.

Дизайн и реализация

существуетDjango - сериализация модели возвращает естественное значение первичного ключаВ этой статье мы узнали о модуле сериализации DRF.Помимо сериализации, DRF также инкапсулирует множество полезных функций, таких как наша текущая платформаAPIViewОн унаследован от DRFAPIViewкласс, а также класс пагинации (Pagination) и класс управления разрешениями (Permission) и так далее.

Наша схема реализации контроля разрешений заимствована из DRF.DjangoModelPermissionсвоего рода.

Модуль разрешений Django на самом деле уже имеетUser,Group,PermissionПричина, по которой модель данных и отношение ассоциации не используют официальные разрешения и не используют напрямую модуль разрешений DRF, заключается в том, что оба они основаны на CURD модели данных, которую можно настроить, но настройка и миграция данных относительно сложны. Дело в том, что бизнес не нуждается в доработке.С гибкой конфигурацией разрешений, поэтому не принято.

ролевые отношения

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

Проектные отношения-роль:

Отношения проект-пользователь-роль:

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

модель данных

from django.db import models
from django.conf import settings
from myapp.codes import role

class UserProjectRole(models.Model):
    """用户-项目-角色关系表"""
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    project = models.ForeignKey('myapp.Project', on_delete=models.CASCADE)
    role = models.IntegerField(default=role.GUEST)

    class Meta:
        db_table = 'myapp_user_project_role'
        unique_together = ['user', 'project']

Роли и сеансы

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

Код

def get_or_create_role(user_id, project_id, session, default_role=role.GUEST):
    """根据session获取当前用户的角色"""
    role = session.get('role')
    if not role:
        role_rel_obj, _ = AuthGroup.objects.get_or_create(
                              user_id=user_id,
                              project_id=project_id,
                              default={'role': default_role})
        session['role'] = role_rel_obj.role
        
    return role

инициализация роли

Когда пользователь выбирает элемент в первый раз,myapp_user_project_roleЧасть данных вставляется в таблицу.

Примечательно, что Джангоauth_userВ таблице есть готовые поля, по которым можно определить, является ли пользователь администратором. я используюauth_user.is_staff == 1Для администраторов права администратора доступны только через Django.adminСайт изменен, чтобы пользователи-администраторы не могли быть обновлены или понижены без разбора.

Если пользователь администратор, вставьтеadminроль; в противном случае вставьтеguestРоль.OperatorРоли создаются через интерфейс конфигурации.

Код

class SelectProject(MyAPIView):
    def post(self, request, project_id):
        """选择项目"""
        default_role = role.ADMIN if is_admin(request.user) else role.GUEST
        role_rel_obj, _ = UserProjectRole.objects.get_or_create(
                              user=request.user,
                              project_id=project_id,
                              defaults={'role': default_role})
        request.session['role'] = role_rel_obj.role
        ...

Авторитетные отношения

Разрешения в основном относятся к разрешениям на операции, отправляемым различным методам запроса каждым интерфейсом.

Отношения интерфейс-запрос-метод-роль:

ДРФ класс Djangomodelpermission

DjangoModelPermissionПолный исходный код доступенисходный код.

Теперь разберем реализацию этого класса.

Во-первых, это описание в строке документации: оно гарантирует, что пользователь аутентифицирован и имеет соответствующиеadd/change/deleteРазрешения на модели. И структура сопоставления взаимосвязи между типами запросов и разрешениями:

perms_map = {
    'GET': [],
    'OPTIONS': [],
    'HEAD': [],
    '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'],
}

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

Затем посмотрите на определения двух методов класса:

  • get_required_permissions: Учитывая тип запроса, вернуть список разрешений, требуемых типом запроса.
  • has_permission: определить, есть ли у пользователя разрешение на выполнение этого запроса.

has_permissionметод в родительском классеBasePermissionопределено в, возвращениеTrueЭто означает, что у него есть разрешение, иначе он будет вAPIViewБыл схвачен, вернулся403.

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

Код

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

class MyAPI(MyAPIView):
    min_perms_map = {
        'POST': role.OPERATOR,
        'DELETE': role.ADMIN,
    }

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

def get_required_permissions(perms_map, allowed_methods, method):
    """
    接收 APIView 配置的 min_perms_map 以及发送的请求方法(Method),返回允许请求的
    角色列表。如果 APIView 中未对 method 进行权限配置,则视为所有角色都用户该
    method 的权限。
    """
    if method not in perms_map:
        if method not in allowed_methods:
            raise exceptions.MethodNotAllowed(method)
        return list(range(1, role.GUEST + 1))
    return list(range(1, perms_map[method] + 1))

has_permsМетоды определены в пользовательской модели данных Django и не могут быть переопределены. Создайте новый нормальный метод напрямуюhas_permsЧтобы узнать, соответствуют ли разрешения, соответствующие этому запросу:

def has_perms(request, perms: list):
    """判断用户在项目中的权限"""
    try:
        role = get_or_create_role(request.user.pk, request.session)
        if not role:
            return False
        if not perms or role in perms:
            return True
        return False
    except:
        return False

Установить как класс разрешений по умолчанию

так какRolePermissionКлассы не инициализируются до тех пор, пока наше приложение не будет сгенерировано, поэтому их нельзя настроить вsettings.pyсередина.

Мое решение состояло в том, чтобы переписатьMyAPIViewкласс, унаследованный от DRFAPIViewсвоего рода. Настройте в этом классе:

from rest_framework.views import APIView
from myapp.permissions import RolePermissions

class MyAPIView(APIView):
    permission_class = [RolePermissions]

Тогда каждый интерфейс наследуется отMyAPIViewВот и все.

модульный тест

В случаях использования, которые не заботятся о ролях, мы можем сделатьMyAPIViewкласс для записи переключателя, например в переменнойRUN_TESTзаTrueКогда вы не настраиваетеpermission_classЧтобы обойти ограничение вынесения решения:

class MyAPIView(APIView):
    if RUN_TEST is False:
        permission_class = [RolePermissions]

Суммировать

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