Fixes #31070: let any authenticated user read glossary term relation types - #31146
Conversation
…types The Related Terms dropdown and the ontology explorer both fetch glossaryTermRelationSettings, but the read was gated behind authorizer.authorizeAdmin. Non-admins got a 403, the UI fell back to its single-entry DEFAULT_GLOSSARY_TERM_RELATION_TYPES_FALLBACK, and the relation-type dropdown offered only "Related To". Relation types are a global vocabulary with no owner to evaluate a policy against and no secrets in the payload, so reads now follow the same rule lineageSettings already gets: open to any authenticated principal, via a USER_READABLE_SETTINGS allowlist that replaces the ad-hoc lineage check. Creating, updating and deleting relation types — and every other setting — stays admin-only. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
✅ PR checks passedThe linked issue has a description and all required Shipping project fields set. Thanks! |
There was a problem hiding this comment.
Pull request overview
This PR fixes a permission bug in the backend System settings API that prevented non-admin users from reading glossary term relation types (used by the Related Terms UI and ontology explorer), while keeping all writes admin-only.
Changes:
- Allow authenticated non-admin reads for
glossaryTermRelationSettings(and keep the existinglineageSettingsexception) via aUSER_READABLE_SETTINGSallowlist inSystemResource. - Remove the admin gate from
GET /system/settings/glossaryTermRelationSettings/relationTypes. - Add unit + integration tests to validate the read/write authorization split and guard against accidental allowlist widening.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| openmetadata-service/src/main/java/org/openmetadata/service/resources/system/SystemResource.java | Introduces USER_READABLE_SETTINGS allowlist and opens glossary relation-type reads to authenticated users while preserving admin-only writes. |
| openmetadata-service/src/test/java/org/openmetadata/service/resources/system/SystemResourceSettingsAuthorizationTest.java | Adds unit tests asserting non-admin can read glossary relation settings/types but cannot write or read other settings. |
| openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/GlossaryTermRelationSettingsPermissionsIT.java | Adds HTTP-level integration tests covering 200/401/403 behavior for non-admin reads/writes, plus an explicit admin-only setting check. |
Suppressed comments (4)
openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/GlossaryTermRelationSettingsPermissionsIT.java:149
- This test issues a DELETE (write) request; it should take a READ_WRITE resource lock to avoid shared glossary-term-relation settings being mutated concurrently if authorization ever regresses.
@Test
@ResourceLock(
value = SharedResourceLocks.GLOSSARY_TERM_RELATION_SETTINGS,
mode = ResourceAccessMode.READ)
openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/GlossaryTermRelationSettingsPermissionsIT.java:161
- This test issues a PUT to /v1/system/settings (write). Use a READ_WRITE lock on the glossary term relation settings resource so an unexpected authorization regression cannot corrupt shared state under only a READ lock.
@Test
@ResourceLock(
value = SharedResourceLocks.GLOSSARY_TERM_RELATION_SETTINGS,
mode = ResourceAccessMode.READ)
openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/GlossaryTermRelationSettingsPermissionsIT.java:177
- This test issues a PATCH (write) request; it should take a READ_WRITE lock for the same reason as other write-path tests, even when expecting 403, to prevent cross-test interference if the write ever succeeds unexpectedly.
@Test
@ResourceLock(
value = SharedResourceLocks.GLOSSARY_TERM_RELATION_SETTINGS,
mode = ResourceAccessMode.READ)
openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/GlossaryTermRelationSettingsPermissionsIT.java:137
- This test issues a PUT (write) request; it should take a READ_WRITE resource lock to prevent unintended shared-state mutations from impacting other concurrent tests if a regression ever allows the write.
@Test
@ResourceLock(
value = SharedResourceLocks.GLOSSARY_TERM_RELATION_SETTINGS,
mode = ResourceAccessMode.READ)
The five tests that fire POST/PUT/DELETE/PATCH at the shared relation settings expect a 403, but a regression in the admin gate is exactly what they guard against — and if one lands, the write mutates settings that concurrent READ-lock tests are asserting on, turning one clean failure into cascading ones. READ_WRITE matches how GlossaryTermRelationSettingsIT already splits its locks. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
✅ Playwright Results — workflow succeededValidated commit ✅ 758 passed · ❌ 0 failed · 🟡 1 flaky · ⏭️ 0 skipped · 🧰 0 lifecycle flaky PerformanceBlocking targets: ✅ met · Optimization targets: 🟡 in progress Shard-job maxima below are not the full workflow wall time; the linked run includes build, fixture, planning, and reporting. 🕒 Full workflow signal wall (to summary) 36m 33s ⏱️ Max setup 3m 12s · max shard execution 19m 14s · max shard-job elapsed before upload 22m 34s · reporting 6s 🌐 197.16 requests/attempt · 2.51 app boots/UI scenario · 6.41% common-shard skew Optimization targets still in progress:
🟡 1 flaky test(s) (passed on retry)
How to debug locally# Download playwright-test-results-<shard> artifact and unzip
npx playwright show-trace path/to/trace.zip # view trace |
… authorizer The create and update tests posted a GlossaryTermRelationType without `category`. Jersey runs @Valid bean validation before the resource method body, so those requests were rejected with 400 and never reached authorizeAdmin — the assertions failed on 400 vs 403, and would have passed just as happily against a broken admin gate. Both payloads now carry all three @NotNull fields (name, displayName, category), and the update targets relatedTo so the body name matches the path. Assertion messages include the response body so the next mismatch says why on the first read. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Failed to cherry-pick changes to the 1.13 branch. |
|
Changes have been cherry-picked to the 2.0 branch. |
…types (#31146) * Fixes #31070: let any authenticated user read glossary term relation types The Related Terms dropdown and the ontology explorer both fetch glossaryTermRelationSettings, but the read was gated behind authorizer.authorizeAdmin. Non-admins got a 403, the UI fell back to its single-entry DEFAULT_GLOSSARY_TERM_RELATION_TYPES_FALLBACK, and the relation-type dropdown offered only "Related To". Relation types are a global vocabulary with no owner to evaluate a policy against and no secrets in the payload, so reads now follow the same rule lineageSettings already gets: open to any authenticated principal, via a USER_READABLE_SETTINGS allowlist that replaces the ad-hoc lineage check. Creating, updating and deleting relation types — and every other setting — stays admin-only. (cherry picked from commit 3171820)
Code Review ✅ ApprovedUpdates system resource authorization to allow any authenticated user to read glossary term relation types while keeping write operations admin-only. No issues found. OptionsDisplay: compact → Showing less information. 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:
Fixes #31070
The Related Terms dropdown and the ontology explorer both fetch
glossaryTermRelationSettings, but the read was gated behindauthorizer.authorizeAdmin. Non-admins got a403 ... is not admin, the UI silently fell back toDEFAULT_GLOSSARY_TERM_RELATION_TYPES_FALLBACK(a single entry), and the relation-type dropdown offered only "Related To". I made reads open to any authenticated principal while every write stays admin-only.Relation types are a global vocabulary — there is no owner or resource to evaluate a policy against, and the payload carries no secrets (
prepareFetchedSettingsonly masks email/auth configs) — so reads now get the same treatmentlineageSettingsalready had, via aUSER_READABLE_SETTINGSallowlist that replaces the ad-hoc single-name check. Creating, updating and deleting relation types (and every other setting) is unchanged and still requires admin.Type of change:
High-level design:
N/A — small change (3 files, one authorization rule).
Tests:
Use cases covered
GET /system/settings/glossaryTermRelationSettingsandGET /system/settings/glossaryTermRelationSettings/relationTypesPOST/PUT/DELETE .../relationTypes,PUT /system/settings, andPATCH /system/settings/{name}searchSettingsasserted explicitly, so the allowlist can't silently widen)Unit tests
openmetadata-service/src/test/java/org/openmetadata/service/resources/system/SystemResourceSettingsAuthorizationTest.java(6 tests) — the authorizer rejects every admin check, standing in for a non-admin caller.Tests run: 6, Errors: 2— both reads throwAuthorizationException: Principal: is not admin, reproducing the reported 403. Passes with it:Tests run: 6, Failures: 0, Errors: 0.isUserReadableSetting1/1,listGlossaryTermRelationTypes10/11 (uncovered line is the pre-existingrelationTypes == nullfallback),getSettingByName4/6 (uncovered lines are the pre-existing auth-config rejection branch). Whole-class coverage is not meaningful here —SystemResourceis ~1450 lines covered mostly by the IT suite, which doesn't run in this surefire session.Backend integration tests
openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/GlossaryTermRelationSettingsPermissionsIT.java(10 tests) — same read/write split over real HTTP with a DataConsumer JWT, plus thesearchSettings403 and unauthenticated 401 guards. Compiles clean; it needs the Testcontainers stack, which I did not spin up locally, so it has not been executed outside CI.Ingestion integration tests
Playwright (UI) tests
Manual testing performed
No live stack run — verification was the automated RED/GREEN above. To reproduce by hand:
curl -H "Authorization: Bearer $TOKEN" $HOST/api/v1/system/settings/glossaryTermRelationSettings→ 200 with the fullrelationTypesarray (was 403).curl -X POST -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"name":"test"}' $HOST/api/v1/system/settings/glossaryTermRelationSettings/relationTypes→ still 403.UI screen recording / screenshots:
Not applicable — no UI changes.
Checklist:
Fixes <issue-number>: <short explanation>Fixes #<issue-number>above.🤖 Generated with Claude Code