Skip to content

Accounts and data

Введение

В многоуровневых страховых информационных системах часто возникает необходимость разделять бизнес-логику (портфели, продукты, договоры) и доступ пользователей к данным. Простое сопоставление «пользователь → права» быстро перестаёт работать, когда на систему выходят брокеры, маркетплейсы и крупные компании с разветвлённой структурой.

Для решения этой задачи используется account — базовая авторизационная единица.

Что такое Account

Account — это анонимная функциональная сущность в дереве компании. Он:

существует независимо от конкретных физических пользователей;

наделён определёнными ролями и правами доступа;

может рассматриваться как контейнер для бизнес-сущностей: договоров, клиентов, комиссий, заявок;

в страховой системе часто приравнивается к страховому портфелю.

Таким образом, account — это "виртуальный субъект", от имени которого работают пользователи.

Привязка пользователей

К одному account может быть привязано несколько логинов (учётных записей людей).
Это даёт следующие преимущества:

несколько сотрудников могут работать с одним и тем же портфелем (например, отдел корпоративных продаж);

один пользователь может иметь доступ к нескольким account (например, агент ведёт несколько страховых портфелей);

при смене сотрудников нет необходимости изменять сам account — достаточно переназначить привязку логинов.

Роли и права

У каждого account определяется набор ролей и прав, который определяет:

какие действия доступны (создание договора, изменение тарифа, просмотр аналитики);

какие продукты или клиенты входят в зону ответственности;

в каких границах действуют бизнес-ограничения (например, лимиты по сумме комиссий или тарифам).

Таким образом, права назначаются на account, а пользователи наследуют эти права через привязку к нему.

Account в дереве компании

Account встроен в иерархию компании:

на верхнем уровне могут быть глобальные account (например, маркетплейс в целом);

ниже — account брокеров или филиалов;

ещё ниже — account отдельных портфелей или команд.

Такое дерево позволяет:

централизованно контролировать права и настройки;

гибко делегировать доступ и ответственность;

агрегировать статистику и отчёты по уровням.

Account как портфель

В страховании account можно рассматривать как портфель:

в него входят договоры, клиенты, продукты;

к нему привязаны пользователи (агенты, менеджеры);

на уровне account настраиваются комиссии, лимиты и права;

статистика строится по account (объём премий, убытки, доходность).

Это позволяет бизнесу мыслить привычными категориями: портфель клиента = account в системе.

Преимущества модели

Гибкость управления доступом
Роли и права настраиваются для account, а пользователи просто подключаются к ним.

Масштабируемость
Дерево account позволяет описывать структуры от малых агентств до крупных холдингов.

Устойчивость к изменениям персонала\

При смене сотрудников не нарушается бизнес-логика.

Прозрачная отчетность
Все операции и результаты аккумулируются на уровне account (портфеля).

Система аутентификации и авторизации

1. Общая модель безопасности

В системе используются две независимые, но связанные подсистемы:
Аутентификация (Authentication) — отвечает на вопрос «Кто пользователь?»
Авторизация (Authorization) — отвечает на вопрос «Что и в каком контексте ему разрешено?»
Эти подсистемы разделены намеренно, чтобы:
упростить масштабирование,
обеспечить корректную работу multi-tenant модели,
поддержать impersonation и администраторские сценарии.

2. Аутентификация (кратко)

Система аутентификации описана в отдельном документе. Результат аутентификации:
пользователь успешно идентифицирован;
возвращается валидный account_id, к которому привязан логин;
формируется security context запроса.

📌 Важно: Аутентификация не определяет права, а только фиксирует, от имени какого account выполняется вход.

3. Иерархическая модель авторизации

В системе используется иерархическая (деревовидная) модель авторизации.

3.1 Структура иерархии

text
Tenant
 └── Client (партнёр)
      ├── (Admin nodes)
      │    ├── SYS_ADMIN
      │    ├── TENANT_ADMIN
      │    └── PRODUCT_ADMIN
      ├── Group
           ├── Portfolio (Account)
           |       ├── External Groups (ограниченный доступ)
           |       └── Policy and Quotes
           └── Group Admin

