"""What a dashboard share link opens. A share link's page (/share/) loads each of its dashboard's widgets from a fragment URL with `&s=` appended. The token opens exactly those URLs, read-only: the same path and the same configured parameters (which host, which container). Only the time window a viewer can switch on the widget itself (VIEW_PARAMS) may differ. Every other page, and the same widget aimed at a different host, still needs a login. """ from __future__ import annotations import hashlib from datetime import datetime, timezone from urllib.parse import parse_qsl from sqlalchemy import select from steward.models.dashboard import DashboardShareToken # Parameters a shared widget may change in place: they pick a time window, # never which thing is shown. VIEW_PARAMS = frozenset({"hours", "range"}) def share_allows(widget_urls: list[str], path: str, args) -> bool: """True when `path` + `args` (minus the token) is one of `widget_urls`, allowing only VIEW_PARAMS to differ.""" got: dict[str, str] = {} for key in args: if key == "s": continue values = args.getlist(key) if len(values) != 1: return False got[key] = values[0] for url in widget_urls: wpath, _, query = url.partition("?") if wpath != path: continue want = {k: v for k, v in parse_qsl(query, keep_blank_values=True) if k not in VIEW_PARAMS} if all(got.get(k) == v for k, v in want.items()) and set(got) <= set(want) | VIEW_PARAMS: return True return False async def share_grants(db, raw_token: str, path: str, args) -> bool: """True when `raw_token` is a live share token whose dashboard loads this URL.""" token_hash = hashlib.sha256(raw_token.encode()).hexdigest() share = (await db.execute( select(DashboardShareToken).where(DashboardShareToken.token_hash == token_hash) )).scalar_one_or_none() if share is None: return False if share.expires_at is not None and share.expires_at <= datetime.now(timezone.utc): return False from .routes import _get_widgets, _resolve_widgets widgets = _resolve_widgets(await _get_widgets(db, share.dashboard_id)) return share_allows([w["hx_url"] for w in widgets], path, args)