fix(semantic-layers): access-filter the /connections database branch - #43887
fix(semantic-layers): access-filter the /connections database branch#43887mikebridge wants to merge 1 commit into
Conversation
sc-119878 (follow-up from Amin's apache#43783 review). `_fetch_connection_items` access-filters the semantic-layer branch (`SemanticLayer.perm.in_(perms)` when not `can_access_all_datasources()`) but issued a bare `db.session.query(Database)` for the database branch, so any caller reaching the `can_read`-gated `/connections/` endpoint got every database's name, backend, allow_dml, allow_file_upload, expose_in_sqllab and last editor regardless of database access. Apply `DatabaseFilter` (DatabaseRestApi's `base_filters` scoping) to the database branch, before the name filter per its dynamic-filter ordering contract, so the DB inventory is scoped the same way every other database path is. Per SECURITY.md this is an authorization-scoping fix, not a regression by apache#43783: the gap is pre-existing on master; built-in Gamma reached the endpoint before (via `can_connections`) and after (via `can_read`), so built-in-role reach is unchanged. What changed is the endpoint is now gated on the coarse, commonly granted `can_read` and advertised in UPDATING.md as broadly accessible, so an operator can no longer grant "browse semantic layers" without also handing over the database inventory. Principal: Gamma / any role with `can_read` on SemanticLayer; the exposed row is DB inventory, access-filtered on every other path. Tests: an all-access user leaves the DB branch unfiltered (sees all); a user without all-database access has the access predicate applied. Reverting the fix fails the limited-user test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01267VBWbvWTNZUg9GvXKgkC
|
@aminghadersohi — this is the It applies A 4-lens pass (preset-review for the security angle + clean-code/tidy-first/python) reviewed it: preset-review confirmed the scoping is correct and complete, matches DatabaseRestApi exactly, and — checked specifically — the retained |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #43887 +/- ##
=======================================
Coverage 79.45% 79.45%
=======================================
Files 2895 2895
Lines 168167 168169 +2
Branches 38995 38995
=======================================
+ Hits 133624 133626 +2
Misses 32044 32044
Partials 2499 2499
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
SUMMARY
The semantic-layer
/connections/endpoint (SemanticLayerRestApi, mapped tocan_read) lists databases and semantic layers together._fetch_connection_itemsaccess-filters the semantic-layer branch (SemanticLayer.perm.in_(perms)whennot can_access_all_datasources()), but issued a baredb.session.query(Database)for the database branch — so any caller reaching the endpoint received every database'sdatabase_name,backend,allow_dml,allow_file_upload,expose_in_sqllaband last editor, regardless of database access.This applies
DatabaseFilter— the same scopingDatabaseRestApiuses viabase_filters = [["id", DatabaseFilter, lambda: []]]— to the database branch, so the database inventory is scoped exactly the way every other database path scopes it. The rawsqlalchemy_uriis not exposed by the serializer (it is loaded only sobackendcan be derived).Security framing (per
SECURITY.md): this is an in-scope authorization-scoping fix, not a regression introduced by any recent change. The gap is pre-existing on master. Built-in Gamma reached this endpoint before (viacan_connections) and after (viacan_read), so built-in-role reach is unchanged; what changed upstream is that the endpoint is now gated on the coarse, commonly-grantedcan_readand advertised as broadly accessible, so an operator can no longer grant "browse semantic layers" without also handing over the database inventory. Database/datasource inventory is object-scoped viaDatabaseFilteron every other path.DatabaseFilterbase_filters on the Database API.can_readonSemanticLayer.BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF
N/A — backend authorization scoping, no UI change.
TESTING INSTRUCTIONS
Unit tests in
tests/unit_tests/semantic_layers/api_test.py:test_connections_all_access_user_sees_all_databases— a full-access caller has the database branch left unfiltered.test_connections_limited_user_access_filters_databases— a caller without all-database access hasDatabaseFilter's access predicate applied (assertsuser_view_menu_names("database_access")was consulted). Reverting the fix (baredb.session.query(Database)) fails this test.Manual: as a Gamma user (or a custom role with only
can_readonSemanticLayer) that lacks access to some databases,GET /api/v1/semantic_layer/connections/returns only the databases the user can access; an all-access user still sees all.ADDITIONAL INFORMATION
SEMANTIC_LAYERS(the endpoint is gated behind it; default off)🤖 Generated with Claude Code
https://claude.ai/code/session_01267VBWbvWTNZUg9GvXKgkC