3.2 Основные принципы

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

4. Account как базовая единица авторизации

4.1 Что такое Account

Account — это:
узел в иерархии доступа;
контекст, в котором пользователь может действовать;
единица проверки прав.

С помощью account моделируются:
клиенты и партнёры,
страховые портфели,
группы пользователей,
администраторские контексты.

4.2 Привязка логина к account

Каждый логин:
** привязан ровно к одному account (в одной транзакции); **
наследует привилегии этого account;
не может действовать вне своего дерева.

Login → Account → Position in hierarchy → Privileges

5. Администраторские account’ы

5.1 Почему админы привязываются к клиенту, а не к tenant

Административные account’ы:
SYS_ADMIN
TENANT_ADMIN
PRODUCT_ADMIN
крепятся к Client, а не напрямую к Tenant.

Причины:
Ограничение области действия администраторов
Контроль приложений и каналов доступа
Поддержка white-label и partner-based deployment
Исключение «глобальных» админов без контекста

Таким образом:
администратор всегда работает в контексте конкретного клиента;
tenant-level доступ предоставляется явно, а не автоматически.

6. Principal Account vs Acting Account

6.1 Зачем нужны два account’а

В системе различаются два типа account: Тип Назначение Principal Account account, к которому привязан логин
Acting Account account, от имени которого выполняются операции

6.2 Поведение для обычных пользователей

Для обычных пользователей:
principalAccount == actingAccount
Права определяются напрямую положением account в дереве.

6.3 Поведение для администраторов

Для администраторов:
логин привязан к администраторскому account (Client-level)
доступ к данным выполняется от имени tenant account

principalAccount = CLIENT_ADMIN
actingAccount = TENANT

Это позволяет:
не дублировать данные,
централизованно проверять доступ,
корректно вести аудит.

7. Вычисление контекста доступа

7.1 Когда вычисляется acting account

actingAccount:
вычисляется один раз при создании security context
может использовать данные из БД
не изменяется в течение запроса

📌 Это происходит: в security-фильтрах или в UserDetailsFactory

8. Проверка прав доступа

8.1 Базовое правило доступа

Пользователь имеет доступ к ресурсу, если: actingAccount находится выше или совпадает с account ресурса в иерархии Иначе — доступ запрещён.

📌 Пример:

text
Tenant (path=1)
 └── Client (партнёр) (path=1.22)
      ├── (Admin nodes)
      │    ├── TENANT_ADMIN
      ├── Group (id=333 path=1.22.333)
      │    ├── Portfolio (Account id=111, path=1.22.333.111)
      │    |       └── Policy and Quotes
      │    └── Group Admin ( id = 222 )
      ├── Group (id=555 path=1.22.555)
           ├── Portfolio (Account id=112, path=1.22.555.112)
           |       └── Policy and Quotes
           └── Group Admin ( id = 223 )

Кто может выполнять действия с account.id=111. У этого узла path=1.22.333.111

у TENANT_ADMIN actingAccount = 1, он находится в том же дереве, выше. Поэтому у него есть доступ.
у Group Admin (id = 222) actingAccount = 333, path=1.22.333. Он находится в том же дереве, выше. Поэтому у него есть доступ.
у Group Admin (id = 223) actingAccount = 555, path=1.22.555. Он находится в том же дереве, но в соседней ветке. Поэтому у него доступа нет.

Так как path вычисляется в момент заведения account, фактически нужно сравнить 2 строки, что не требует обращений к БД и производится быстро.

8.2 Отсутствие отдельной проверки tenant

Отдельная проверка tenant не требуется, потому что:
account’ы разных tenant находятся в разных деревьях;
пересечение невозможно на уровне данных.
Проверка по иерархии автоматически исключает межтенантный доступ.

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

Каждая транзакция в системе происходит с правами какого-то account_id.
Этот account_id сохраняется вместе с договором страхования. Каждый договор иммет ссылку на path=1.22.555....
При запросе данных из БД, берется path account из текщей транзакции и добавляется в запрос.
Текуущий account имеет доступ ко всем данным из его узла и ниже по дереву.
Поэтому в SQL это выглядит как

