Skip to content

Fixes #24180: Domain isolation in UI, search and lineage for multi-tenant#28889

Open
harshach wants to merge 3 commits into
mainfrom
harshach/issue-24180-review-plan
Open

Fixes #24180: Domain isolation in UI, search and lineage for multi-tenant#28889
harshach wants to merge 3 commits into
mainfrom
harshach/issue-24180-review-plan

Conversation

@harshach

@harshach harshach commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

Describe your changes:

Fixes #24180

I worked on domain isolation for multi-tenant setups because users restricted to a Domain could still see other domains' names (dropdowns/lists/search) and foreign-domain lineage, which leaks metadata across tenants. This enforces isolation server-side for users carrying DomainOnlyAccessRole: lineage results (search, DB-graph, exports, and the data-quality lineage endpoint) are pruned to the user's accessible domains (own + sub-domains + domainless, reachable-from-root so traversal through foreign nodes is severed), the Domain REST list//hierarchy are filtered, the domain search index is filtered through the search RBAC evaluator, and the UI mirrors this (hides "All Domains", auto-selects a single allowed domain, scopes listing/lineage queries). Activity-feed isolation already existed and is unchanged.

Type of change:

  • New feature

High-level design:

  • Lineage: LineageDomainFilter prunes the assembled SearchLineageResult in SearchRepository (single fail-closed chokepoint, reachability-from-root using the hierarchy-aware SubjectContext.hasDomains); the DB-graph get/getByName and the data-quality lineage endpoint (searchDataQualityLineage, visibility-only prune over its node/edge sets) are filtered the same way. The graph builders are untouched.
  • Domain list/hierarchy: ListFilter.getDomainSelfCondition + EntityUtil.applyDomainSelfRestriction filter the Domain entity by its own id + sub-domain FQN-hash prefix (domains aren't themselves domain-tagged, so generic domain filters don't apply to them).
  • Domain search: driven through search RBAC — RBACConditionEvaluator.hasDomain() matches the user's own domains by id.keyword and excludes domain documents from the "domainless" clause (gated on enableAccessControl, like all search RBAC).
  • Frontend: defense-in-depth + UX only; the backend is the security boundary.
  • Backward compatible: additive overloads, inert for admins/bots/non-restricted users; no wire-schema changes.

Tests:

Use cases covered

  • A DomainOnlyAccessRole user querying lineage (search, DB-graph, or data-quality lineage) sees only own-domain + domainless nodes; foreign-domain nodes and the paths through them are hidden.
  • The same user sees only their own domains in /v1/domains, /v1/domains/hierarchy, the domain search index, and the navbar dropdown.
  • Per-user (not global) isolation: a foreign asset hidden from user A is still visible to user B who owns that domain.
  • Admins and users without the role are unaffected (full visibility).

Unit tests

  • Added/updated.
  • Files: LineageDomainFilterTest (incl. data-quality prune), ElasticSearchRBACConditionEvaluatorTest, OpenSearchRBACConditionEvaluatorTest, SearchRepositoryBehaviorTest, DomainRestrictionUtils.test.ts, useApplicationStore.domain.test.ts, DomainSelectableTree.test.tsx.

Backend integration tests

  • Added.
  • Files: openmetadata-integration-tests/.../DomainIsolationIT.java (lineage + domain list/hierarchy/search isolation).

Ingestion integration tests

  • Not applicable (no ingestion changes).

Playwright (UI) tests

  • Added — a dedicated DomainIsolation Playwright project/shard (runs serially because the specs toggle the global enableAccessControl setting; excluded from the main chromium project).
  • Files (playwright/e2e/Features/DomainIsolation/): DomainSearchIsolation.spec.ts, DomainListingIsolation.spec.ts, DomainLineageIsolation.spec.ts, DomainDropdownIsolation.spec.ts, domainIsolationUtils.ts. Multi-user (admin / userA / userB) cross-tenant isolation.

Manual testing performed

  • Java unit tests run locally (LineageDomainFilterTest, ES/OS RBAC evaluator suites, SearchRepositoryBehaviorTest — all green); frontend Jest specs run locally (38/38). The integration test and the Playwright DomainIsolation shard compile/lint/type-check cleanly but were not executed against a live stack in this change.

UI screen recording / screenshots:

Not applicable yet — happy to attach a recording of the restricted-user dropdown/search if required for review.

