feat(policy): phase 5a policy model, parser, generator and runtime mapper #14

Merged
KofTwentyTwo merged 1 commits from feature/policy-model into develop 2026-10-13 18:44:05 +00:00
KofTwentyTwo commented 2026-10-13 18:34:56 +00:00 (Migrated from github.com)

Phase 5a of DESIGN.md section 12 (spec #13): IAM-style policy documents stored on Authentik groups.

What is in it

  • Format (docs/POLICY-FORMAT.md): PolicyParser fails closed. An unknown spec or version, a non-Allow effect (including Deny), an unknown action, a resource outside [A-Za-z0-9_]+, or malformed JSON grants nothing and logs a warning. parseStrict names the problem for the editor.
  • Generator: PolicyGenerator writes the canonical form: one statement per action, in the order hasAccess, read, insert, edit, delete; resources sorted; no *. Compact and pretty writers use a fixed key order. Every generated document is checked to round-trip to exactly the permissions it came from.
  • Config: policySpec (qqq-iam), policyVersion (1.0.0), policyAttribute (qqq.io/policy), policyClaim (qqq_policy) and policyTwoPersonApproval (off), all validated. A host can keep its own spec name, attribute and claim.
  • Runtime:
    • AuthentikPolicyClaimMapper reads the claim from the ID token, else the access token, and adds the permissions. It has an applySecurityKeys hook as the record-lock extension point.
    • PolicyPermissions.fromClaims serves a host's own customizer.
    • AuthentikGroupPermissionMapper also adds the claim.
  • Graph and resolver: GroupGraph parses each group's qqq.io/policy and fails closed per group; a bad document is listed as malformed. EffectivePermissionResolver includes policy grants, so effective permissions, the subset rule and the last-admin check see them.
  • Blueprint example: the scope mapping merges the policies of all_groups() into the claim. PolicyClaimMappingIT pins it on Authentik 2026.8.3 through the property-mapping test endpoint: inherited parents, object-valued attributes, other specs skipped, and an empty document for users without policies.

Tests

CI=true ./mvnw verify: 180 unit tests and 42 contract tests on Authentik 2026.8.3, all green; checkstyle and JaCoCo pass. New tests:

  • PolicyParserTest: 24 fail-closed cases plus semantics.
  • PolicyGeneratorTest: canonical bytes, 500 random round trips, refusals.
  • AuthentikPolicyClaimMapperTest: ID token, access token, fail closed, custom names, the hook.
  • PolicyGraphTest: inheritance, per-group fail-closed, catalog filtering.
  • Mapper and config additions.
  • PolicyClaimMappingIT.

🤖 Generated with Claude Code

Phase 5a of DESIGN.md section 12 (spec #13): IAM-style policy documents stored on Authentik groups. ## What is in it - **Format** (`docs/POLICY-FORMAT.md`): `PolicyParser` fails closed. An unknown spec or version, a non-`Allow` effect (including `Deny`), an unknown action, a resource outside `[A-Za-z0-9_]+`, or malformed JSON grants nothing and logs a warning. `parseStrict` names the problem for the editor. - **Generator**: `PolicyGenerator` writes the canonical form: one statement per action, in the order hasAccess, read, insert, edit, delete; resources sorted; no `*`. Compact and pretty writers use a fixed key order. Every generated document is checked to round-trip to exactly the permissions it came from. - **Config**: `policySpec` (`qqq-iam`), `policyVersion` (`1.0.0`), `policyAttribute` (`qqq.io/policy`), `policyClaim` (`qqq_policy`) and `policyTwoPersonApproval` (off), all validated. A host can keep its own spec name, attribute and claim. - **Runtime**: - `AuthentikPolicyClaimMapper` reads the claim from the ID token, else the access token, and adds the permissions. It has an `applySecurityKeys` hook as the record-lock extension point. - `PolicyPermissions.fromClaims` serves a host's own customizer. - `AuthentikGroupPermissionMapper` also adds the claim. - **Graph and resolver**: `GroupGraph` parses each group's `qqq.io/policy` and fails closed per group; a bad document is listed as malformed. `EffectivePermissionResolver` includes policy grants, so effective permissions, the subset rule and the last-admin check see them. - **Blueprint example**: the scope mapping merges the policies of `all_groups()` into the claim. `PolicyClaimMappingIT` pins it on Authentik 2026.8.3 through the property-mapping test endpoint: inherited parents, object-valued attributes, other specs skipped, and an empty document for users without policies. ## Tests `CI=true ./mvnw verify`: 180 unit tests and 42 contract tests on Authentik 2026.8.3, all green; checkstyle and JaCoCo pass. New tests: - `PolicyParserTest`: 24 fail-closed cases plus semantics. - `PolicyGeneratorTest`: canonical bytes, 500 random round trips, refusals. - `AuthentikPolicyClaimMapperTest`: ID token, access token, fail closed, custom names, the hook. - `PolicyGraphTest`: inheritance, per-group fail-closed, catalog filtering. - Mapper and config additions. - `PolicyClaimMappingIT`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.