Пример пользовательских разрешений для проекта на основе Django Вячеслав Буханцов, SDH

Первоначально опубликовано в блоге Software Development Hub.

Вячеслав Буханцов, генеральный директор Software Development Hub, архитектор программного обеспечения, эксперт в построении высоконагруженных CRM/ERP систем, серверных и веб-решений, делится своим подходом к созданию пользовательских прав доступа на Django.

Фреймворк Django имеет встроенный механизм разрешений, который подойдет для большинства простых проектов. Я могу сказать, что это идеальное решение для встроенного интерфейса администратора. Но что если права доступа к проекту должны быть реализованы более сложным способом?

В этой статье я поделюсь опытом нашей команды и приведу несколько примеров использования пользовательского механизма прав доступа. Наш пример не будет мешать встроенным правам доступа Django. К статье прилагается полный проект Django с реализацией. Для упрощения представления кода я намеренно удалил строки, относящиеся к активному кэшированию запросов и оптимизации производительности.

Демонстрационный проект на GitHub также будет полезен для изучения начинающим разработчикам.

Наши методы работы с группами и разрешениями Django

Мы начали использовать первую версию описанного подхода, начиная с версии Django 1.2, на проекте под названием Billing platform for ISP. Требованием проекта с точки зрения безопасности было разграничение прав доступа пользователей между различными компаниями. Приведенное ниже резюме дает более подробную информацию:

  • В системе может быть создано несколько компаний
  • Существует только одна запись для пользователя (т.е. пользователь имеет один логин и пароль во всех компаниях)
  • Права доступа в разных компаниях независимы, например, пользователь A в компании A является менеджером и может изменять данные клиента A (принадлежит компании A), а пользователь A в компании B имеет доступ ReadOnly к клиенту B (принадлежит клиенту B).
  • Как можно понять из описания требований выше, нарушение прав доступа должно быть выполнено на уровне объекта.

Для наглядности приведу краткое описание моделей:

class Company(models.Model):
   name = models.CharField(max_length=255)

   class Meta:
       db_table = 'company' 


class Customer(models.Model):
   company = models.ForeignKey(Company, on_delete=models.PROTECT)
   name = models.CharField(max_length=255)

   class Meta:
       db_table = 'customer'
Вход в полноэкранный режим Выход из полноэкранного режима

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

Модуль

Действие

Описание

компания

читать

Просмотр объекта Компания

компания

создать

Создание объекта Компания

компания

обновить

Изменение объекта Компания

компания

удалить

Удаление объекта Компания

клиент

читать

Просмотр объекта Клиент

клиент

создать

Создание объекта Customer

заказчик

обновить

Изменение объекта Заказчик

клиент

удалить

Удаление объекта Клиент

клиент

проверить

Верификация объекта Клиент

клиент

активировать

Активация объекта Customer

Согласно таблице, некоторые действия являются «классическим» CRUD, но только для отображения; как реальные бизнес-приложения, так и система разрешений более сложны; для модуля Customer были добавлены две дополнительные функции: верификация и активация.

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

Двухуровневая структура прав доступа

Двухуровневая структура прав будет описана следующими моделями:

class AccessModule(models.Model):
   name = models.CharField(max_length=128)
   slug = models.CharField(max_length=128, unique=True)

   class Meta:
       db_table = 'access_module'

class AccessPermission(models.Model):
   module = models.ForeignKey(AccessModule, on_delete=models.PROTECT)
   name = models.CharField(max_length=128)
   slug = models.CharField(max_length=128)
   groups = models.ManyToManyField(Group)

   class Meta:
       db_table = 'access_permission'
       unique_together = (('module', 'slug'),)
Вход в полноэкранный режим Выход из полноэкранного режима

Модель AccessPermission имеет гиперссылку на группу. Таким образом, определяется список групп, которым разрешено данное разрешение.

В проекте ERP-платформы такие модели могут быть представлены следующим интерфейсом конфигурации:

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

class AccessGroup(models.Model):
   user = models.ForeignKey(settings.AUTH_USER_MODEL)
   group = models.ForeignKey(Group)
   company = models.ForeignKey(Company)

   class Meta:
       db_table = 'access_group'
       unique_together = ('user', 'group', 'company')
Вход в полноэкранный режим Выход из полноэкранного режима

Объект HttpRequest присутствует во многих контекстах: его можно найти в View, он может быть передан в контекст шаблона. Информация об авторизованном пользователе также доступна в request.user

Поэтому было бы удобно добавить доступ к проверке прав через Request. Для этого было разработано промежуточное ПО, это ленивый объект к Acl.

Например, можно использовать следующую конструкцию:

def get_queryset(self):
   qs = super().get_queryset()
   qs = qs.filter(company__in=self.request.acl.get_companies('customer', 'view'))
   return qs
Вход в полноэкранный режим Выход из полноэкранного режима

В приведенном примере показана фильтрация списка клиентов только для тех компаний, на которые у пользователя есть права.
Настоятельно рекомендуется просмотреть пример CustomerListView из проекта.

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

::

В моих следующих работах будет выполнен пример и описание использования данного движка прав в сочетании с Django Rest Framework, а также будет представлено автодокументирование прав в связке с drf-spectacular.

Читайте также:

Как работает SDH в военное время? Отвечает Вячеслав Буханцов, генеральный директор

Оцените статью
devanswers.ru
Добавить комментарий