feat(admin): invitationslinks med gruppe og udløbstid
Udrullet direkte til prod 2026-09-29; importeret her så repo == prod. Bruger Authentik-flowet weircon-invitation (grupper og engangs via fixed_data og politikker i flowet). Claude-Session: https://claude.ai/code/session_012LCTmU2ewo8tnM1dByYAoG
This commit is contained in:
@@ -281,3 +281,58 @@ async def set_app_access(app_pk, want_groups, want_users):
|
||||
ops.append(c.delete(f"/api/v3/policies/bindings/{pk}/"))
|
||||
await asyncio.gather(*ops)
|
||||
return len(want_g - set(cur_g)) + len(want_u - set(cur_u)), len(dels)
|
||||
|
||||
|
||||
# --- Invitationer (enrollment-flowet "weircon-invitation" i Authentik) -------
|
||||
# Modtageren vælger selv brugernavn, navn, e-mail og kode. Gruppe(r) og
|
||||
# engangs-flag ligger i invitationens fixed_data og håndteres af politikker i
|
||||
# flowet: "weircon-invitation-grupper" (lægger brugeren i grupperne lige før
|
||||
# oprettelse) og "weircon-invitation-forbrug" (sletter en engangs-invitation
|
||||
# FØRST når brugeren er oprettet; Authentiks eget single_use sletter allerede
|
||||
# når linket åbnes, så et link der bare blev kigget på, ville være brændt).
|
||||
INV_FLOW = "weircon-invitation"
|
||||
# Grupper der aldrig giver mening at invitere til (service-/systemgrupper).
|
||||
INV_SKJUL = {"authentik Read-only", "authentik Agent-Users", "ldapservice", "sonderlund-avatar-læs"}
|
||||
|
||||
|
||||
def invitation_url(pk):
|
||||
return f"{AUTHENTIK}/if/flow/{INV_FLOW}/?itoken={pk}"
|
||||
|
||||
|
||||
async def list_invitations():
|
||||
async with _client(15) as c:
|
||||
rows = (await c.get("/api/v3/stages/invitation/invitations/",
|
||||
params={"page_size": 100, "ordering": "expires"})).json().get("results", [])
|
||||
out = []
|
||||
for r in rows:
|
||||
if (r.get("flow_obj") or {}).get("slug") != INV_FLOW:
|
||||
continue
|
||||
fd = r.get("fixed_data") or {}
|
||||
out.append({"pk": r["pk"], "name": r.get("name"), "expires": r.get("expires"),
|
||||
"grupper": fd.get("weircon_grupper") or [], "engangs": bool(fd.get("weircon_engangs")),
|
||||
"af": (r.get("created_by") or {}).get("username") if isinstance(r.get("created_by"), dict) else None,
|
||||
"url": invitation_url(r["pk"])})
|
||||
return out
|
||||
|
||||
|
||||
async def create_invitation(label, group_pks, minutes, engangs):
|
||||
"""Opret en invitation. Returnerer (ok, pk eller fejltekst)."""
|
||||
import re, secrets
|
||||
from datetime import datetime, timedelta, timezone
|
||||
slug = re.sub(r"[^a-z0-9_-]+", "-", (label or "invitation").lower()).strip("-")[:40] or "invitation"
|
||||
slug = f"{slug}-{secrets.token_hex(3)}"
|
||||
udloeb = (datetime.now(timezone.utc) + timedelta(minutes=int(minutes))).isoformat()
|
||||
async with _client(20) as c:
|
||||
flow = (await c.get(f"/api/v3/flows/instances/{INV_FLOW}/")).json()
|
||||
r = await c.post("/api/v3/stages/invitation/invitations/", json={
|
||||
"name": slug, "expires": udloeb, "single_use": False, "flow": flow["pk"],
|
||||
"fixed_data": {"weircon_grupper": [str(g) for g in group_pks], "weircon_engangs": bool(engangs)}})
|
||||
if r.status_code >= 300:
|
||||
return False, r.text[:300]
|
||||
return True, r.json()["pk"]
|
||||
|
||||
|
||||
async def delete_invitation(pk):
|
||||
async with _client(15) as c:
|
||||
r = await c.delete(f"/api/v3/stages/invitation/invitations/{pk}/")
|
||||
return r.status_code < 300
|
||||
|
||||
Reference in New Issue
Block a user