Обзор — что это даёт
Роли и команды — это информационные подписи для классификации людей. Единственное, что открывает доступ к экранам и действиям, — это уровень (user_level). Что пользователь видит и что ему разрешено сохранять или удалять, на 100 % определяется уровнем; роли («DBA», «DevOps», «Безопасность») и команды («Команда инфраструктуры», «Команда безопасности») — это теги для отображения и организационной структуры, они не дают никаких прав.
Так модель прав остаётся простой. Чтобы дать человеку права администратора, вы повышаете его уровень, а не меняете роль. Отметка «Безопасность» у пользователя не откроет экраны безопасности; его уровень должен быть Сотрудник (5) или выше.
В статье разобраны три вещи:
- Роли (
infr_role) — шесть должностных подписей (DBA, DevOps, Безопасность, Администратор, Сотрудник, Разработчик). - Команды (
infr_team) — организационные единицы, используются для принадлежности пользователей и назначения ответственных за активы. - Селектор ответственного — объединённый выпадающий список, в котором можно назначить либо пользователя, либо команду; его единственный источник истины —
App\Support\Owners. Поле «ответственный» на экранах Инстансы, Оборудование и Хранилище использует именно его.
Управление доступом = только уровень (
user_level). Роли и команды программно не открывают никаких прав. Об устройстве уровней см. Как устроены уровни (RBAC).
Когда это нужно
- При оформлении нового сотрудника — отметить его должность (роль) и принадлежность (команду).
- При реорганизации — создать команду или сменить её руководителя.
- Чтобы назначить ответственного (человека или команду) за серверные, аппаратные или дисковые активы, и было явно видно, «кто за это отвечает».
- Для аудита или передачи дел: выяснить, «кто отвечает за этот актив».
Структура экрана
/admin/users (Администрирование ▸ Пользователи и роли) содержит две вкладки.
| Вкладка | Содержимое |
|---|---|
| Пользователи | Список пользователей (логин · имя · email · роль · команда · уровень · статус · последний вход) + CRUD в модальном окне |
| Команды | Список команд (название · цвет · руководитель · описание · число участников) + CRUD в модальном окне |
В модальном окне редактирования пользователя есть селекторы уровня / роли / команды, а под ними такая подсказка:
Уровень определяет доступ — Демо (чтение), Сотрудник (работа), Администратор (всё). Роль — только подпись.
Как пользоваться
Роли — должностные подписи
Справочник ролей состоит из шести записей, которые InfrRoleSeeder идемпотентно записывает через upsert по key.
| key | Подпись | Токен цвета | seq |
|---|---|---|---|
dba |
DBA | info | 10 |
devops |
DevOps | accent | 20 |
security |
Безопасность | crit | 30 |
admin |
Администратор | warn | 40 |
staff |
Сотрудник | muted | 50 |
developer |
Разработчик | good | 60 |
- У пользователя может быть не более одной роли (
users.role_id, допускает null). Роль можно и не задавать. - Роли появляются только как подпись и цвет в пользовательском меню верхней панели и в бейджах списков.
- Отображаемую подпись возвращает
App\Support\RolePolicy::roleLabel($roleId)с кэшем на 60 секунд (после изменений вызывайтеRolePolicy::flush()).
Роль
admin(подпись «Администратор») не имеет никакого отношения к уровню администратора (10). Роль — всего лишь подпись, поэтому пользователь с ролью «Администратор», но уровнем Демо (1), не сможет ничего сохранить. И наоборот: администратор с уровнем 10 получает всё администрирование даже с пустой ролью.
Команды — организационные единицы
Команды свободно создаются и изменяются на вкладке Команды. Четыре команды из сидера:
| Название | Цвет | Описание |
|---|---|---|
| Команда инфраструктуры | accent | Эксплуатация серверов / сети / хранилищ |
| Команда безопасности | crit | Обнаружение · реагирование · блокировка |
| Команда разработки | good | Разработка сервисов · развёртывание |
| Команда DBA | info | Эксплуатация баз данных · тюнинг |
Поля редактирования команды:
| Поле | Значение |
|---|---|
Название (name) |
Обязательно. Название команды |
Цвет (color) |
Оттенок бейджа (accent/crit/good/info/muted …) |
Руководитель (lead_user_id) |
Один пользователь (необязательно) |
Описание (description) |
Произвольный текст |
Активна (is_active) |
Неактивные команды скрыты в селекторе ответственного |
- Пользователь входит не более чем в одну команду (
users.team_id, допускает null). - При удалении команды: у участников этой команды
team_idстановитсяnull(сами пользователи остаются, но уже без команды). Активы, которые ссылались на команду черезowner_type='team', — это отдельный вопрос, поэтому сами активы не удаляются.
Селектор ответственного — пользователи и команды вместе
У активов (серверные инстансы, оборудование, хранилища) есть поле «ответственный», в котором может быть как человек, так и команда. Правила этого объединённого селектора находятся в App\Support\Owners (SOT).
Форма хранения = два столбца
Таблица актива хранит ответственного в двух столбцах.
| Столбец | Значение |
|---|---|
owner_id |
Целочисленный первичный ключ пользователя или команды |
owner_type |
'user' или 'team' |
Форма обмена на фронтенде = одна строка ref
Браузер (формы, фильтры) обменивается owner_type и owner_id как одной строкой.
user:5 → user id 5
team:3 → team id 3
Начальные варианты (Owners::options()) возвращают две группы для отрисовки optgroup (кэш на 20 секунд). Закрытые пользователи (is_close=1) и неактивные команды (is_active=0) исключаются.
{
"users": [ { "ref": "user:5", "id": 5, "name": "Jane Doe" } ],
"teams": [ { "ref": "team:3", "id": 3, "name": "Infra Team" } ]
}
Разбор и сборка на стороне сервера
| Помощник | Поведение |
|---|---|
Owners::split($ref) |
"user:5" → ['user', 5]. Неверный формат → ['user', 0] (= не назначен) |
Owners::ref($ownerId, $ownerType) |
(5, 'user') → "user:5". Пустой ownerId → null |
Owners::resolveName($ownerId, $ownerType) |
Отображаемое имя для карточек и списков (null, если его нет, кэш на 20 секунд) |
Owners::flush() |
Сбросить кэш после изменений пользователей или команд |
Например, контроллер сохранения инстанса делает так:
// ref ответственного ("user:5"/"team:3") → owner_id + owner_type
[$ownerType, $ownerId] = Owners::split($request->input('owner_ref'));
$payload['owner_id'] = $ownerId ?: null;
$payload['owner_type'] = $ownerId ? $ownerType : 'user'; // по умолчанию 'user', если не задано
Тот же ref используется и при фильтрации по ответственному — запрос списка разбирает параметр owner (user:5/team:3) через split() и сужает выборку до WHERE owner_id = ? AND owner_type = ?.
Селектор ответственного в неизменном виде переиспользуется на экранах активов Инстансы, Оборудование и Хранилище и файлы. Назначение команды ответственной ставит на линию ответственности всю команду (это устойчиво к уходу любого одного человека).
Аватар · инициалы · цвет
Аватар рядом с подписью генерирует RolePolicy по имени.
- Инициалы (
initials): CJK и корейский → первый символ; латинские имена → первая буква первого и последнего слова (2 символа). - Оттенок (
hue):crc32(name) % 360— стабильный фон в HSL. - Оттенок уровня (
tierTone): администратор (≥10) = crit · сотрудник/работа (≥2) = accent · демо = muted.
Источники данных
| Таблица | Назначение |
|---|---|
infr_role |
Справочник ролей (6 информационных подписей) — key·name·color·seq·is_active |
infr_team |
Справочник команд — name·color·lead_user_id·description·is_active·seq |
users |
Пользователи — user_level (определяет доступ), role_id (допускает null), team_id (допускает null) |
| таблицы активов | Ответственный хранится в двух столбцах owner_id + owner_type (user/team) (Инстансы/Оборудование/Хранилище и др.) |
Помощники SOT:
App\Support\Owners— объединённый селектор ответственного (options/split/ref/resolveName/flush).App\Support\RolePolicy— подписи и оттенки уровней, подписи ролей (кэш на 60 секунд), инициалы и цвет аватара.
Права доступа
- Вход на весь экран доступен только администраторам.
/admin/*защищён групповым middlewareinfr.level:10, аUserController/TeamControllerдополнительно сами проверяютuser_level >= 10в сессии (RolePolicy::ADMIN). Демо (1) и Сотрудник (5) получают 403. - Пороги уровней (для справки): запись ≥ 2 (
RolePolicy::WRITE, блокирует демо) · администрирование ≥ 10 (ADMIN) · разработка и наблюдаемость ≥ 9 (DEV, включая администратора 10). - При сохранении пользователя
user_levelограничен значениями1,5,10. - Каждое изменение пользователя или команды записывается в аудит
infr_audit_log(team.create/team.update/team.delete, сохранение и удаление пользователей и т. д.).
Предохранители (защита последнего администратора)
| Ситуация | Результат |
|---|---|
| Понизить / отключить / удалить последнего активного администратора | Отклонено — «Последнего активного администратора нельзя понизить, отключить или удалить.» |
| Понизить или отключить собственный уровень администратора | Отклонено — «Нельзя понизить или отключить собственный доступ администратора.» |
| Удалить самого себя | Отклонено — «Нельзя удалить самого себя.» |
| Логин уже используется | Отклонено — «Этот логин уже используется.» |
Диагностика и частые вопросы
В. Я выдал человеку роль «Безопасность», но экраны безопасности не открываются. Роли — это подписи, они не открывают прав. Экранам безопасности (в основном на чтение) нужен уровень — поднимите его до Сотрудника (5) или выше. См. Уровни RBAC.
В. В выпадающем списке ответственных не хватает какого-то пользователя или команды.
Закрытые пользователи (is_close=1) и неактивные команды (is_active=0) исключаются из селектора. Активируйте пользователя или команду заново, а если список устарел — дождитесь истечения кэша на 20 секунд (Owners::flush() сбрасывает его немедленно).
В. Если удалить команду, исчезнут ли её люди?
Нет. Пользователи остаются; только их team_id становится null (без команды). Ссылка актива owner_type='team' существует отдельно, поэтому актив тоже не удаляется (просто отображение ответственного станет пустым).
В. Я сохранил ответственного, но имя не отображается.
У resolveName() кэш на 20 секунд. Если вы только что что-то переименовали, значение обновится в ближайшее время. Также проверьте, что формат ref — user:5/team:3: сломанный формат split() трактует как «не назначен» (user:0).
В. Может ли у пользователя быть две роли?
Нет. role_id — одиночное значение (допускающее null). Если нужна ещё и организационная группировка, используйте команду (тоже одиночное значение).
Смежные темы
- Как устроены уровни (RBAC) — единственная ось, которая действительно определяет доступ
- Консоль пользователей и команд — подробности CRUD на
/admin/users - Инстансы — ключевой экран активов, использующий селектор ответственного
- Оборудование — ответственные за физические активы
- Хранилище и файлы — ответственные за активы хранения
- Журнал аудита — история изменений пользователей, команд и ответственных
