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 Структура иерархии
Tenant
└── Client (партнёр)
├── (Admin nodes)
│ ├── SYS_ADMIN
│ ├── TENANT_ADMIN
│ └── PRODUCT_ADMIN
├── Group
├── Portfolio (Account)
| ├── External Groups (ограниченный доступ)
| └── Policy and Quotes
└── Group Admin3.2 Основные принципы
Все сущности доступа представлены как Account
Account образуют изолированные деревья
Деревья разных tenant никогда не пересекаются
Доступ определяется положением в дереве, а не только ролью
4. Account как базовая единица авторизации
4.1 Что такое Account
Account — это:
узел в иерархии доступа;
контекст, в котором пользователь может действовать;
единица проверки прав.
С помощью account моделируются:
клиенты и партнёры,
страховые портфели,
группы пользователей,
администраторские контексты.
4.2 Привязка логина к account
Каждый логин:
** привязан ровно к одному account (в одной транзакции); **
наследует привилегии этого account;
не может действовать вне своего дерева.
Login → Account → Position in hierarchy → Privileges5. Администраторские 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 ресурса в иерархии Иначе — доступ запрещён.
📌 Пример:
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 это выглядит как
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, и при создании котировок используется именно эта версия продукта. Это позволяет протестировать новый продукт или новую версию продукта. Получить ПФ, проверить весь цикл продажи, но в изолированном пространстве данных. После публикации версии в прод, продукт становится недоступен для роли. остальные роли не могут создавать котировки и работать с продуктами вообще.
WHERE policy.data_scope session_account.dataScope9. Результат авторизации
При отсутствии доступа выбрасывается 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 Проверка доступа (полная схема)
Для выполнения любого действия система выполняет две обязательные проверки:
- Иерархическая проверка доступа
Проверяется, что:
actingAccount ⊇ resourceAccountТо есть acting account пользователя: совпадает с account ресурса или находится выше в иерархии
- Проверка привилегии
Проверяется, что роль пользователя содержит привилегию:
<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_ADMIN | TNT_ADMIN | GROUP_ADMIN | PRODUCT_ADMIN | ACCOUNT | SUB |
|---|---|---|---|---|---|---|---|---|
| TENANT | LIST | Получить список | ✅ | ✅ | ||||
| VIEW | Получить иформацию об объекте | ✅ | ✅ | |||||
| MANAGE | CRUD | ✅ | ✅ | |||||
| CLIENT | LIST | Получить список | ✅ | ✅ | ||||
| VIEW | Получить иформацию об объекте | ✅ | ✅ | |||||
| MANAGE | CRUD | ✅ | ✅ | |||||
| CLIENT_PRODUCT | LIST | Получить список | ✅ | ✅ | ✅ | ✅ | ||
| VIEW | Получить иформацию об объекте | ✅ | ✅ | ✅ | ✅ | |||
| MANAGE | CRUD | ✅ | ||||||
| TENANT_ADMIN | LIST | Получить список | ||||||
| VIEW | Получить иформацию об объекте | |||||||
| VIEW | CRUD | |||||||
| LOB | LIST | Получить список | ||||||
| VIEW | Получить иформацию об объекте | |||||||
| MANAGE | CRUD | |||||||
| PRODUCT | LIST | Получить список | ||||||
| VIEW | Получить иформацию об объекте | |||||||
| MANAGE | CRUD |
| ACCOUNT | MANAGE | Получить список | | | | | | | | | MANAGE | Получить иформацию об объекте | | | | | | | | | MANAGE | CRUD | | | | | | | | ACCOUNT_PRODUCT | LIST | Получить список | | | | | | | | | MANAGE | Получить иформацию об объекте | | | | | | | | | MANAGE | CRUD | | | | | | | | CONTRACT | MANAGE | Получить список | | | | | | | | | MANAGE | | | | | | | | | | MANAGE | | | | | | | | | POLICY | TEST | | | | | | | | | | QUOTE | | | | | | | | | | SELL | | | | | | | |
Связанные документы
Продукты и договоры страхования
Доступность продуктов
Доступность продукта задается на уровне клиента (client). Для каждого клиента указывается:
- Какой продукт может быть доступен
- Условия агентского договора:
- Номер агентского договора
- Тип комиссии (процент или фиксированная сумма)
- Значение комиссии
Разрешения на действия с продуктом
Разрешения на действия с продуктом задаются на любом уровне иерархии:
- Клиент (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:
✅ Значительно выше производительность (один индексный поиск вместо рекурсии) ✅ Меньше нагрузка на базу данных ✅ Проще в отладке и поддержке
Запрос поиска разрешений
-- Поиск ближайшей записи с разрешениями для текущего аккаунта
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():
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;Примеры иерархии
Клиент (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) будут действовать разрешения, заданные на уровне его аккаунта
Для аккаунта без собственных разрешений будут действовать разрешения родительской группы
На уровне клиента задаются разрешения по умолчанию