⟨/⟩ 40_Arch/ADR/ADR_011_AbacAuthorization.cs

137 строк · в начало

using System;
using Ban.Sdaid.Notation.Documents;

namespace Ban.Sdaid.Icd.Arch.Adr
{
    /// <summary>Решение: разграничение доступа по модели ABAC — claims → функции → группы; периметр вне охвата.</summary>
    public class ADR_011_AbacAuthorization : IAdrDocument
    {
        public string Name => "ADR-011. Разграничение доступа по модели ABAC (claims → функции → группы)";

        public string Description =>
            @"Полномочия описываются как claims (пара ключ+значение), собираемые в человекочитаемые функции, а функции — в группы, назначаемые пользователю. Backend проверяет claims на каждой команде/запросе, UI скрывает недоступные разделы и действия. Модель адаптирована из rtfm (ADR-SRV-023) без периметра — в домене ICD орг-структуры для скоупинга нет.";

        public string Version => "0.1";
        public string Status => "proposed";
        public string[] Comments => new[]
        {
            "2026-08-20. Принято как системное решение по доступу; закрывает OQ-V-1 видения (состав ролей и прав). Адаптировано из rtfm ADR-SRV-023 (ABAC), периметр из переноса исключён.",
        };

        public Type? Supersedes => null;

        public static string S1_Context = $"""
            ## Контекст

            До сих пор ICD работал без доступа: команды выполняет кто угодно, поля `CreatedByUserId` /
            `AssignedToUserId` пишутся заглушкой (`Guid.Empty`), а решение «кто ввёл» vs «кто взял в работу»
            зафиксировано, но не защищено ({nameof(Ban.Sdaid.Icd.Arch.Adr.ADR_004_CreatorVsAssignee)}).
            Видение предполагает две роли — **хранитель** (ввод, проверка и корректировка, экспорт) и
            **администратор** (конфигурация обработки), — но состав ролей и прав оставлен открытым вопросом
            (`OQ-V-1` в {nameof(Ban.Sdaid.Icd.Vision.IcdVision)}). Требования уже опираются на «доступность
            действий по правам»: набор операций проверки ({nameof(Ban.Sdaid.Icd.Requirements.FR_005_Verification)})
            и удаление/переходы в очереди ({nameof(Ban.Sdaid.Icd.Requirements.FR_009_PacketQueue)}).

            Нужна модель разграничения, которая:

            1. **Гранулярна** — контроль на уровне отдельной команды/запроса и отдельного UI-действия, а не
               только грубых ролей.
            2. **Настраивается администратором** человекочитаемо, без участия разработчика после первичного
               посева.
            3. **Едина для backend и UI** — фронт потребляет подмножество тех же прав.

            В rtfm та же задача решена как ABAC с двумя осями: функциональной (что можно делать) и
            **периметром** (к каким объектам — в терминах сетей/организаций). У ICD второй оси нет: сервис
            оцифровки карт не мультитенантен, признака для скоупинга данных в домене не существует. Поэтому
            переносим функциональную ось ABAC и **исключаем периметр**.
            """;

        public static string S2_Decision = """
            ## Решение

            Принимается **ABAC на функциональных claims** (плоский, без периметра).

            **claim = пара (ключ, значение).** Ключи — контролируемый реестр:
            - `icd/cqrs/command` — право на выполнение команды (значение = имя класса команды или `*`);
            - `icd/cqrs/query` — право на выполнение запроса (значение = имя класса запроса или `*`);
            - `icd/ui` — право на UI-элемент (значение — `подсистема/ресурс/действие`, напр. `queue/delete`, или `*`).

            **Функция** — человекочитаемая единица полномочий (напр. «Хранитель», «Администратор», «Только
            просмотр»); её фактические права — набор claim-значений. **Группа** набирается из функций;
            пользователю назначаются группы. Функции сеет разработчик (посев в инфраструктуре), группы и
            назначения ведёт администратор.

            **Проверки на двух уровнях:**
            - **Backend** — базовый уровень: перед выполнением команда/запрос проверяется по имени своего
              класса против claims пользователя (`icd/cqrs/command` | `icd/cqrs/query`). Нет права → отказ (403).
              Проверка встроена в базовый хендлер — покрывает каждую команду/запрос автоматически, включая
              будущие (совместимо со skills `new-command`/`new-query`). Без проверки намеренно — только вход,
              выход и получение профиля.
            - **UI** — фронт получает из профиля только claims ключа `icd/ui` и скрывает недоступные разделы
              меню и действия. UI — витрина прав, не граница безопасности (граница — backend).

            **Сессия.** Вход по логину/паролю выписывает JWT с уникальным `jti`. Контекст пользователя (его
            claims) собирается из БД (группы → функции → claim-значения) и кэшируется по `jti` на срок жизни
            токена. Выход/отзыв — через список инвалидированных токенов + вытеснение контекста из кэша.

            **Dev-суперпользователь (break-glass).** Один сид-пользователь проходит любые проверки (профиль
            отдаёт `*/*`). Только для разработки, на проде убирается. Единый источник — backend.

            **Периметр — вне охвата.** Фильтрация данных по орг-структуре не вводится: скоупить нечем.
            Оставлено точкой расширения — добавляется отдельным решением при появлении разграничения
            (по образцу rtfm ADR-SRV-024), не ломая claim-инфраструктуру.
            """;