Checklist:

  • I have read the CONTRIBUTING document.
  • My PR title is Fixes <issue-number>: <short explanation>
  • My PR is linked to a GitHub issue via Fixes #24180 above.
  • I have commented on my code, particularly in hard-to-understand areas.
  • For JSON Schema changes: not needed (no schema changes).
  • For UI changes: I attached a screen recording and/or screenshots above.
  • I have added tests (unit / integration / Playwright as applicable) and listed them above.

🤖 Generated with Claude Code

Domain-restricted users (DomainOnlyAccessRole) now only see their own
domains and sub-domains across lineage graphs, domain list/hierarchy/search,
and the UI domain selector.

Backend:
- LineageDomainFilter prunes search-lineage results (reachability-from-root,
  severs traversal through foreign nodes); wired into SearchRepository
  lineage/export paths and the DB-graph get/getByName endpoints.
- ListFilter.getDomainSelfCondition + EntityUtil.applyDomainSelfRestriction
  filter the Domain REST list and /domains/hierarchy.
- RBACConditionEvaluator.hasDomain() matches a user's own domains by id and
  excludes domain documents from the domainless clause, so the domain search
  index hides foreign domains (gated on enableAccessControl).

Frontend (defense-in-depth + UX):
- Wire currentUser.domains into useDomainStore; hide "All Domains" and
  auto-select the single allowed domain; scope listing + lineage queries.

