eschutho opened a new pull request, #45792:
URL: https://github.com/apache/superset/pull/45792

   Shortcut: sc-129171 (related: sc-128628)
   
   ## Root cause
   On Snowflake a schema-level Data Access Role (DAR) grant never matches at 
runtime because of catalog casing.
   
   - Core permission sync (`superset/commands/database/utils.py`, 
`add_permissions`, ~L242-256) builds `catalog_access`/`schema_access` rows from 
`database.get_all_catalog_names()`. Snowflake lists UPPERCASE names, giving 
e.g. `[salesplanning_db].[SALESPLANNING_DB].[consumer_marts]`.
   - Datasets / runtime use the URI default catalog 
(`SnowflakeEngineSpec.get_default_catalog`, 
`db_engine_specs/snowflake.py:463`), i.e. lowercase: 
`[salesplanning_db].[salesplanning_db].[consumer_marts]`.
   - Core compares exactly: `can_access_catalog` 
(`superset/security/manager.py:2538`), `can_access_schema` (:2591), 
`get_schemas_accessible_by_user` (:3111, `parts[1] == catalog` ~L3162), 
`get_catalogs_accessible_by_user` (:3188), `get_datasources_accessible_by_user` 
(:3282-3296), `raise_for_access` (:5434-5442).
   - The DAR editor lists all data-access PVMs (`sm.get_data_access_pvms`, 
`preset/security/manager.py`), so the uppercase row is selectable. The Manager 
event path (`workspace_pub_sub._build_pvm_from_dar_event_permissions` -> 
`find_permission_view_menu`) is exact-match and silently drops misses.
   - Full `database_access` short-circuits all of this, which is why that 
workaround works.
   
   ## Fix
   No submodule changes. In `SupersetShellSecurityManager` 
(`preset/security/manager.py`), using the pure helpers in the new 
`preset/security/catalog_case.py`:
   
   - `can_access` override: if the exact check fails for 
`catalog_access`/`schema_access`, allow when the user holds a grant identical 
except for the **catalog segment's case**, and the database is Snowflake. This 
single seam covers every core `can_access("catalog_access"|"schema_access", 
...)` caller (`can_access_catalog`, `can_access_schema`, datasource listing, 
`raise_for_access`) without copying any core method.
   - `user_view_menu_names` override: for Snowflake databases, adds 
lower/upper-case catalog spellings of the user's catalog-scoped grants. This 
covers the set-membership code that bypasses `can_access` 
(`get_schemas_accessible_by_user`, `get_catalogs_accessible_by_user`, 
`utils/filters.py`).
   - New `find_permission_view_menus_case_insensitive`, used by 
`_build_pvm_from_dar_event_permissions`: returns the exact PVM plus every other 
PVM that differs only by catalog case (all variants are attached to the DAR). 
`perm_urn_to_vm` is unchanged.
   
   Gating: explicit allowlist `CASE_INSENSITIVE_CATALOG_BACKENDS = 
{"snowflake"}` keyed on `Database.backend`. There is no engine-spec attribute 
for identifier case sensitivity in core, so the allowlist lives here.
   
   ## Trade-offs / risks
   - **Access widening**: only the catalog segment is relaxed. Database name, 
schema name and permission kind still compare exactly; a different 
database/schema/catalog never matches (tested). Residual risk: Snowflake quoted 
identifiers can in theory create two catalogs differing only by case; they 
would now be treated as one for grants. Considered acceptable for Snowflake 
(very rare, catalogs are normally unquoted); it is not applied to 
case-sensitive engines.
   - **Schema case is not relaxed**: the evidence only shows a catalog 
mismatch, and relaxing schema widens access further. If schema casing turns out 
to diverge too, extend `differs_only_by_catalog_case`.
   - `user_view_menu_names` expansion only lower/upper-cases the catalog; a 
mixed-case catalog is still handled by the `can_access` path but not by the 
set-based paths.
   - Extra cost: one `Database` lookup per call when the user holds 
catalog-scoped grants, and per failed exact `can_access` only when a 
case-variant grant exists.
   - **Silent-revocation caveat (#5650)**: DAR updates replace the permission 
list wholesale, and unresolvable permissions are still dropped (now only after 
the case-insensitive lookup). This PR does not change that.
   - **Upstream**: the root problem (sync vs. default-catalog casing) belongs 
in apache/superset (normalise catalog case at permission creation/check for 
case-insensitive engines). It lives in the shell for now because core is the 
submodule; an upstream follow-up is desirable and this override can then be 
deleted.
   
   ## Dependency on open PRs
   #5650 and #5657 are both still open and also edit 
`_build_pvm_from_dar_event_permissions`. This PR replaces the 
`find_permission_view_menu` lookup with 
`find_permission_view_menus_case_insensitive`, so whichever lands second will 
conflict textually in those few lines, and the tests in those PRs that mock 
`_sm.find_permission_view_menu` will need to mock the new method instead. 
`perm_urn_to_vm` (touched by #5650) is not modified here.
   
   ## Verified vs. not verified
   Verified (by reading the code at the pinned submodule `bea6db6d`): all line 
references above; Snowflake `get_default_catalog`; exact comparisons in core.
   
   Verified by running: the new unit tests (below) in a **stubbed local 
environment** (see test plan). They run the real core 
`get_schemas_accessible_by_user`, `get_catalogs_accessible_by_user`, 
`can_access_catalog` and `can_access_schema` against 
`SupersetShellSecurityManager`, with the DB lookups faked.
   
   NOT verified:
   - No run against a real Snowflake database, real Superset metadata DB, or 
the Docker stack / Manager (private packages / VPN unavailable here). Nothing 
in a deployed environment was touched.
   - The full repo test suite and pre-commit `mypy` were not run; CI is the 
first real run of the project's conftest/`BaseTest` harness. These tests 
deliberately avoid it.
   - That the DAR editor/Manager flow end-to-end now yields schemas in SQL Lab.
   - That schema names never differ in case in practice.
   
   ## Test plan
   Local (stubbed env, not the repo's standard environment): a venv with 
`superset-oss` installed editable; the private `clients`, `service_lib` and 
`cloudevents` packages and other uninstalled third-party modules were replaced 
by `MagicMock` import stubs.
   
   ```
   pytest --noconftest tests/security/test_catalog_case.py 
tests/multi_tenant/test_dar_permission_case_variants.py
   # 37 passed
   ruff check / ruff format (v0.4.0, pinned) on changed files: clean
   ```
   Scenarios covered: user holds `schema_access` 
`[salesplanning_db].[SALESPLANNING_DB].[consumer_marts]`, default catalog 
`salesplanning_db`, candidates {consumer_marts, other} -> only 
`consumer_marts`; explicit catalog in either case; dataset-level 
`can_access_schema` with lowercase `schema_perm`; `catalog_access` variant; 
event path (lowercase URN -> uppercase PVM, all variants included); negatives 
(case-sensitive engine, no grant, other catalog / database / schema, 
schema-case difference, anonymous user, non-Snowflake database).
   CI should run: `pytest tests/security/test_catalog_case.py 
tests/multi_tenant/test_dar_permission_case_variants.py`.
   
   🤖 Generated with [Claude Code](https://claude.com/claude-code)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to