Перейти к основному содержимому

ADR-005: Переопределение разрешений

  • Статус: Принято
  • Дата: 16 апреля 2026
  • Автор: CTO Agent (KALA-148)

Контекст

Кастомные роли покрывают стабильную tenant-политику, но production-системам всегда нужны исключения:

  • временно выдать одному пользователю доступ к просмотру ролей;
  • отозвать рискованное разрешение у пользователя, который в целом попадает под широкую роль;
  • добавить один отдел для проекта;
  • убрать один отдел из широкой роли.

Заводить новую роль под каждое исключение — это role sprawl. Полноценная таблица разрешений с per-user грантами добавит больше механики, чем сейчас нужно продукту.

Решение

Хранить override-ы прямо в User:

extraPermissions      String[]
revokedPermissions String[]
extraDepartmentIds String[]
revokedDepartmentIds String[]

Эффективные разрешения считаются так:

effectivePerm = role.permissions + extraPermissions - revokedPermissions

Эффективные отделы — по тому же шаблону "add-then-remove":

effectiveDept = role.departmentIds + primary department + extraDepartmentIds - revokedDepartmentIds

Revoke применяется последним — значит бьёт и базовую роль, и additions.

Рассмотренные альтернативы

Per-user permission join-таблица

Пока отклонено. Запросы писать проще, но это добавляет write-пути, джоины и сложность UI до того, как permissions станут достаточно мощными, чтобы оправдать это.

Кастомная роль под каждое исключение

Отклонено. Список ролей становится шумным, и размывается сам факт, что изменение — это индивидуальное исключение.

Только deny-override-ы

Отклонено. Продукту нужны и временные grants, и дополнительный department-доступ.

Последствия

Плюсы

  • Исключения явно видны в user-записи.
  • Эффективный доступ детерминирован и легко объясним.
  • Grant- и revoke-API могут писать audit-записи с измененным ключом.
  • Revoke даёт простой deny-механизм без полноценного policy engine.

Минусы

  • Массивы менее нормализованы, чем join-строки.
  • Переименование permission-ключа требует array-миграции.
  • Слишком много override-ов на одного пользователя — сигнал, что нужно делать отдельную роль.

Заметки по реализации

  • POST /users/:id/permissions/grant добавляет permission и extra department.
  • POST /users/:id/permissions/revoke добавляет permission revoke и department revoke.
  • DELETE-маршруты убирают grant- или revoke-записи.
  • GET /users/:id/effective-access возвращает роль, override-ы и вычисленный доступ — для отладки.
  • Каждая override-мутация пишет audit log entry.