Widgets flow (PlantUML)¶ Диаграмма рендерится из исходника .puml: GET /api/metabase/widgets - runtime sequence (Token + LDAP)ClientSecurityFilterChainRoutingDataSourceFilterBasicAuthenticationFilterLdapAuthFilterTokenAuthFilterTokenAuthenticationProviderUserAuthServiceUserDataRepositoryMetabaseWidgetsControllerCurrentUserServiceMetabaseWidgetsServiceEmployeeAccessRepositoryRoleWidgetAccessRepositoryMetabaseCollectionResolverMetabaseCollectionAccessServiceMetabaseClientEmbeddingParamsResolverGuestTokenServiceClientSecurityFilterChainRoutingDataSourceFilterBasicAuthenticationFilterLdapAuthFilterTokenAuthFilterTokenAuthenticationProviderUserAuthServiceUserDataRepositoryMetabaseWidgetsControllerCurrentUserServiceMetabaseWidgetsServiceEmployeeAccessRepositoryRoleWidgetAccessRepositoryMetabaseCollectionResolverMetabaseCollectionAccessServiceMetabaseClientEmbeddingParamsResolverGuestTokenServiceClientSecurityFilterChainRoutingDataSourceFilterBasicAuthenticationFilterLdapAuthFilterTokenAuthFilterTokenAuthenticationProviderUserAuthServiceUserDataRepositoryMetabaseWidgetsControllerCurrentUserServiceMetabaseWidgetsServiceEmployeeAccessRepositoryRoleWidgetAccessRepositoryMetabaseCollectionResolverMetabaseCollectionAccessServiceMetabaseClientEmbeddingParamsResolverGuestTokenService1GET /api/metabase/widgets?page=...Headers: Authorization, token, X-BMS-DOMEN(optional for TokenAuth)2doFilter3resolve source by X-BMS-DOMEN for TokenAuthunknown/missing/LDAP -> default-source4continue5attempt BasicAuthalt[Authorization Basic present and LDAP bind success]6Authentication(principal=DirContextOperations)[no Basic header or bind failed]7continue8doFilteralt[current authentication is LDAP principal]9resolve username (uid)10resolve entryUUIDalt[entryUUID missing in principal]11LDAP search by uid12findTagsByUserId(ldapUserUuid)13assigned tags14derive AccessContext from all FILTER_BMS_ID / FILTER_INSTANCE_ID tags15convert to AuthenticatedUser(authSystem=LDAP)16set request attr ujin.ldapLogin[already AuthenticatedUser or anonymous]17skip conversion18continue19doFilteralt[AuthenticatedUser already exists (e.g. LDAP)]20skip token auth[no authenticated user yet]21resolve token (header token first, then Bearer)alt[token exists]22authenticate(token)23authenticate(rawToken)24findByToken(rawToken)25Optional<UserData>26Optional<UserData>alt[valid user]27Authentication(AuthenticatedUser, authSystem=TOKEN)28set SecurityContext + headers[invalid token]29AuthenticationException30clear SecurityContext[token missing]31skip auth32authorize requestalt[unauthenticated]33401 Authentication required[authenticated]34listWidgets(page)35currentUser()36AuthenticatedUser37listWidgets(user, page)38resolveAccessResolution(user)alt[user.authSystem == LDAP]39findAllowedWidgetIdsByUser(bmsIds[], ldapUserUuid)40Set<Long>41findDeniedSubfolderIdsByUser(ldapUserUuid)42Set<Long>alt[ACL set is empty]43applyRoleAccess=false44roleForCollectionFilter=null[ACL set not empty]45applyRoleAccess=true46roleForCollectionFilter=null47resolveRootCollectionIds(user)48findTagsByUserId(ldapUserUuid)49assigned tags50keep only ROOT_COLLECTION tags51apply tag-roots mapping in YAML order52unmapped tag -> fallback collection name = tag.code53listCollections()54collections[]55rootCollectionIds[user.authSystem == TOKEN]56findAllowedWidgetIds(bmsId, roleId, source_key)57Set<Long>58resolveRootCollectionIds(user)59listCollections()60collections[]61rootCollectionIds62applyRoleAccess=true63roleForCollectionFilter=user.roleloop[recursive collections tree from all rootCollectionIds]64listSubcollections(collectionId)65GET /api/collection/{id}/items?models=collection66subcollections[]alt[LDAP denied subfolder]67skip subtree[visible collection]68loadAllowedItems(collectionId, userRole, bmsIds|bmsId, allowedWidgetIds, applyRoleAccess)69GET /api/collection/{id}/items?models=dashboard,card70items[]71filter archived72filter role tags (#ROLE)73filter BMS tags (#BMS / #!BMS)74apply allowedWidgetIds only when applyRoleAccess=true75allowed widgets[]alt[token request with x_enterprise_id]76filter enterprise tags in folder/widget description (#ENTERPRISE_ID-<id>)loop[each allowed widget]alt[structureOnly=true]77return widget without token[structureOnly=false]alt[embedding_params already present in collection item]78resolveLockedParams(user, metadata)[metadata required]79GET /api/{dashboard|card}/{id}80resource metadata.embedding_params81resolveLockedParams(user, metadata)alt[locked params supported]82params map (Token: bms_id scalar; LDAP: bms_ids / instance_ids lists)83createToken(type, id, params)84signed token[unknown locked param]85WidgetAccessException86log warn and skip widget (for list endpoint)opt[page is provided]87recursively keep groups where name.equalsIgnoreCase(page)or group.id == page88WidgetsResponse(groups)89200 JSONGET /api/metabase/widgets - runtime sequence (Token + LDAP)ClientSecurityFilterChainRoutingDataSourceFilterBasicAuthenticationFilterLdapAuthFilterTokenAuthFilterTokenAuthenticationProviderUserAuthServiceUserDataRepositoryMetabaseWidgetsControllerCurrentUserServiceMetabaseWidgetsServiceEmployeeAccessRepositoryRoleWidgetAccessRepositoryMetabaseCollectionResolverMetabaseCollectionAccessServiceMetabaseClientEmbeddingParamsResolverGuestTokenServiceClientSecurityFilterChainRoutingDataSourceFilterBasicAuthenticationFilterLdapAuthFilterTokenAuthFilterTokenAuthenticationProviderUserAuthServiceUserDataRepositoryMetabaseWidgetsControllerCurrentUserServiceMetabaseWidgetsServiceEmployeeAccessRepositoryRoleWidgetAccessRepositoryMetabaseCollectionResolverMetabaseCollectionAccessServiceMetabaseClientEmbeddingParamsResolverGuestTokenServiceClientSecurityFilterChainRoutingDataSourceFilterBasicAuthenticationFilterLdapAuthFilterTokenAuthFilterTokenAuthenticationProviderUserAuthServiceUserDataRepositoryMetabaseWidgetsControllerCurrentUserServiceMetabaseWidgetsServiceEmployeeAccessRepositoryRoleWidgetAccessRepositoryMetabaseCollectionResolverMetabaseCollectionAccessServiceMetabaseClientEmbeddingParamsResolverGuestTokenService1GET /api/metabase/widgets?page=...Headers: Authorization, token, X-BMS-DOMEN(optional for TokenAuth)2doFilter3resolve source by X-BMS-DOMEN for TokenAuthunknown/missing/LDAP -> default-source4continue5attempt BasicAuthalt[Authorization Basic present and LDAP bind success]6Authentication(principal=DirContextOperations)[no Basic header or bind failed]7continue8doFilteralt[current authentication is LDAP principal]9resolve username (uid)10resolve entryUUIDalt[entryUUID missing in principal]11LDAP search by uid12findTagsByUserId(ldapUserUuid)13assigned tags14derive AccessContext from all FILTER_BMS_ID / FILTER_INSTANCE_ID tags15convert to AuthenticatedUser(authSystem=LDAP)16set request attr ujin.ldapLogin[already AuthenticatedUser or anonymous]17skip conversion18continue19doFilteralt[AuthenticatedUser already exists (e.g. LDAP)]20skip token auth[no authenticated user yet]21resolve token (header token first, then Bearer)alt[token exists]22authenticate(token)23authenticate(rawToken)24findByToken(rawToken)25Optional<UserData>26Optional<UserData>alt[valid user]27Authentication(AuthenticatedUser, authSystem=TOKEN)28set SecurityContext + headers[invalid token]29AuthenticationException30clear SecurityContext[token missing]31skip auth32authorize requestalt[unauthenticated]33401 Authentication required[authenticated]34listWidgets(page)35currentUser()36AuthenticatedUser37listWidgets(user, page)38resolveAccessResolution(user)alt[user.authSystem == LDAP]39findAllowedWidgetIdsByUser(bmsIds[], ldapUserUuid)40Set<Long>41findDeniedSubfolderIdsByUser(ldapUserUuid)42Set<Long>alt[ACL set is empty]43applyRoleAccess=false44roleForCollectionFilter=null[ACL set not empty]45applyRoleAccess=true46roleForCollectionFilter=null47resolveRootCollectionIds(user)48findTagsByUserId(ldapUserUuid)49assigned tags50keep only ROOT_COLLECTION tags51apply tag-roots mapping in YAML order52unmapped tag -> fallback collection name = tag.code53listCollections()54collections[]55rootCollectionIds[user.authSystem == TOKEN]56findAllowedWidgetIds(bmsId, roleId, source_key)57Set<Long>58resolveRootCollectionIds(user)59listCollections()60collections[]61rootCollectionIds62applyRoleAccess=true63roleForCollectionFilter=user.roleloop[recursive collections tree from all rootCollectionIds]64listSubcollections(collectionId)65GET /api/collection/{id}/items?models=collection66subcollections[]alt[LDAP denied subfolder]67skip subtree[visible collection]68loadAllowedItems(collectionId, userRole, bmsIds|bmsId, allowedWidgetIds, applyRoleAccess)69GET /api/collection/{id}/items?models=dashboard,card70items[]71filter archived72filter role tags (#ROLE)73filter BMS tags (#BMS / #!BMS)74apply allowedWidgetIds only when applyRoleAccess=true75allowed widgets[]alt[token request with x_enterprise_id]76filter enterprise tags in folder/widget description (#ENTERPRISE_ID-<id>)loop[each allowed widget]alt[structureOnly=true]77return widget without token[structureOnly=false]alt[embedding_params already present in collection item]78resolveLockedParams(user, metadata)[metadata required]79GET /api/{dashboard|card}/{id}80resource metadata.embedding_params81resolveLockedParams(user, metadata)alt[locked params supported]82params map (Token: bms_id scalar; LDAP: bms_ids / instance_ids lists)83createToken(type, id, params)84signed token[unknown locked param]85WidgetAccessException86log warn and skip widget (for list endpoint)opt[page is provided]87recursively keep groups where name.equalsIgnoreCase(page)or group.id == page88WidgetsResponse(groups)89200 JSON