        public static string S3_Rationale = """
            ## Обоснование и рассмотренные варианты

            - **RBAC (только роли).** Пользователю назначаются роли с фиксированным набором прав. Просто, но
              грубо: не даёт контроль на уровне отдельной команды/запроса и требует ручной расстановки
              `[Authorize]` на каждом endpoint. Плохо ложится на CQRS-конвейер ICD и не даёт авто-покрытия
              новых операций.
            - **ABAC с периметр-тегами на claim.** Каждый claim несёт применимость (к каким объектам). Макс.
              гибкость, но заметно сложнее модель — а скоупить в ICD нечего. Избыточно.
            - **ABAC, плоские функциональные claims (выбрано).** Гранулярность по каждой команде/запросу/
              действию при человекочитаемой настройке (функции→группы). Единый словарь для backend и UI.
              Проверка в базовом хендлере по имени класса даёт авто-энфорс без ручной разметки и совместима
              с генераторами команд/запросов.

            Функции и группы сохраняем даже для небольшого приложения ради двух вещей: администратор управляет
            доступом без разработчика, и модель **консистентна с rtfm** (общий тулинг и подход в проектах
            организации) — перенос ADR и кода дешевле, чем изобретать упрощение.
            """;

        public static string S4_Consequences = """
            ## Следствия

            **Положительные:**
            - Тонкая гранулярность (по команде/запросу/действию) при человекочитаемой настройке.
            - Единый словарь claims для backend и UI; фронт получает только `icd/ui`-подмножество.
            - Авто-энфорс в базовом хендлере покрывает и текущие, и будущие команды/запросы.

            **Компромиссы:**
            - Модель тяжелее «двух ролей»: появляются сущности пользователей/функций/групп/токенов, посев
              функций, страница администрирования — оправдано управляемостью и преемственностью с rtfm.
            - Кэш контекста по `jti` требует инвалидации при изменении полномочий; между несколькими
              инстансами локальный кэш не распространяется (для мульти-инстанса — отдельно, отложено).

            **Вне охвата (точки расширения):**
            - **Периметр** — фильтрация данных по орг-структуре; вводится отдельным решением при появлении
              признака разграничения. Аддитивно: не затрагивает JWT/контекст/claims/guards, добавляет
              проверки только в те хендлеры, что работают с ограничиваемыми данными.
            - **Хеширование пароля** — на этапе разработки сид-пароль в открытом виде; хеш + соль до продакшна.
            - **OAuth2 / OIDC, refresh-токен** — позже.
            """;

        public static string S99_Related = $"""
            ## Связанные артефакты

            - {nameof(Ban.Sdaid.Icd.Vision.IcdVision)} — роли (хранитель, администратор) и открытый вопрос доступа `OQ-V-1`, закрываемый этим ADR.
            - {nameof(Ban.Sdaid.Icd.Arch.Adr.ADR_004_CreatorVsAssignee)} — «кто ввёл» vs «кто взял в работу»; поля пользователя, которые теперь заполняются из контекста.
            - {nameof(Ban.Sdaid.Icd.Requirements.FR_005_Verification)}, {nameof(Ban.Sdaid.Icd.Requirements.FR_009_PacketQueue)} — операции, доступность которых определяется правами.
            - Реализация решения (создаются далее): `ADR-SRV-006` (backend: JWT, авто-энфорс, сущности) и `ADR-UI-010` (аутентификация), `ADR-UI-011` (claims на UI).
            - Доменный модуль `50_Domain/Access/` (пользователи, функции, группы, claims, токены), endpoints `45_Api/Endpoints/Auth/` и хендлеры — оформляются отдельной задачей и получат `nameof`-ссылки отсюда.
            """;
    }
}