sql
WHERE policy.account_path like session_account.path||'%'

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

Второе правило ограничивающее доступ к данным, каждая роль имеет свой dataScope. Раздел бизнес данных в ЬД, которые она видит. dataScope задается при заполнении security сontext. для GROUP_ADMIN, ACCOCUNT, SUB - dataScope = 'PROD' для PRODUCT_ADMIN - dataScope = 'DEV' для остальных ролей dataScope пустой.

На что это влияет: "продажные" роли GROUP_ADMIN, ACCOCUNT, SUB работают с бизнес данными из scope = PROD. Это реальнуе договоры, проданные страхователям. Так же для котировок используются настройки продуктов выведенные в прод. "продуктовые" роли PRODUCT_ADMIN не видят перс данных с прода. Могут создавать нове версии продуктов в статусе DEV, и при создании котировок используется именно эта версия продукта. Это позволяет протестировать новый продукт или новую версию продукта. Получить ПФ, проверить весь цикл продажи, но в изолированном пространстве данных. После публикации версии в прод, продукт становится недоступен для роли. остальные роли не могут создавать котировки и работать с продуктами вообще.

sql
WHERE policy.data_scope session_account.dataScope

9. Результат авторизации

При отсутствии доступа выбрасывается 403 Forbidden
Ошибка означает: «Пользователь аутентифицирован, но не имеет прав на выполнение операции»

10. Аудит и трассируемость

Каждое действие может быть однозначно зааудировано:
principalAccount → actingAccount → resource → action

Это обеспечивает:
корректный audit trail
поддержку impersonation
анализ инцидентов безопасности

11. Модель привилегий (Privileges Model)

12.1 Общий принцип

Каждое действие в системе требует наличия явной привилегии.
Привилегия описывается строкой вида:

<resource>:<action>

Примеры:

POLICY:CREATE
POLICY:VIEW
POLICY:EDIT
CLAIM:REGISTER
PRODUCT:SELL
DOCUMENT:GENERATE

📌 Отсутствие привилегии всегда означает запрет, даже если иерархический доступ разрешён.

11.2 Роль ≠ привилегия

В системе роли не используются напрямую для проверки доступа.
Роль — это:
логическая группировка
преднастроенный набор привилегий

Привилегия — это:
минимальная атомарная единица доступа
объект проверки в AuthorizationService

11.3 Связь ролей и привилегий

Каждой роли сопоставлен набор привилегий, определённый в коде. Пример ролей:

ADMIN
ACCOUNT
PRODUCT_ADMIN
READ_ONLY

Каждая роль содержит фиксированный список допустимых операций. Пример (AuthZ.class)

public final class AuthZ {

    public enum Role {
        ADMIN,
        SELLER,
        PRODUCT_ADMIN
    }

    public static final Map<Role, Set<String>> ROLE_PRIVILEGES = Map.of(
        Role.ADMIN, Set.of(
            "POLICY:CREATE",
            "POLICY:EDIT",
            "POLICY:VIEW",
            "CLAIM:REGISTER",
            "DOCUMENT:GENERATE"
        ),
        Role.SELLER, Set.of(
            "POLICY:CREATE",
            "POLICY:VIEW",
            "PRODUCT:SELL"
        ),
        Role.PRODUCT_ADMIN, Set.of(
            "PRODUCT:CREATE",
            "PRODUCT:EDIT",
            "PRODUCT:PUBLISH"
        )
    );
}

11.4 Проверка доступа (полная схема)

Для выполнения любого действия система выполняет две обязательные проверки:

  1. Иерархическая проверка доступа

Проверяется, что:

actingAccount ⊇ resourceAccount

То есть acting account пользователя: совпадает с account ресурса или находится выше в иерархии

  1. Проверка привилегии

Проверяется, что роль пользователя содержит привилегию:

<resource>:<action>

Если хотя бы одна из проверок не пройдена → доступ запрещён.

