Skip to main content
Petanque Life

Role & Permission Management

F02.03 10 features Shipped

At a glance

Role & Permission Management is the capability-based RBAC layer that authorises every API call on the platform. Capabilities are deny-by-default, grouped into reusable role templates (FIPJP Admin, Club President, Umpire L1-L5, Tournament Director and others), and assigned per user per tenant with an optional OrgNode scope so authority always matches organisational reality.

How it works

The authorisation model is built on capabilities, not roles. Each endpoint declares the capabilities it requires (e.g. competitions:create, members:read, finance:export); the auth middleware looks up the caller's effective capability set and denies any request whose required capabilities are not present. Roles are convenience containers: a role document holds a list of capabilities (with wildcard support such as competitions:* or *:read), and assigning a role to a user grants the union of its capabilities at the assignment's scope.

Scope is two-dimensional — tenant_id is mandatory, and an optional org_node_id narrows the grant to a subtree. A District Admin role assigned at a Swedish district OrgNode therefore yields competitions:* only for clubs and players in that subtree, while the same role assigned to a national admin yields it tenant-wide. Role inheritance lets templates extend each other (national-admin inherits regional-admin), keeping the predefined catalogue maintainable.

The petanque-specific catalogue ships with FIPJP Admin, Continental President, National Federation Admin, National Sports Director, District Admin, Club President/Secretary/Treasurer, Licensed Player, Umpire L1-L5, Tournament Director, Coach, Scorer/Volunteer and Spectator, each tuned to a realistic capability set. Sensitive-field restrictions add a column-level overlay — even a role with members:read may have date_of_birth or personal_id_number stripped from responses unless a more privileged capability is held. Temporary role grants attach a valid_from/valid_until window so a tournament director gets full event-management capabilities for one weekend only and is automatically removed afterwards.

Role request and approval workflows let users self-request (e.g. an aspiring umpire applying for L1 once certified), with the appropriate higher role acting as approver. Access debugging tools let administrators simulate "why was this request denied?" and trace the exact missing capability.

Key capabilities

  • Capability-based access control with deny-by-default enforcement and wildcard support
  • Reusable role templates with inheritance across the petanque-specific catalogue
  • Tenant + OrgNode scoping for every role assignment
  • Sensitive-field restrictions for column-level data masking
  • Temporary role grants with valid_from/valid_until windows
  • Self-service role request and approval workflow
  • Access debugging tools for tracing denied requests

In practice

An umpire passes the L3 examination at a national clinic. From his profile he requests the Umpire L3 role; the request is routed to the National Sports Director, who approves it with one click. The role is assigned at tenant scope (national federation) and instantly grants matches:officiate and disciplinary:report capabilities.

The next weekend he is appointed Tournament Director for a single regional competition; the federation admin issues a temporary grant valid only Friday-Sunday, scoped to that event. On Monday morning the temporary capabilities expire automatically and he is back to his standing umpire role, with the access debugger showing exactly which capabilities were active at any moment of the weekend.

Features in this subsystem

