Baseline before the clean-up (PD-13): the PD-11 reader setup and the PD-12 app dashboards as they ran on the local stack, uncommitted until now. Not deployed, not independently reviewed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
77 lines
2.9 KiB
Python
77 lines
2.9 KiB
Python
"""The role "ATOMA Reader": read the dashboards granted to it, nothing else.
|
|
|
|
Runs inside the Superset container, after `superset init` (stacks/fuel-monitoring/superset/init.sh):
|
|
|
|
docker exec <superset> /app/.venv/bin/python /app/petrolyst-roles/atoma_reader.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. Readers reach a
|
|
dashboard only when it lists this role (DASHBOARD_RBAC; dashboards/atoma/import_bundle.py
|
|
attaches it), and petrolyst_reader.py adds the rules a role cannot express.
|
|
|
|
The list was established on the local stack by reading the ATOMA dashboard as a reader:
|
|
everything the 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 = "ATOMA Reader"
|
|
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 (atoma_guest.py too)."""
|
|
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())
|