11.5 Итоговое правило доступа

Access allowed ⇔ hierarchyCheck == true AND privilegeCheck == true

11.6 Почему привилегии определены в коде

Привилегии хранятся в AuthZ.class, а не в БД, потому что:
они являются частью архитектурного контракта
меняются редко
упрощают анализ безопасности
исключают «магические» права в данных

11.7 Поведение при отсутствии привилегии

Если у пользователя: есть иерархический доступ но нет привилегии, результат: 403 Forbidden

Семантика:

«Доступ к ресурсу существует, но действие запрещено»

12 Список ролей

  • SYS_ADMIN - админ, создается при установке систему. Так же создается тенант sys, и приложение для администирования - sys. SYS_ADMIN имеет права только в своем тенанте, может создавать новые тенанты и назначать в них админов TNT_ADMIN. На этом права SYS_ADMIN заканиваются. Все остальное нужно делать уже в конкретном тенанте под ролью TNT_ADMIN.
  • TNT_ADMIN - администратор тенанта. Администрирует заведение клиентов, управляет заведенеим пользователей, управляет ролями - PRODUCT_ADMIN, GROUP_ADMIN. Управляет конфигурацие клиента, данные по агентскому договору.
  • GROUP_ADMIN - Администратор группы продавцов, ( CLIENT так же является группой ). Может добавлять, удалять персональные учетки, управлять продуктовыми правами на каждом account. В общем случае это руководитель группы продаж.
  • PRODUCT_MANAGER - продуктовик в рамках тенанта. Позволяет управлять продуктовым портфелем, настраивать продукты, продуктовые группы, давать доступ к продуктам клиентам. PRODUCT_ADMIN делает продукт доступным клиенту, но управляет продажами GROUP_MANAGER.
  • ACCOUNT - роль агента/продавца. Может создавать договор, изменять, оплачивать и т.д. в рамках продуктовых прав, настроенных GROUP_MANAGER.
  • SUB - тоже самое что ACCOUNT, но с ограниченными правами.

Ресурсы

РесурсРазрешениеОписаниеSYS_ADMINTNT_ADMINGROUP_ADMINPRODUCT_ADMINACCOUNTSUB
TENANTLISTПолучить список
VIEWПолучить иформацию об объекте
MANAGECRUD
CLIENTLISTПолучить список
VIEWПолучить иформацию об объекте
MANAGECRUD
CLIENT_PRODUCTLISTПолучить список
VIEWПолучить иформацию об объекте
MANAGECRUD
TENANT_ADMINLISTПолучить список
VIEWПолучить иформацию об объекте
VIEWCRUD
LOBLISTПолучить список
VIEWПолучить иформацию об объекте
MANAGECRUD
PRODUCTLISTПолучить список
VIEWПолучить иформацию об объекте
MANAGECRUD

| ACCOUNT | MANAGE | Получить список | | | | | | | | | MANAGE | Получить иформацию об объекте | | | | | | | | | MANAGE | CRUD | | | | | | | | ACCOUNT_PRODUCT | LIST | Получить список | | | | | | | | | MANAGE | Получить иформацию об объекте | | | | | | | | | MANAGE | CRUD | | | | | | | | CONTRACT | MANAGE | Получить список | | | | | | | | | MANAGE | | | | | | | | | | MANAGE | | | | | | | | | POLICY | TEST | | | | | | | | | | QUOTE | | | | | | | | | | SELL | | | | | | | |

Связанные документы

Продукты и договоры страхования

Доступность продуктов

Доступность продукта задается на уровне клиента (client). Для каждого клиента указывается:

  • Какой продукт может быть доступен
  • Условия агентского договора:
  1. Номер агентского договора
  2. Тип комиссии (процент или фиксированная сумма)
  3. Значение комиссии

Разрешения на действия с продуктом

Разрешения на действия с продуктом задаются на любом уровне иерархии:

  • Клиент (client) — верхний уровень
  • Группа (group) — внутренняя группировка внутри клиента
  • Аккаунт (account) — конкретный пользователь/подразделение
  • Субаккаунт (subaccount) — дочерний аккаунт

