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

Metabase Widgets Controller

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

Документ описывает runtime-flow запроса GET /api/metabase/widgets и связанных GET /api/metabase/widgets/{id}, GET /api/metabase/widgets/by-ids.

1. Контракт запроса

Эндпоинты:

- GET /api/metabase/widgets

- GET /api/metabase/widgets/{id}

- GET /api/metabase/widgets/by-ids

Поддерживаемая аутентификация:

  • TokenAuth:
  • Header token (основной вариант в OpenAPI)
  • Header Authorization: Bearer <token> (runtime fallback)
  • BasicAuth (LDAP):
  • Header Authorization: Basic <base64(login:password)>

Прочие параметры:

  • Header X-BMS-DOMEN — опциональный выбор источника данных для routing только для TokenAuth. Для LDAP BasicAuth заголовок игнорируется.
  • Query page (/api/metabase/widgets) — опциональный рекурсивный фильтр по имени вложенной папки (equalsIgnoreCase, после trim) или по collectionId.
  • Query structureOnly=true (/api/metabase/widgets) — вернуть дерево без embed JWT и без metadata lookup для каждого виджета.

2. Security chain и аутентификация

Фильтры для /api/metabase/**:

  1. RoutingDataSourceFilter
  2. BasicAuthenticationFilter (Spring Security)
  3. LdapAuthFilter
  4. TokenAuthFilter

Что происходит:

  1. RoutingDataSourceFilter для TokenAuth нормализует X-BMS-DOMEN, ищет domain -> instance_id в кеше on-prem boxes и выбирает source; для LDAP всегда используется default-source.
  2. BasicAuthenticationFilter + LdapAuthenticationProvider обрабатывают BasicAuth.
  3. LdapAuthFilter:
  4. берет uid (логин),
  5. берет entryUUID из LDAP principal (или делает lookup по uid),
  6. читает filter-теги пользователя из сервисной БД,
  7. собирает AccessContext из всех назначенных FILTER_BMS_ID / FILTER_INSTANCE_ID,
  8. формирует AuthenticatedUser(authSystemType=LDAP, ldapUserUuid=...),
  9. выставляет контекстные заголовки X-Ujin-User-Id, X-Ujin-Bms-Id.
  10. TokenAuthFilter срабатывает только если аутентифицированный пользователь еще не установлен; поэтому LDAP-пользователь не перетирается token-flow.
  11. Если ни LDAP, ни token не аутентифицировали пользователя, endpoint вернет 401.

Endpoint policy:

  • GET/PATCH /api/metabase/settings/widgets/access — только TokenAuth.
  • /api/metabase/settings/admin/** — только BasicAuth (LDAP) + login в app.ldap.adminLogins.
  • Widgets/Favorites/Analytics endpoints — допускают TokenAuth и BasicAuth.

3. Бизнес-алгоритм listWidgets

Метод: MetabaseWidgetsService.listWidgets(user, page).

3.1 Разрешенные виджеты и режим ACL

resolveAccessResolution(user):

  • Для AuthSystemType.TOKEN:
  • allowedWidgetIds из metabase_widget_access (source_key + bms_id + role_id).
  • applyRoleAccess=true.
  • Для AuthSystemType.LDAP:
  • allowedWidgetIds из metabase_tag_widget_access через теги пользователя.
  • deniedCollectionIds из metabase_user_tag_denied_collection через пользователя.
  • если список пустой, applyRoleAccess=false и используется доступ только от корневых коллекций.

3.2 Корневые коллекции

MetabaseCollectionResolver:

  • TOKEN: используется metabase.collections.root-names.token.
  • LDAP:
  • читаются теги пользователя из metabase_user_tag/metabase_access_tag;
  • root-коллекции определяются только тегами ROOT_COLLECTION;
  • применяются маппинги root-names.tag-roots (порядок как в YAML);
  • для unmapped тегов используется fallback: имя коллекции = tag.code.

Если имя коллекции не найдено в Metabase, сервис завершит запрос ошибкой конфигурации: No collection configured for name=....

3.3 Построение дерева и фильтры

Для каждой коллекции рекурсивно:

  • listSubcollections: GET /api/collection/{id}/items?models=collection
  • loadAllowedItems: GET /api/collection/{id}/items?models=dashboard,card

Фильтры MetabaseCollectionAccessService:

  • archived == false
  • role-tags (#ADMIN, #manager, ...)
  • BMS-tags (#BMS-<id> / #!BMS-<id>)
  • ACL-фильтр по allowedWidgetIds только когда applyRoleAccess=true

Важно: если userRole == null/blank, role-теги не ограничивают доступ. Это штатно для LDAP-flow.

Для token-flow с x_enterprise_id дерево дополнительно фильтруется по enterprise-тегам в description папок и виджетов:

  • стандартный формат: #ENTERPRISE_ID-<id>; теги можно перечислять через запятую;
  • если enterprise-теги не заданы, элемент считается общим;
  • если enterprise-теги заданы, элемент доступен только для matching x_enterprise_id;
  • фильтр применяется и к подпапкам: если подпапка не прошла по enterprise-тегу, вся её дочерняя ветка не сканируется.

Для LDAP-flow перед page-фильтром дополнительно:

  • из дерева вырезаются все подпапки из deny-list;
  • дочерние папки запрещенной подпапки не сканируются и их виджеты не попадают ни в tree, ни в index для /widgets/{id} и /widgets/by-ids.

После этого применяется page-фильтр:

  • поиск идет рекурсивно по всем уровням дерева;
  • совпадение по group.id == page или group.name.equalsIgnoreCase(page);
  • в ответ попадают только matching-группы со своей вложенной структурой.

4. Генерация embed JWT

Для каждого виджета:

  1. Нормализация типа: card -> question.
  2. Получение embedding_params (из collection item или через GET /api/dashboard/{id} / GET /api/card/{id}).
  3. EmbeddingParamsResolver разрешает locked-параметры (bms_id, bms_ids, instance_ids).
  4. Для TokenAuth поддерживается только bms_id как одиночное число.
  5. Для LDAP поддерживаются только bms_ids и instance_ids как списки значений из admin-assigned тегов.
  6. GuestTokenService подписывает JWT (HMAC256, metabase.embedding.secret, TTL из конфигурации).

Поведение при неизвестном locked-параметре:

  • /api/metabase/widgets — проблемный виджет пропускается.
  • /api/metabase/widgets/{id} и /api/metabase/widgets/by-ids403.

Если structureOnly=true:

  • embed JWT не генерируются;
  • GuestTokenService не вызывается;
  • metadata lookup для dashboard/card не делается.

5. Кеши

  • on-prem domain mapping: app.datasource-routing.mapping-cache-ttl-seconds (default 300)
  • root collection id: metabase.collections.root-ttl-seconds
  • collections list: metabase.collections.collections-ttl-seconds
  • collection items: metabase.collections.items-ttl-seconds
  • metadata (/api/card|dashboard/{id}): metabase.collections.metadata-ttl-seconds

6. Ошибки

  • 401 — нет валидной аутентификации
  • 403 — запрошенный виджет недоступен / ошибка locked-параметров
  • 502 — ошибка интеграции с Metabase
  • 400 — ошибка входных данных
  • 500 — внутренняя ошибка

7. Diagram (PlantUML)

  • docs/controllers/metabase-widgets-list-request.puml