Перейти к содержанию

Metabase Admin Controller

Контроллер: tech.ujin.api.AdminAccessController

Эндпоинты

- GET /api/metabase/settings/admin/users - Возвращает список LDAP-сотрудников (uid, displayName, givenName, sn, mail, entryUUID) и назначенные им теги. - GET /api/metabase/settings/admin/is-admin - Возвращает true, если текущий LDAP-логин присутствует в app.ldap.adminLogins, иначе false. - GET /api/metabase/settings/admin/build-info - Возвращает версию приложения, commit id и build metadata текущей сборки. - GET /api/metabase/settings/admin/tags - Возвращает список тегов доступа с type и value. - POST /api/metabase/settings/admin/tags - Создает или обновляет тег по code. - Если type не передан, используется ROOT_COLLECTION. - DELETE /api/metabase/settings/admin/tags/{tagId} - Удаляет тег доступа и связанные назначения/матрицу доступов. - PUT /api/metabase/settings/admin/users/{userId}/tags - Полностью заменяет набор тегов пользователя (userId = LDAP entryUUID). - GET /api/metabase/settings/admin/users/{userId}/tags/{tagId}/subfolders - Возвращает дерево доступных Metabase-подпапок для выбранного пользовательского тега. - Поле available показывает effective access с учетом наследования deny от родительской папки. - PUT /api/metabase/settings/admin/users/{userId}/tags/{tagId}/subfolders - Полностью заменяет deny-list подпапок для выбранного пользовательского тега. - В хранилище сохраняются только явно запрещенные id подпапок. - GET /api/metabase/settings/admin/widgets/access?tagId=... - Возвращает матрицу доступности виджетов/категорий для выбранного тега. - PATCH /api/metabase/settings/admin/widgets/access - Включает или отключает доступ к виджету/категории для выбранного тега. - GET /api/metabase/settings/admin/on-prem-boxes - Возвращает список on-prem коробок с маппингом domain -> instanceId. - POST /api/metabase/settings/admin/on-prem-boxes - Создает или обновляет on-prem коробку по домену. - DELETE /api/metabase/settings/admin/on-prem-boxes/{id} - Удаляет on-prem коробку. - GET /api/metabase/settings/admin/sql/preview?resourceType=...&resourceId=... - Возвращает formatted preview SQL для question или dashboard. - POST /api/metabase/settings/admin/sql/export?resourceType=...&resourceId=... - Выгружает question, dashboard или collection в git-ready файловую структуру. - POST /api/metabase/settings/admin/sql/publish?resourceType=...&resourceId=... - Делает export, сравнивает snapshot с GitLab и создает commit + merge request. - POST /api/metabase/settings/admin/sql/deploy - Читает snapshot из GitLab и применяет его в текущий Metabase как upsert по entityId. - При пустом body берется HEAD ветки gitlab.target-branch. - Поддерживает два варианта доступа: LDAP admin BasicAuth или shared secret header из metabase.deploy.*.