Tests: LineageDomainFilterTest, ES/OS RBACConditionEvaluatorTest additions,
DomainIsolationIT, frontend Jest specs, and DomainIsolation.spec.ts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@harshach harshach requested a review from a team as a code owner June 9, 2026 17:44
@github-actions github-actions Bot added backend safe to test Add this label to run secure Github workflows on PRs labels Jun 9, 2026
harshach and others added 2 commits June 9, 2026 11:03
The /v1/lineage/getDataQualityLineage endpoint bypassed the domain prune,
letting a DomainOnlyAccessRole user observe foreign-domain nodes/edges.
Thread SubjectContext through searchDataQualityLineage (SearchManagementClient
-> clients -> managers) and apply LineageDomainFilter.pruneDataQualityLineage
(visibility-only over the node/edge sets) before assembling the response.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…d shard (#24180)

Add a dedicated Playwright project "DomainIsolation" (own shard, serial workers
since the specs toggle the global enableAccessControl setting) and exclude the
directory from the main chromium project. The suite verifies per-user, cross-tenant
isolation across multiple users (userA/userB/admin):
- DomainSearchIsolation: each user only finds their own domain's assets (+ domainless),
  not the other tenant's — proving per-user, not global, hiding.
- DomainListingIsolation: domain listing page scoped per user.
- DomainLineageIsolation: cross-tenant lineage node hidden per user; admin sees all.
- DomainDropdownIsolation: navbar dropdown scoped per user (relocated here).

Wired into the postgresql e2e workflow shard-1 group (alongside SearchRBAC); the
mysql e2e --shard run picks it up automatically.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@gitar-bot

gitar-bot Bot commented Jun 9, 2026

Copy link
Copy Markdown
Code Review ✅ Approved 3 resolved / 3 findings

Implements comprehensive domain isolation for multi-tenant setups across UI, search, and lineage, resolving all identified bypasses in data-quality and graph pruning. No issues found.

✅ 3 resolved
Performance: N+1 entity fetch per node in DB-graph lineage pruning

📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/LineageRepository.java:172-186
In the DB-graph lineage path (LineageRepository.get/getByNamepruneLineageByDomainvisibleNodeIdsresolveNodeDomains), each lineage node triggers a separate Entity.getEntity(ref.getType(), ref.getId(), FIELD_DOMAINS, Include.ALL) call to resolve its domains. For a restricted user this issues one DB (and possibly cache/index) round trip per node in the assembled graph — an N+1 pattern that scales with the size of the lineage graph (up/downstream depth up to 3 can fan out to many nodes).

The search-based path avoids this entirely by reading domains straight from the already-materialized node entity map in LineageDomainFilter.nodeDomains. Consider batch-fetching domains for all node ids in one query (or reusing already-loaded references) instead of fetching per node inside the loop.

Edge Case: Foreign root entity ref retained after DB-graph prune

📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/LineageRepository.java:159-170 📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/LineageRepository.java:194-208
In pruneLineageByDomain, when the requested root entity is itself foreign (not visible), reachableNodeIds returns an empty set so lineage.getNodes() is emptied, but lineage.getEntity() is never cleared/filtered — it is a separate field on EntityLineage and filterNodes only touches the nodes list. A DomainOnlyAccessRole user requesting lineage for an entity outside their domains thus still receives the root EntityReference (id, name, fqn) in the response even though the rest of the graph is correctly pruned.

Impact is limited (the caller supplied the id), but the entity name/FQN can still leak. The search path does not have this issue because the root is part of the nodes map. Consider returning empty/omitting the entity when the root is not visible.

Security: Data Quality lineage endpoint bypasses domain isolation

📄 openmetadata-service/src/main/java/org/openmetadata/service/resources/lineage/LineageResource.java:433-447 📄 openmetadata-service/src/main/java/org/openmetadata/service/search/SearchRepository.java:3169-3179
The /v1/lineage/getDataQualityLineage endpoint (searchDataQualityLineage) still calls Entity.getSearchRepository().searchDataQualityLineage(fqn, upstreamDepth + 1, queryFilter, deleted) with no SubjectContext, so its result never passes through LineageDomainFilter.prune. Every other lineage-returning endpoint in this PR (get, getByName, searchLineage, searchLineageWithDirection, getLineageByEntityCount, the export jobs) was updated to thread getSubjectContext(securityContext) through to the filter, but this one was missed.

This endpoint returns a graph of nodes and edges built from the same lineage index, so a user holding DomainOnlyAccessRole can call it and observe foreign-domain nodes/edges — exactly the cross-tenant metadata leak the PR sets out to close. The PR description claims a "single fail-closed chokepoint," but this path is an additional, unfiltered chokepoint.

Because searchDataQualityLineage returns a raw Response/map rather than a SearchLineageResult, it cannot reuse LineageDomainFilter.prune directly; the data-quality lineage builder needs an equivalent domain-aware prune (reachability-from-root over visible nodes) applied before the response is assembled, gated on LineageDomainFilter.shouldApply(subjectContext).

Options

Display: compact → Showing less information.

Comment with these commands to change:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Jest test Coverage

UI tests summary

Lines Statements Branches Functions
Coverage: 62%
62.81% (66938/106560) 44.18% (37164/84114) 46.47% (11318/24355)

@sonarqubecloud

sonarqubecloud Bot commented Jun 9, 2026

Copy link
Copy Markdown

@sonarqubecloud

sonarqubecloud Bot commented Jun 9, 2026

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

🟡 Playwright Results — all passed (14 flaky)

✅ 4269 passed · ❌ 0 failed · 🟡 14 flaky · ⏭️ 88 skipped

Shard Passed Failed Flaky Skipped
🟡 Shard 1 299 0 2 4
🟡 Shard 2 805 0 1 9
🟡 Shard 3 802 0 2 8
🟡 Shard 4 842 0 5 12
🟡 Shard 5 720 0 1 47
🟡 Shard 6 801 0 3 8
🟡 14 flaky test(s) (passed on retry)
  • Features/DataAssetRulesDisabled.spec.ts › Database service (shard 1, 1 retry)
  • Features/NavigationBlocker.spec.ts › should stay on current page and keep changes when X button is clicked (shard 1, 1 retry)
  • Features/Glossary/GlossaryWorkflow.spec.ts › should display correct status badge color and icon (shard 2, 1 retry)
  • Features/RTL.spec.ts › Verify Following widget functionality (shard 3, 1 retry)
  • Features/Table.spec.ts › Tags term should be consistent for search (shard 3, 1 retry)
  • Flow/PersonaFlow.spec.ts › Set default persona for team should work properly (shard 4, 1 retry)
  • Pages/CustomProperties.spec.ts › Time Interval (shard 4, 1 retry)
  • Pages/CustomProperties.spec.ts › Hyperlink (shard 4, 1 retry)
  • Pages/CustomProperties.spec.ts › Time (shard 4, 1 retry)
  • Pages/Domains.spec.ts › Domain Rbac (shard 4, 1 retry)
  • Pages/Entity.spec.ts › Inactive Announcement create & delete (shard 5, 1 retry)
  • Pages/Lineage/LineageFilters.spec.ts › Verify lineage schema filter selection (shard 6, 1 retry)
  • Pages/ODCSImportExport.spec.ts › Multi-object ODCS contract - object selector shows all schema objects (shard 6, 1 retry)
  • Pages/ServiceListing.spec.ts › should render the service listing page (shard 6, 1 retry)

📦 Download artifacts

How to debug locally
# Download playwright-test-results-<shard> artifact and unzip
npx playwright show-trace path/to/trace.zip    # view trace

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend safe to test Add this label to run secure Github workflows on PRs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Request: Improve domain isolation in UI and lineage for multi-tenant setups

1 participant