Repository navigation
fix(security): enforce authorization on sub-resource, listing and admin endpoints - #34838
Conversation
…in endpoints Several endpoints answered without the per-entity check the entity itself gets, or returned data about entities the caller may not view. - Sub-resource reads and actions now authorize against their parent entity: system profiles, entity relationships, pipeline statuses, KPI results, data contract validation, audit cancel, workflow trigger and redeploy, framework fork, intake forms, lineage edges and lineage graph roots, and the automations workflow trigger (checked per workflow type). - Asset listings and counts (domain, data product, glossary term, tag, team, user), knowledge page hierarchies and field/source-URL search lookups carry the caller's search access policies. - Responses that mix entities are filtered to those the caller may view: activity feeds, lineage neighbours, column search/export, folder contents, recognizer feedback, MCP semantic search. ViewPermissionFilter batch-loads entities per type for this and fails closed. - Votes and follows require view access; usage checks carry the entity id. - Deployment and installation-wide endpoints are limited to admins (and bots where an integration reads them): email template reset, reindex failures, change events, system counts and status, raw web analytics, the recognizer feedback queue, search documents by id. Non-admins get a generic pipeline service status reason. - ValidationHttpUtil validates every outbound URL, so validator fetches that follow a discovery document cannot reach metadata or private addresses; the SAML connectivity probe skips refused URLs. - Password reset and verification tokens are no longer logged, and a reset token is not stored when no mail can be sent. Page views are recorded for the caller. - RBACConditionEvaluator no longer shares one evaluation context across concurrent callers. - Reports list through the policy-filtered listing; this also fixes GET /v1/reports failing with HV000151.
The mentions feed, added on main after the other activity feeds were filtered, returned change events on any entity a reply mentioned the caller under. It now goes through the same per-entity view filter.
|
✅ SSO Login Nightly — PASSED View workflow run · Commit
Totals: 94 passed · 0 failed · 0 flaky · 0 skipped · duration 18m 55s |
| @@ -192,6 +194,22 @@ protected Response deleteFollowerInternal( | |||
| .toResponse(); | |||
There was a problem hiding this comment.
We check ViewBasic when adding a follower, but not when removing one. Since deleteFollower returns a ChangeEvent containing the entity, a user who followed it before losing access can still retrieve its data. Can we apply the same view check here and add coverage for DELETE?
🚦 Removed from the merge queue —
|
# Conflicts: # openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ColumnRepository.java # openmetadata-service/src/main/java/org/openmetadata/service/lineage/LineagePermissionFilter.java
…ollow Search lineage roots its graph on the indexed entity with the requested FQN and never uses `type` to find it, so a missing, unknown or wrong type skipped the root check while the search still ran. The check now looks the root up the way the search does (SearchRepository.getLineageRoot) and authorizes ViewBasic on that entity, falling back to the named type only when the index does not have the root yet. Removing a follower returns the entity, so it now needs the same ViewBasic check as adding one.
totalFound, returnedCount and the page messages were computed before unviewable hits were dropped, and hiddenCount reported how many were dropped. Hits are now filtered before anything is counted; only the cursor advances past the dropped ones, resuming right after the last viewable hit a budget-trimmed page kept.
🚦 Removed from the merge queue —
|
🚦 Removed from the merge queue —
|
# Conflicts: # openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DomainRepository.java
# Conflicts: # openmetadata-service/src/main/java/org/openmetadata/service/resources/tags/TagResource.java
Code Review 👍 Approved with suggestions 0 closed / 1 findings🔴 High risk · Changes authorization across listings, feeds, lineage, and admin endpoints; incorrect policy enforcement could expose restricted catalog data or block legitimate access. Adds authorization checks across sub-resource, listing, count, feed, lineage, vote/follow and admin endpoints, reusing parent-entity checks, search-side access policies and a new post-filter for mixed responses. Approved with one suggestion: 💡 Performance: Semantic search authorizes hits one by one, an N+1 of entity loads📄 openmetadata-mcp/src/main/java/org/openmetadata/mcp/tools/SemanticSearchTool.java:274-276 📄 openmetadata-mcp/src/main/java/org/openmetadata/mcp/tools/SemanticSearchTool.java:329-337 📄 openmetadata-service/src/main/java/org/openmetadata/service/security/ViewPermissionFilter.java:35-39 📄 openmetadata-service/src/main/java/org/openmetadata/service/security/ViewPermissionFilter.java:64-69 📄 openmetadata-service/src/main/java/org/openmetadata/service/security/ViewPermissionFilter.java:149-152
🤖 Prompt for agentsVerificationRules No rules evaluated Functional validation Not enabledAuto-approval Not enabledFor more verification details, comment OptionsDisplay: compact → Showing compact verification results. Comment with these commands to change the behavior for this request:
Was this helpful? React with 👍 / 👎 | Powered by Gitar — free for open source |
|



Describe your changes:
No linked issue: security hardening, labelled
skip-pr-checks.Several endpoints answered without the per-entity check the entity itself gets, or returned data about entities the caller may not view. This PR puts each of them behind the same authorization the entity's own endpoints apply.
ViewDataProfile), table/schema entity relationships, ingestion pipeline status history, KPI results, data contract validation (/validate,/validate/yaml,/odcs/validate/yaml), audit report cancel, governance workflow trigger and redeploy, AI governance framework fork, intake forms by entity type, lineage edges (both ends), and the root of every lineage graph and export endpoint. The automations workflow trigger is now checked per workflow type: test connection as before, reverse ingestion needsEditAllon its service, query runner is limited to the requesting user, and the Spark engine test needsCreateon ingestion pipelines.semantic_search.ViewBasicon the entity, in all 26 resources that expose them. Usage endpoints build their resource context with the entity id, so entity-conditioned policies apply.GET /v1/events), system entity/service counts and status, raw web analytic events (GET .../collect), and search documents by id. Admin only: email template reset, reindex failures (now paged, max 1000) and the recognizer feedback review queue. Non-admins get a generic reason for an unavailable pipeline service.ValidationHttpUtilvalidates every request, so validator fetches that follow a discovery document cannot reach metadata or private addresses. The SAML connectivity probe skips a refused URL as a warning.RBACConditionEvaluatorno longer shares one SpEL evaluation context across concurrent callers.GET /v1/reportslists through the policy-filtered listing. This also fixes the endpoint failing withHV000151.Type of change:
High-level design:
The fixes reuse three existing mechanisms rather than adding new authorization concepts.
authorizer.authorize(...)with the parent'sResourceContext, built by id or name with the rightInclude. Conditional policies (tags, owners, domains) therefore see the real entity.RBACConditionEvaluator) through the new package-privateElasticRbacQueriesandOpenSearchRbacQueries. These fail closed when the policies compile to nothing, and are skipped for admins or when search access control is off.SubjectContextis threaded throughSearchRepository, the search and aggregation managers, andInheritedFieldQuery.ViewPermissionFilterbuckets references by type and batch-loads each bucket with exactly the fields authorization reads. It hands the authorizer pre-resolvedResourceContexts, hydrates tags lazily, and treats anything but a clear allow as hidden.LineagePermissionFilternow delegates to it.Compatibility: existing repository and search-client signatures stay and delegate with a
nullcaller, so downstream callers compile unchanged.SearchClientgets default caller-awarelistPageHierarchy*overloads.Behaviour changes integrators may notice: the admin/bot-only endpoints listed above, the generic pipeline-service reason for non-admins, no reset token without SMTP, and page views bound to the caller. The Health Check page that reads
/v1/system/statusis already admin-only in the UI.Not in this PR: pruning denied nodes from the search-based lineage graph endpoints (their root is checked here), and
/v1/columns/grid.Tests:
Use cases covered
ViewBasicon a table, unconditionally or through a tag-conditioned deny policy, gets 403 on its system profiles, entity relationships, lineage edge and lineage graph, and data contract validation. The same applies to KPI results, pipeline status history and intake forms.EditAll/Create. Query runner triggers work for the requester only; reverse ingestion works for the service owner only.Unit tests
BasicAuthenticatorTokenTest.IngestionPipelineResourceStatusTest,ValidationHttpUtilTest,SamlValidatorTest,SemanticSearchToolTest,ElasticSearchFieldQueryTest,OpenSearchFieldQueryTest,OpenSearchRBACConditionEvaluatorTest, and signature updates inSearchRepositoryBehaviorTest,ElasticSearchFieldlessQueryTestandOpenSearchFieldlessQueryTest.RBACConditionEvaluator.main: every unit test class that references a changed class. That is 207 service classes (3046 tests) and 7 MCP classes (155 tests): 0 failures, 1 skipped.Backend integration tests
SubResourceAuthorizationIT(24 tests) andAdminOnlyEndpointsIT(8 tests).FeedAccessAuthzIT(activity by actor, mentions),WorkflowTriggerPermissionsIT(query runner, reverse ingestion, Spark),IntakeFormResourceIT,FolderIT,DomainIsolationIT, andDenyPolicyPrincipals.clientDeniedWhenfor tag-conditioned denies.ActivityResourceIT,DataContractResourceIT,DataContractPermissionIT,WebAnalyticEventResourceIT,SystemResourceIT,TagRecognizerFeedbackIT,KnowledgePageHierarchyIT,LineageResourceIT,UsageResourceIT,WorkflowDefinitionResourceIT,KpiResourceIT,ColumnBulkUpdateIT,ColumnResourceIT,McpIntegrationIT,McpToolsValidationIT,SearchResourceIT,DomainResourceIT,DataProductResourceIT,TeamResourceIT,UserResourceIT,GlossaryTermResourceIT,TagResourceIT,IngestionPipelineResourceIT,WorkflowResourceITandDocStoreResourceIT. The one error,WorkflowDefinitionResourceIT.test_NewApprovalRunSupersedesStalePendingTask(an approval task not superseded within 60 s), passed when the class ran alone on the same commit (59 tests, 0 failures). This PR changes no workflow execution code.Ingestion integration tests
Playwright (UI) tests
make playwright_affectedmaps these backend files to about 190 specs and reports impact-map gaps, so the merge queue runs the full suite.Not run locally: no UI change, and the planner selects about 190 specs plus impact-map gaps (full suite in the merge queue).
Manual testing performed
No manual UI testing. The ITs run each scenario end to end against a real server, MySQL and Elasticsearch, using a data consumer, deny-policy principals and bots.
I also checked the OpenMetadata and Collate UIs for calls to the endpoints that became admin/bot-only. The only caller is the admin-only Health Check page.
UI screen recording / screenshots:
Not applicable.
Checklist:
Fixes <issue-number>: <short explanation>. No issue, see above.Fixes #<issue-number>above. No issue, see above.🤖 Generated with Claude Code