10
ID Status Features
F02.03.01 Shipped Capability-based access control (deny-by-default) ✅ PL-F0203a, 🔁 Go-backend f07177c7.m-1 (F1-4 AuthZ m-1) — register + persistens: capability-registret materialiserat som tunt internt Go-paket api/internal/authz/ — capabilities-referenstabell (00009_authz.sql) + deklarativ in-process-katalog (catalog.go) med namnrymds-grammatik (<namespace>.<…> / <resurs>:<verb>, validerad av ValidateName) och is_sensitive_field-markör; idempotent seed-reconcile av katalogen. ✅ Go-backend f07177c7.m-2 (F1-4 AuthZ m-2) — enforcement-spine: beslutsoraklet authz.Decide (rent, fail-closed, en beslutskälla); route-markerande primitiv (RouteRegistry.Public/SelfGuarded/Require) + deny-by-default-boot-gate (CheckRoutes → serve-vägran + api authz-check-subkommando i CI — en omarkerad route = boot-fel, ej runtime-läcka); gaten fyller F1-1:s reserverade middleware.Authz()-slot; tenant-resolution + X-Tenant-Id-fallback + spoof (JWT-claim auktoritativ, fail-closed, 403 tenant_spoof); agent-scope-block på beslutsnivå (finance.* nekas agent-aktörer, 403 agent_action_blocked); audit på varje deny/spoof/agent-block; tunn orakel-HTTP-yta (GET /me/capabilities, POST /authz/capability-check(-batch)); felkod capability_denied. ✅ Go-backend f07177c7.m-3 (F1-4 AuthZ m-3) — data-lager-isolering + fält-maskering: tree-aware orakel + authz.ScopeTree-subträdsexpansion (fixtur-impl tills F1-9), ScopeFilter (fail-closed list-filtrering till caller:s synliga subträd), ScopedLookup (enforce:at 404-ej-403 på cross-scope/cross-tenant-miss + intern scope-miss-audit), scope-medveten RequireCapability (RequireScoped, beslut i resursens scope) och MaskSensitive (generisk DTO-agnostisk fält-maskering — authz:"sensitive=<cap>"-fält utelämnas för caller utan <cap>, 200 på resten, aldrig 403 på hela posten; VisibilityPolicy-krok för privacy_settings). Roll-/sys-CRUD och UI är senare F1-4-milstolpar; den riktiga org_nodes-hierarkin ägs av F1-9 (plugg:as i ScopeTree-seamen utan signaturändring). ✅ Go-backend f07177c7.m-4 (F1-4 AuthZ m-4) — sys cross-tenant-roller + agent-block-härdning: deterministisk sys-roll → capability-mappning (sysroles.go) + fail-closed sys-capability-resolver (sysresolver.go, cross-tenant); /sys/roles-ytan (GET /sys/roles, GET/POST /sys/users/{email}/roles, DELETE /sys/users/{email}/roles/{role}) med four-eyes self-grant-block (400 self_grant_blocked), fresh-auth (401 reauth_required) och idempotent grant; sys-roll-backad gate ersätter env-allowlisten (allowlist = A4-bootstrap); agent-scope-block härdat — konfigurerbar finans-lista (PL_AGENT_BLOCKED_NAMESPACES) + garantin att en agent som impersonerar sys_finance ändå nekas finans (agent_scope_block); audit sys.role.grant/sys.role.revoke. Kopierbart mönster i api/README.md → "AuthZ fundament (F1-4)" §6–§7
F02.03.02 Shipped Role definition with capability sets ✅ PL-F0203a, 🔁 Go-backend f07177c7.m-1 (F1-4 m-1): roll-modellen (roles, role_capabilities) + de sju seedade plattforms-mall-rollerna (player, umpire, coach, clubadmin, district_admin, federation_admin, support_member — tenant_id=NULL, is_system=true, deterministisk dokumenterad capability-uppsättning per roll); additiva roller (api/internal/authz/seed.go)
F02.03.03 Shipped Role inheritance (e.g., national-admin inherits regional-admin) ✅ PL-F0203a
F02.03.04 Shipped Role assignment per user per tenant (+ OrgNode scope for regional/club roles) ✅ PL-F0203a, 🔁 Go-backend f07177c7.m-1 (F1-4 m-1): role_assignments (principal IN A SCOPE — opakt tenant_id/scope_id/principal_id utan FK, polymorf principal, idempotent UNIQUE-tuple) + den rena fail-closed datalager-resolvern (EffectiveCapabilities — additiv union över aktiva, icke-utgångna tilldelningar, scope-bevarande, tom mängd vid saknad tenant = F-AT-03-skydd; ingen trädexpansion/Allow-Deny — senare milstolpe)
F02.03.05 Shipped Wildcard capabilities (e.g., competitions:*) ✅ PL-F0203a
F02.03.06 Shipped Sensitive field restrictions ✅ PL-F0203b (tabelldriven SENSITIVE_FIELD_SPECS, filter_document/filter_documents, assert_write_allowed, profile:read_sensitive-override, GET /sensitive-fields, POST /sensitive-fields/check), 🔁 Go-backend f07177c7.m-3 (F1-4 AuthZ m-3): MaskSensitive (api/internal/http/authz_mask.go) — generiskt, DTO-agnostiskt primitiv som vid serialisering utelämnar varje fält taggat authz:"sensitive=<capability>" när caller saknar <capability> i resursens scope (prövat via samma orakel), 200 på resten av posten, aldrig 403 på hela; field-level-capabilities privacy.personal_data.read/privacy.medical.read/privacy.safeguarding.read/finance.account.read (is_sensitive_field=true) idempotent seedade; VisibilityPolicy-krok för privacy_settings (full modell ägs av D-MEMBER/D-GDPR)
F02.03.07 Shipped Petanque-specific role templates (see below) ✅ PL-F0203b (category/suggested_scope/is_temporal/is_petanque_specific/requires_approval metadata på RoleTemplate, GET /role-templates?category=...&scope=...)
F02.03.08 Shipped Access debugging tools ✅ PL-F0203b (GET /access-debug/role-tree/{name} BFS inkl. cycle/missing-markering, GET /access-debug/users/{id}?at=... active/inactive split, POST /access-debug/check med granting_roles)
F02.03.09 Shipped Temporary role grants (e.g., tournament director for one event) ✅ PL-F0203b (POST /role-grants/temporary, GET /role-grants/temporary?active_only=..., DELETE /role-grants/{id}, exkluderar permanenta grants automatiskt)
F02.03.10 Shipped Role request and approval workflow ✅ PL-F0203b (RoleRequest-dokument, POST /role-requests med dubblettguard 409, /approve med scope-narrowing, /deny, /cancel med requester-only guard 403)