Security

  • Большинство admin endpoints доступны только через LDAP BasicAuth.
  • Для LDAP-сценария логин пользователя должен быть в app.ldap.adminLogins.
  • TokenAuth на /api/metabase/settings/admin/** отклоняется политикой SecurityConfig (403).
  • GET /api/metabase/settings/admin/is-admin также требует LDAP BasicAuth; для LDAP-пользователя вне whitelist возвращает false.
  • Header X-BMS-DOMEN на admin endpoints не используется.
  • Исключение: git-flow endpoints POST /api/metabase/settings/admin/sql/export, POST /api/metabase/settings/admin/sql/publish и POST /api/metabase/settings/admin/sql/deploy принимают либо LDAP BasicAuth, либо deploy token header.
  • По умолчанию используется header X-Metabase-Deploy-Token, но имя заголовка может быть переопределено через metabase.deploy.token-header-name.
  • Внутренний prod endpoint POST /api/internal/metabase/deploy не использует LDAP/TokenAuth и защищается shared secret header из metabase.deploy.*.

Бизнес-логика

  • Пользователи для /admin/users читаются из LDAP (LdapDirectoryService), теги подтягиваются из сервисной БД.
  • Теги (/admin/tags) хранятся в metabase_access_tag.
  • On-prem boxes (/admin/on-prem-boxes) хранятся в metabase_on_prem_box как domain -> instance_id.
  • Если домен не найден в metabase_on_prem_box, token runtime-routing уходит в default-source (SaaS).
  • Runtime cache domain -> instance_id инвалидируется после create/update/delete on-prem box.
  • Поддерживаются типы тегов:
  • ROOT_COLLECTION — назначает корневую коллекцию LDAP-пользователя.
  • FILTER_BMS_ID — добавляет одно значение в список locked bms_ids.
  • FILTER_INSTANCE_ID — добавляет одно значение в список locked instance_ids.
  • Связь пользователь-тег хранится в metabase_user_tag (user_id — UUID LDAP пользователя).
  • Запреты подпапок хранятся в metabase_user_tag_denied_collection (user_id, tag_id, root_collection_id, collection_id).
  • Доступ тега к виджетам хранится в metabase_tag_widget_access (bms_id, tag_id, widget_id).
  • Для поддерева подпапок используется default-open модель: новые подпапки ничего не требуют в БД и автоматически доступны, пока не появится явный deny.
  • При расчете матрицы доступа используется тот же каталог Metabase, что и в settings/widgets flow.
  • Scope категорий/виджетов ограничен корневыми коллекциями текущего LDAP-администратора (MetabaseCollectionResolver.resolveRootCollectionIds(actor)).
  • Scope подпапок ограничен root-коллекцией того тега, который уже назначен выбранному пользователю.
  • Для одного пользователя допускается любое количество тегов всех трех типов.
  • Effective filter-списки для LDAP формируются объединением всех назначенных FILTER_BMS_ID и FILTER_INSTANCE_ID.
  • При замене deny-list сервис канонизирует вход: если запрещена родительская подпапка, ее дочерние id отдельно не сохраняются.
  • Для git-based аналитики используется отдельный GitLab-проект только с export-файлами.
  • publish на staging создает MR в этот проект, а deploy на prod читает текущее snapshot-состояние ветки или точного commit SHA и делает snapshot-based sync.
  • Один и тот же deploy token из metabase.deploy.* может использоваться как для internal endpoint, так и для POST /api/metabase/settings/admin/sql/deploy, если LDAP-admin недоступен.
  • Collections, questions/cards и dashboards синхронизируются по стабильному entityId.
  • Полное archive/delete отсутствующих сущностей пока не включено.
  • Dashboard tabs сейчас не создаются автоматически: карточка будет привязана к tab только если на целевом стенде уже есть tab с таким именем.

Git-flow пример переноса коллекции между стендами

Пример ниже описывает ручной процесс для коллекции QA. По смыслу этот же flow должен запускаться из UI основного сервиса кнопкой рядом с действиями вроде избранного или заказа аналитики: если deploy включен и текущий пользователь является администратором, он может опубликовать в GitLab всю коллекцию, папку, dashboard или отдельный виджет через POST /api/metabase/settings/admin/sql/publish.

  1. На тестовом стенде менеджер готовит изменения в Metabase. Пример исходной коллекции: https://metabase.unicorn.icu/collection/50-qa.

  2. Администратор вызывает publish для коллекции 50.

Пример команды:

PUBLISH_URL='https://ujin-api.unicorn.icu/api/metabase/settings/admin/sql/publish'

curl -X 'POST' \
  "${PUBLISH_URL}?resourceType=collection&resourceId=50&createArchitectReviewTask=false" \
  -H 'accept: application/json' \
  -H 'X-Metabase-Deploy-Token: change-me' \
  -d ''

Параметры resourceType:

  • collection или folder — экспорт всей папки/коллекции рекурсивно;
  • dashboard — экспорт dashboard и связанных SQL questions/cards;
  • question или card — экспорт отдельного виджета/question.

  • Сервис выгружает snapshot, сравнивает его с GitLab и создает merge request. Пример MR: https://gitlab.unicorn.icu/ujin/metabase-collection/-/merge_requests/6.

  • Сревис создает задачу в Jira на Архитектора с указанием МР.

  • Ответственный reviewer проверяет diff и мержит MR в main. Права на merge в main настраиваются в GitLab; список ответственных владельцев процесса описывается отдельно.

  • После merge snapshot из main применяется на втором стенде. Сейчас это можно вызвать вручную:

curl -X 'POST' \
  'https://ujin-api-git.unicorn.icu/api/metabase/settings/admin/sql/deploy' \
  -H 'accept: application/json' \
  -H 'X-Metabase-Deploy-Token: change-me' \
  -H 'Content-Type: application/json' \
  -d '{
  "ref": "main"
}'

Целевое состояние процесса: этот шаг должен выполняться автоматически после merge в main, например GitLab CI/webhook вызывает deploy endpoint второго стенда.

  1. На втором стенде коллекция появляется или обновляется по стабильным entityId. Пример результата: https://metabase-git.unicorn.icu/collection/54-qa. При корректном snapshot deploy не должен создавать дубли и мусорные папки.

Валидация

  • code тега: ^[a-z0-9_\\-]+$, max 128.
  • name тега: max 255.
  • domain on-prem box: должен нормализоваться в непустой hostname.
  • instanceId on-prem box:
  • обязателен;
  • должен совпадать с одним из настроенных app.datasource-routing.sources.*;
  • не должен совпадать с default-source.
  • value:
  • обязателен и должен быть положительным числом для FILTER_BMS_ID;
  • обязателен и должен быть непустой строкой для FILTER_INSTANCE_ID;
  • должен отсутствовать для ROOT_COLLECTION.
  • tagId, targetId > 0.
  • deniedFolderIds[*] > 0.
  • targetType: WIDGET или CATEGORY.

Коды ошибок

  • 400 — невалидные параметры или цель отсутствует в доступном scope.
  • 401 — нет аутентификации.
  • 403 — логин не в whitelist или неверный тип auth.
  • 500 — внутренняя ошибка.