Sign-in by customer: realm petrolyst, row rules, guest role (PD-21)
Keycloak import for realm petrolyst: two clients, petrolyst with MFA for Superset and Traefik, petrolyst-app password only. It lists only app.petrolyst.com and traefik.petrolyst.com, and no customer names, as this repository is public. public.v_operator shows each customer's group. Dashboards overview and site, role Petrolyst Guest, filtered by row. Superset refuses a user with no mapped role. The realm travels with the release: each release's compose files name their realm and secret file, and a rollback needs only its target's secret. Not deployed. Checks earlier on this content: tests/run.sh 109 of 109 and read-only, dashboards import twice, column drift 0, compose config on old and new trees, the secret check on a scratch folder, a rehearsal on a throwaway Keycloak 26.4. Never a real deploy or rollback; shellcheck not installed today, clean when last run. Codex inspected once; its fixes are not re-inspected, on the owner's word. Bands: persistence, Superset. Rules: 1, 2, 3, 6, 14. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ab553526f6
commit
78a498f1ce
20 files changed
+279
-78
No files matched your search
@@ -0,0 +1,81 @@
|
||||
"""The role "Petrolyst Guest": what the Petrolyst app's embedded dashboards may do (PD-12).
|
||||
|
||||
Superset gives every embedded viewer (a guest pass signed for one dashboard) the role named by
|
||||
GUEST_ROLE_NAME. Which dashboard and which customer's rows a guest sees come from the pass, not from
|
||||
the role. Superset
|
||||
leaves the SQL out of chart-data responses for guests.
|
||||
|
||||
Embedding is switched on in the local stack only (.local/fmdb-scratch, superset_config_local.py),
|
||||
so this script is not part of init.sh; run it by hand inside the Superset container, once, and
|
||||
again whenever PERMISSIONS change:
|
||||
|
||||
docker exec <superset> /app/.venv/bin/python /app/petrolyst-roles/petrolyst_guest.py
|
||||
|
||||
Creates the role if needed and gives it exactly PERMISSIONS: missing ones are added, any other
|
||||
is removed (also one added by hand in the UI). A second run changes nothing.
|
||||
|
||||
The list was established on the local stack by reading the ATOMA dashboard as a reader (the
|
||||
former "ATOMA Reader" role, retired with PD-13): everything a dashboard asks the server for, and
|
||||
nothing more. No menu access, nothing to write, no Explore, CSV, sharing, drill or query view.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
ROLE = "Petrolyst Guest"
|
||||
PERMISSIONS = [
|
||||
("can_read", "Dashboard"), # the dashboard and its charts' definitions
|
||||
("can_dashboard", "Superset"), # the dashboard page
|
||||
("can_dashboard_permalink", "Superset"), # a dashboard permalink
|
||||
("can_read", "Chart"), # chart data
|
||||
("can_time_range", "Api"), # the Viewing period filter
|
||||
("can_read", "DashboardFilterStateRestApi"), # native filter state
|
||||
("can_write", "DashboardFilterStateRestApi"),
|
||||
("can_read", "DashboardPermalinkRestApi"),
|
||||
("can_read", "CurrentUserRestApi"), # who is signed in
|
||||
("can_read", "SecurityRestApi"), # the CSRF token
|
||||
("can_get", "MenuApi"),
|
||||
("can_log", "Superset"), # Superset's event log
|
||||
]
|
||||
|
||||
|
||||
def sync(name: str, permissions: list[tuple[str, str]]) -> int:
|
||||
"""Give the role `name` exactly `permissions`, creating it if needed."""
|
||||
from superset.app import create_app
|
||||
|
||||
app = create_app()
|
||||
with app.app_context():
|
||||
from superset import db, security_manager as sm
|
||||
|
||||
wanted = []
|
||||
for permission, view in permissions:
|
||||
pvm = sm.find_permission_view_menu(permission, view)
|
||||
if pvm is None:
|
||||
print(f"no permission {permission} on {view}: run `superset init` first?",
|
||||
file=sys.stderr)
|
||||
return 1
|
||||
wanted.append(pvm)
|
||||
|
||||
role = sm.find_role(name) or sm.add_role(name)
|
||||
added = [pvm for pvm in wanted if pvm not in role.permissions]
|
||||
removed = [pvm for pvm in role.permissions if pvm not in wanted]
|
||||
for pvm in added:
|
||||
sm.add_permission_role(role, pvm)
|
||||
for pvm in removed:
|
||||
sm.del_permission_role(role, pvm)
|
||||
db.session.commit()
|
||||
|
||||
print(f'role "{name}": {len(wanted)} permissions, {len(added)} added, '
|
||||
f"{len(removed)} removed")
|
||||
for pvm in removed:
|
||||
print(f" removed {pvm.permission.name} on {pvm.view_menu.name}")
|
||||
return 0
|
||||
|
||||
|
||||
def main() -> int:
|
||||
return sync(ROLE, PERMISSIONS)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user