feat(policy): phase 5b policy editor and save to Authentik #15

Merged
KofTwentyTwo merged 1 commits from feature/policy-editor into develop 2026-10-13 18:54:58 +00:00
KofTwentyTwo commented 2026-10-13 18:48:01 +00:00 (Migrated from github.com)

Phase 5b of DESIGN.md section 12.4: the policy editor and saving policies to Authentik. It builds on #14.

What is in it

  • Edit Policy: a record action on authentik_group and on authentik_policy, never in the nav. It needs the POLICIES feature (which needs GROUPS) and authentik.policies.manage. It is a standard streamed-ETL action:
    • Edit step: shows the AUTHENTIK_POLICY_EDITOR widget. The Next UI component is a separate PR in qqq-frontend-next. The widget hands the generated document back as the process value policyDocument; API clients post that value directly.
    • Review (a dry run):
      • parses the document, failing closed
      • regenerates it in canonical form and checks the round trip
      • refuses new resources the app does not define, and keeps existing unknowns with a note
      • applies the subset rule to the permissions it adds
      • lists what is added and removed, and how many members are affected
    • Result: re-reads the group, merge-patches qqq.io/policy into its current attributes (the key is removed when the policy is empty), and writes the audit row and the Authentik event. Second-person approval is optional (policyTwoPersonApproval).
  • PolicyCatalog: the apps, tables, processes, reports and protected widgets a policy can grant, grouped by app, with the actions QQQ actually checks.
    • Table actions are limited by the table's capabilities and permission level.
    • Hidden record actions are included.
    • READ_WRITE tables offer read only, with a note.
    • Objects that are not protected, or have a custom checker or a dotted base name, are left out.
    • The catalog's permission names join the runtime permission catalog.
  • authentik_policy: a local table with one row per group, written only by Edit Policy. Field-level QQQ audit is the change history. It is in the nav under Access and appears as a child list on the group. The DDL is in docs/schema.sql.
  • Widget authentikPolicyEditor: sends the catalog and the group's live document, the source of truth. It flags a policy changed in Authentik since the last QQQ save, and an invalid stored document.

Tests

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

  • PolicyCatalogTest: apps, capabilities, levels, shared base names, hidden actions, Other, exclusions.
  • PolicyEditorTest:
    • a save that writes the canonical document and the history row
    • no change, and removal
    • refusals: invalid document, other spec, unknown resource, subset rule, superuser group, stale fingerprint
    • kept unknowns, and replacing an invalid stored document
    • gating by permission and feature
    • two-person approval
    • the process end to end, from a group and from a policy record
    • widget data, including the changed-in-Authentik flag
  • QbitWriteContractIT.savePolicy: on a real Authentik, the subset refusal, the exact stored string, other attributes kept, the graph reading the policy back, and clearing it.

🤖 Generated with Claude Code

Phase 5b of DESIGN.md section 12.4: the policy editor and saving policies to Authentik. It builds on #14. ## What is in it - **Edit Policy**: a record action on `authentik_group` and on `authentik_policy`, never in the nav. It needs the `POLICIES` feature (which needs `GROUPS`) and `authentik.policies.manage`. It is a standard streamed-ETL action: - **Edit step**: shows the `AUTHENTIK_POLICY_EDITOR` widget. The Next UI component is a separate PR in qqq-frontend-next. The widget hands the generated document back as the process value `policyDocument`; API clients post that value directly. - **Review** (a dry run): - parses the document, failing closed - regenerates it in canonical form and checks the round trip - refuses new resources the app does not define, and keeps existing unknowns with a note - applies the subset rule to the permissions it adds - lists what is added and removed, and how many members are affected - **Result**: re-reads the group, merge-patches `qqq.io/policy` into its current attributes (the key is removed when the policy is empty), and writes the audit row and the Authentik event. Second-person approval is optional (`policyTwoPersonApproval`). - **`PolicyCatalog`**: the apps, tables, processes, reports and protected widgets a policy can grant, grouped by app, with the actions QQQ actually checks. - Table actions are limited by the table's capabilities and permission level. - Hidden record actions are included. - `READ_WRITE` tables offer `read` only, with a note. - Objects that are not protected, or have a custom checker or a dotted base name, are left out. - The catalog's permission names join the runtime permission catalog. - **`authentik_policy`**: a local table with one row per group, written only by Edit Policy. Field-level QQQ audit is the change history. It is in the nav under Access and appears as a child list on the group. The DDL is in `docs/schema.sql`. - **Widget `authentikPolicyEditor`**: sends the catalog and the group's live document, the source of truth. It flags a policy changed in Authentik since the last QQQ save, and an invalid stored document. ## Tests `CI=true ./mvnw verify`: 190 unit tests and 43 contract tests on Authentik 2026.8.3, all green; checkstyle and JaCoCo pass. - `PolicyCatalogTest`: apps, capabilities, levels, shared base names, hidden actions, Other, exclusions. - `PolicyEditorTest`: - a save that writes the canonical document and the history row - no change, and removal - refusals: invalid document, other spec, unknown resource, subset rule, superuser group, stale fingerprint - kept unknowns, and replacing an invalid stored document - gating by permission and feature - two-person approval - the process end to end, from a group and from a policy record - widget data, including the changed-in-Authentik flag - `QbitWriteContractIT.savePolicy`: on a real Authentik, the subset refusal, the exact stored string, other attributes kept, the graph reading the policy back, and clearing it. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.