Действия, для которых могут быть заданы разрешения:

ДействиеОписание
VIEWПросмотр договора
QUOTEПолучение котировки
CREATEСоздание договора
EDITРедактирование договора
RENEWПролонгация договора
CANCELРасторжение договора
PAYОплата договора

Алгоритм вычисления разрешений

При поступлении запроса на действие с договором страхования: Определяется продукт и тип запрашиваемого действия Выполняется поиск по дереву accounts от текущего узла вверх до корня (клиента) Находится первая (ближайшая к текущему account) запись, связанная с интересующим продуктом Извлекаются разрешения на действия из найденной записи

Преимущества подхода: Гибкая настройка разрешений на любом уровне иерархии Возможность переопределения разрешений для конкретных групп или пользователей Единая точка управления доступом

Техническая реализация

Материализованный путь (Materialized Path)

Для ускорения поиска разрешений в таблице acc_product_roles используется материализованный путь (id_path). Структура пути: Путь строится от корня (клиента) до текущего узла Уровни разделяются точкой Пример: "1002.1003.1005" означает: клиент=1002 → группа=1003 → аккаунт=1005 Преимущества перед рекурсивными CTE:

✅ Значительно выше производительность (один индексный поиск вместо рекурсии) ✅ Меньше нагрузка на базу данных ✅ Проще в отладке и поддержке

Запрос поиска разрешений

sql
-- Поиск ближайшей записи с разрешениями для текущего аккаунта
SELECT DISTINCT ON (product_id) 
    t.*
FROM acc_product_roles t
WHERE '1002.1003.1005' LIKE (t.id_path || '%')  -- путь текущего аккаунта
  AND t.product_id IN (1, 2, 3)                  -- интересующие продукты
ORDER BY product_id, LENGTH(t.id_path) DESC;    -- DESC = ближайший к аккаунту

Как это работает: '1002.1003.1005' LIKE (id_path || '%') — находит все записи, чей путь является префиксом пути текущего аккаунта (все родительские узлы)
ORDER BY LENGTH(id_path) DESC — сортирует так, что ближайший к аккаунту узел (с самым длинным путем) идет первым
DISTINCT ON (product_id) — для каждого продукта берет первую (ближайшую) запись

Важное замечание о совместимости

⚠️ Конструкция SELECT DISTINCT ON (product_id) является специфичной для PostgreSQL и не поддерживается другими СУБД (MySQL, Oracle, SQL Server).
Альтернативный (стандартный) запрос с ROW_NUMBER():

sql
WITH ranked AS (
    SELECT 
        t.*,
        ROW_NUMBER() OVER (
            PARTITION BY product_id 
            ORDER BY LENGTH(id_path) DESC
        ) AS rn
    FROM acc_product_roles t
    WHERE '1002.1003.1005' LIKE (t.id_path || '%')
      AND t.product_id IN (1, 2, 3)
)
SELECT * FROM ranked WHERE rn = 1;

Примеры иерархии

text
Клиент (tenant_id=1002)
    id_path = "1002"

    ├── Группа "Агенты" (id=1003)
    │       id_path = "1002.1003"
    │       │
    │       ├── Аккаунт "Иванов" (id=1005)
    │       │       id_path = "1002.1003.1005"
    │       │       permissions для продукта "ОСАГО": ["VIEW", "CREATE", "PAY"]
    │       │
    │       └── Аккаунт "Петров" (id=1006)
    │               id_path = "1002.1003.1006"

    └── Группа "Прямые продажи" (id=1004)
            id_path = "1002.1004"
            permissions для продукта "ОСАГО": ["VIEW", "QUOTE"]

Приоритет разрешений:
Для аккаунта "Иванов" (путь 1002.1003.1005) будут действовать разрешения, заданные на уровне его аккаунта
Для аккаунта без собственных разрешений будут действовать разрешения родительской группы
На уровне клиента задаются разрешения по умолчанию

PxP PoliTech — Direct Insurance Platform