From d138c5c911ace217c2764f4613d636ba9a49cf1f Mon Sep 17 00:00:00 2001 From: James Maes Date: Tue, 13 Oct 2026 19:51:47 -0500 Subject: [PATCH] chore(deps): build on QQQ 4.1.0-RC.5 Move the core baseline from 4.1.0-RC.2 to the released 4.1.0-RC.5. No source changes were needed. Comments now reflect core's opt-in permission enforcement in 4.1. The synthetic feature-permission catalog in tests is tagged STAND-IN(core=QRun-IO/qqq#1136, removeBy=4.1.0-RC.6), because TableFeaturePermissionsMetaData is on develop but not in RC.5. Refs #20 (architecture review QRun-IO/qqq#1150, finding F19). Co-Authored-By: Claude Opus 5.5 --- AGENTS.md | 4 +-- CHANGELOG.md | 4 +++ CLAUDE.md | 4 +-- README.md | 4 +-- UPGRADE.md | 27 +++++++++++++++++++ docs/DESIGN.md | 8 +++--- docs/POLICY-FORMAT.md | 2 +- pom.xml | 4 +-- .../AuthentikDriftReportWidgetRenderer.java | 2 +- .../authentik/security/AuthentikGuard.java | 6 ++--- .../policy/PolicyFeaturePermissionsTest.java | 4 ++- 11 files changed, 51 insertions(+), 18 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 8720473..e3cab22 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -40,7 +40,7 @@ CI=true ./mvnw verify # + contract ITs: Authentik 2026.8.3 server+worker, Post ## Invariants (do not break) -- Every `AuthentikActionUtil` entry point calls the guard first (QQQ core does not enforce permissions for non-HTTP callers, qqq#1061). +- Every `AuthentikActionUtil` entry point calls the guard first (in addition to core action permission enforcement, retain Authentik-specific rules and protection for direct util calls). - Never log query strings (search terms are PII), the token, successful GET bodies, or bodies of sensitive endpoints; run anything stored through `AuthentikRedactor`. - Never filter client-side; reject what Authentik cannot evaluate. - Multi-value params are repeated plain names (`type=a&type=b`). Authentik silently ignores `type[]=`. @@ -54,7 +54,7 @@ CI=true ./mvnw verify # + contract ITs: Authentik 2026.8.3 server+worker, Post ## Dependencies -Java 21. Parent `qbit-build-parent:2.0.0`. The default-active `qqq-snapshot` profile imports `qqq-bom-pom:4.1.0-RC.2` (ADR-0007, as in qbit-sql-workbench). The project uses `qqq-backend-core` and `qqq-backend-module-api`. Test dependencies: JUnit 5, AssertJ, Mockito, Testcontainers 1.21.4, and Jackson YAML (from qqq) for the schema tests. +Java 21. Parent `qbit-build-parent:2.0.0`. The default-active `qqq-snapshot` profile imports `qqq-bom-pom:4.1.0-RC.5` (ADR-0007, as in qbit-sql-workbench). The project uses `qqq-backend-core` and `qqq-backend-module-api`. Test dependencies: JUnit 5, AssertJ, Mockito, Testcontainers 1.21.4, and Jackson YAML (from qqq) for the schema tests. ## Release diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e861c8..0ff72f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project are documented here. Format: [Keep a Changel ## [Unreleased] +### Changed (core baseline, #20 / F19) +- Build against the released QQQ BOM `4.1.0-RC.5` through the existing `qqq.snapshot.version` property; no core SNAPSHOT is used. No Java API compatibility fixes were needed. +- Core actions now enforce permissions for non-HTTP callers. Authentik-specific guards remain for direct util calls and domain rules. See [UPGRADE.md](UPGRADE.md) for host impact and the remaining test stand-in. + ### Added (table feature permissions) - Policies can grant table feature permissions (e.g. analyze, comment) registered through core TableFeaturePermissionsMetaData diff --git a/CLAUDE.md b/CLAUDE.md index 1ab53bd..b9b8242 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -43,7 +43,7 @@ CI=true ./mvnw verify # + contract ITs: Authentik 2026.8.3 server+worker, Post ## Invariants (do not break) -- Every `AuthentikActionUtil` entry point calls the guard first (QQQ core does not enforce permissions for non-HTTP callers, qqq#1061). +- Every `AuthentikActionUtil` entry point calls the guard first (in addition to core action permission enforcement, retain Authentik-specific rules and protection for direct util calls). - Never log query strings (search terms are PII), the token, successful GET bodies, or bodies of sensitive endpoints; run anything stored through `AuthentikRedactor`. - Never filter client-side; reject what Authentik cannot evaluate. - Multi-value params are repeated plain names (`type=a&type=b`). Authentik silently ignores `type[]=`. @@ -59,7 +59,7 @@ CI=true ./mvnw verify # + contract ITs: Authentik 2026.8.3 server+worker, Post ## Dependencies -Java 21. Parent `qbit-build-parent:2.0.0`. The default-active `qqq-snapshot` profile imports `qqq-bom-pom:4.1.0-RC.2` (ADR-0007, as in qbit-sql-workbench). The project uses `qqq-backend-core` and `qqq-backend-module-api`. Test dependencies: JUnit 5, AssertJ, Mockito, Testcontainers 1.21.4, and Jackson YAML (from qqq) for the schema tests. +Java 21. Parent `qbit-build-parent:2.0.0`. The default-active `qqq-snapshot` profile imports `qqq-bom-pom:4.1.0-RC.5` (ADR-0007, as in qbit-sql-workbench). The project uses `qqq-backend-core` and `qqq-backend-module-api`. Test dependencies: JUnit 5, AssertJ, Mockito, Testcontainers 1.21.4, and Jackson YAML (from qqq) for the schema tests. ## Release diff --git a/README.md b/README.md index 4b1cb61..547fd5e 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ QQQ permissions, held as `.hasAccess`, granted through the group attribute Policies also grant table feature permissions registered through core `TableFeaturePermissionsMetaData`: `analyze` (qbit-data-analysis) and `comment`, `attach`, `tag`, `share`, `task`, `assign` (qbit-collaboration), or any future registered feature. For example, `{"Effect":"Allow","Action":["analyze","comment"],"Resource":["person"]}` grants `person.analyze` and `person.comment`. The resource is the table's effective permission base name. Features must be granted explicitly: policy action `"*"` still means only `hasAccess`, `read`, `insert`, `edit`, `delete`. -The editor discovers features and labels through `PermissionsHelper.getAllAvailablePermissions`; they appear when the host's core version exposes them. This qbit does not compile against the unreleased metadata hook. See [the policy format](docs/POLICY-FORMAT.md) for validation and host customizer usage. +The editor discovers features and labels through `PermissionsHelper.getAllAvailablePermissions`; they appear when the host's core version exposes them. The pinned RC.5 release does not include `TableFeaturePermissionsMetaData`; its later core implementation is a follow-up for the next baseline upgrade. See [the policy format](docs/POLICY-FORMAT.md) for validation and host customizer usage. ### Authentik side @@ -82,7 +82,7 @@ See also the QQQ [adoption checklist](https://github.com/QRun-IO/qqq/blob/develo | Item | What to do | |---|---| | Coordinates | `com.kingsrook.qbits:qbit-authentik`. No release is on Maven Central yet; CircleCI publishes snapshots from `develop`. | -| QQQ version | QQQ 4.1 (see `pom.xml` for the exact RC). Java 21. | +| QQQ version | QQQ 4.1.0-RC.5 (released BOM, no core SNAPSHOT). Java 21. | | Infrastructure | Authentik, contract-tested against 2026.8.3. The API must be reachable from the app at `baseUrl`. | | Tables | Create `authentik_admin_action` and `authentik_approval_request` on your local RDBMS backend (`docs/schema.sql`) before turning write features on. | | Registration | `new AuthentikQBitProducer().withQBitConfig(config).produce(qInstance)`. | diff --git a/UPGRADE.md b/UPGRADE.md index 3da3be3..6f57785 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,5 +1,32 @@ # Upgrading qbit-authentik +## QQQ 4.1.0-RC.5 baseline (#20 / F19) + +Hosts should align their QQQ dependencies with the released `qqq-bom-pom:4.1.0-RC.5` +and use Java 21. The historical `qqq-snapshot` profile and `qqq.snapshot.version` +property names remain, but the imported core version is a release, not a SNAPSHOT. +Core actions now enforce permissions and field security locks for non-HTTP callers +(QRun-IO/qqq#1061, #1062); callers must supply an appropriately authorized session. +The qbit keeps its guards, scope checks and sensitive-field stripping because they +also protect direct backend calls and implement Authentik-specific rules. +No qbit configuration, stored policy format or schema changes are introduced. + +Stand-in audit against core tag `v4.1.0-RC.5`: + +- `PolicyFeaturePermissionsTest` supplies a synthetic available-permission catalog: + `STAND-IN(core=QRun-IO/qqq#1136, removeBy=4.1.0-RC.6)`. RC.6 is the removal target, + subject to verifying that release contains the metadata hook. Retain malformed + and duplicate catalog test cases when adding real metadata registration coverage. +- The requested stand-in/RC grep found only the old baseline references; the broader + audit found the test catalog above and stale core-enforcement explanations. + No production bridge can safely be removed by this baseline bump. +- `TableFeaturePermissionsMetaData` (core commit `9a302136d`, #1136) and client + channels (core commit `741bb4974`, PR #1141) exist after RC.5, but neither commit + is an ancestor of the RC.5 tag and neither API is present in that release. + Follow up on a released baseline containing them: verify the policy editor's + catalog with real feature metadata and adopt client-channel attribution and + identity/credential channel restrictions. These features are not added here. + ## Permissions renamed `identity.*` → `authentik.*` (unreleased → first release) The qbit's permissions follow the same owner naming rule (2026-10-12) and now diff --git a/docs/DESIGN.md b/docs/DESIGN.md index 08a91e7..13377ef 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -24,7 +24,7 @@ Seven design rules shape everything below: - A group's QQQ permissions live in the group attribute `qqq.io/permissions`. - The reusable `AuthentikGroupPermissionMapper` turns a user's groups, including inherited parents, into QQQ session permissions. - Who can operate the qbit is decided by those same permissions (`authentik.*`). No operator group is hard-coded. -4. **The qbit checks permissions itself.** QQQ core actions do not enforce permissions (QRun-IO/qqq#1061), and `FieldSecurityLock` is not enforced (#1062). An `AuthentikGuard` in the backend action layer checks every read and write. +4. **The qbit checks permissions itself.** The original core gaps (QRun-IO/qqq#1061, #1062) are fixed in the RC.5 baseline. Direct backend calls and Authentik-specific rules still require local checks. An `AuthentikGuard` in the backend action layer checks every read and write. 5. **No escalation.** - Operators can only grant permissions they hold themselves, whether through memberships or permission attributes. - Operators cannot edit themselves. @@ -652,7 +652,7 @@ This is weaker than revision 1's per-group object permissions. That is the cost - `AuthentikActionUtil.doQuery/doGet/doInsert/doUpdate/doDelete` call `AuthentikGuard.check(table, op, session)` first. The guard checks the QQQ permission, the feature flag, `readOnly` and scope. - A session with no user is refused, except `MirrorSync`'s internal read-only session. - Every qbit process's first backend step re-checks `checkProcessPermissionThrowing` and the per-record rules (§5.4). It does not trust that the middleware ran. -- Field-level protection does not use `FieldSecurityLock` (#1062): +- Field-level protection retains explicit stripping for direct backend calls; core also enforces `FieldSecurityLock` in the RC.5 baseline (#1062): - Sensitive columns are removed in `jsonObjectToRecord` without `authentik.audit.read`. These are `attributes` (except the `qqq.io/*` keys, shown under `users.read`), `last_ip`, `user_agent` and `geo_ip`. - Filters on those fields are rejected for that session. - Tables also declare `QPermissionRules`, so the UI hides what the user can't use. @@ -933,7 +933,7 @@ Authenticated API calls are not throttled by Authentik, and kof22's Authentik sh | A blueprint re-apply reverts QQQ edits | No `present` identity entries, no member lists (CI lint); bootstrap as `created` (verified, §4.2); reversion detector | | Lost update between QQQ and the Authentik UI | Stale check at execute; attribute merge-patch after a fresh read | | Account takeover through a recovery link | Links are never shown; email only | -| A non-HTTP caller bypasses permissions (#1061) | Guard in the backend util, plus ArchUnit | +| A direct backend caller bypasses core actions | Guard in the backend util, plus ArchUnit | | A field leaks because `FieldSecurityLock` isn't enforced (#1062) | Sensitive fields stripped in the util | | A forged webhook | Shared secret, constant-time compare; it only triggers re-reads | | Stale session permissions after a demotion | Re-evaluation on token refresh plus webhook-triggered marking (§5.6); live groups used for guard checks | @@ -1011,7 +1011,7 @@ The estimates assume one senior engineer with AI assistance, plus about 20% of a | The service account can lock administrators out (remove members from `authentik Admins`, deactivate break-glass, strip `qqq.io/protected`, §0.2 ii-b). Re-parenting under a superuser group **is** refused by Authentik (§0.2 ii) | Guard refusal of every write that targets a protected account or a superuser group; drift report; recovery through cluster access | | Load on the shared Postgres | Throttle, caps, mirror | | Webhook loss | Inbox, 15-minute reconcile, nightly sweep | -| QQQ core gaps (#1061, #1062) | Guard in the backend util, ArchUnit, field stripping | +| Direct backend calls outside core enforcement | Guard in the backend util, ArchUnit, field stripping | | Token leakage | Redaction tests; no body logging on sensitive paths | | An Authentik upgrade changes fields | Pinned schema, fixtures per version, version check | diff --git a/docs/POLICY-FORMAT.md b/docs/POLICY-FORMAT.md index 3cfb429..ea4dd90 100644 --- a/docs/POLICY-FORMAT.md +++ b/docs/POLICY-FORMAT.md @@ -105,7 +105,7 @@ Authentik puts the merged document into the token through a scope mapping. Steps - `AuthentikGroupPermissionMapper`, which also reads the claim and the group graph, or - your own customizer, calling `PolicyPermissions.fromClaims(claims, config.getPolicyFormat(), PolicyCatalog.of(qInstance))`. -The catalog-aware overloads of `PolicyParser`, `PolicyGenerator`, `PolicyPermissions`, `PolicyStore.fingerprint` and `GroupGraph.fromGroups` support features. The older overloads without a catalog continue to accept only standard actions. Both bundled mappers and the editor pass the catalog, as do group-policy resolution and access comparison. This qbit consumes the existing available-permission API without compiling against the unreleased `TableFeaturePermissionsMetaData` hook; features become available when the host's core supplies those entries. +The catalog-aware overloads of `PolicyParser`, `PolicyGenerator`, `PolicyPermissions`, `PolicyStore.fingerprint` and `GroupGraph.fromGroups` support features. The older overloads without a catalog continue to accept only standard actions. Both bundled mappers and the editor pass the catalog, as do group-policy resolution and access comparison. This qbit consumes the existing available-permission API without compiling against the `TableFeaturePermissionsMetaData` hook (absent from the pinned RC.5 release); features become available when the host's core supplies those entries. The mapping merges the statements of every group in `request.user.all_groups()` (direct groups and their ancestors) whose document has the right spec and version. It always emits a document, with an empty `Statement` array when the user has no policies. diff --git a/pom.xml b/pom.xml index 3702407..48fe26a 100644 --- a/pom.xml +++ b/pom.xml @@ -237,7 +237,7 @@ - + qqq-snapshot @@ -245,7 +245,7 @@ true - 4.1.0-RC.2 + 4.1.0-RC.5 diff --git a/src/main/java/com/kingsrook/qbits/authentik/metadata/AuthentikDriftReportWidgetRenderer.java b/src/main/java/com/kingsrook/qbits/authentik/metadata/AuthentikDriftReportWidgetRenderer.java index 9cd95e2..92e6aff 100644 --- a/src/main/java/com/kingsrook/qbits/authentik/metadata/AuthentikDriftReportWidgetRenderer.java +++ b/src/main/java/com/kingsrook/qbits/authentik/metadata/AuthentikDriftReportWidgetRenderer.java @@ -39,7 +39,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaData; /******************************************************************************* ** Renders the drift report as a TableData. Re-checks authentik.admin itself - ** (QQQ core does not enforce permissions for non-HTTP callers, qqq#1061). + ** for direct renderer calls as well as calls through core RenderWidgetAction. *******************************************************************************/ public class AuthentikDriftReportWidgetRenderer extends AbstractWidgetRenderer { diff --git a/src/main/java/com/kingsrook/qbits/authentik/security/AuthentikGuard.java b/src/main/java/com/kingsrook/qbits/authentik/security/AuthentikGuard.java index aa712b3..bea8334 100644 --- a/src/main/java/com/kingsrook/qbits/authentik/security/AuthentikGuard.java +++ b/src/main/java/com/kingsrook/qbits/authentik/security/AuthentikGuard.java @@ -36,9 +36,9 @@ import org.json.JSONObject; /******************************************************************************* - ** The qbit's own enforcement point (DESIGN.md section 5.3). QQQ core actions - ** do not enforce permissions for non-HTTP callers (QRun-IO/qqq#1061), so every - ** call into AuthentikActionUtil is checked here first: + ** The qbit's own enforcement point (DESIGN.md section 5.3). Core actions now + ** enforce permissions (QRun-IO/qqq#1061); direct AuthentikActionUtil calls and + ** Authentik-specific rules still need this guard: ** - reads need the table's authentik.* permission (system sessions may read, ** for the mirror sync); ** - sensitive fields need authentik.audit.read; diff --git a/src/test/java/com/kingsrook/qbits/authentik/policy/PolicyFeaturePermissionsTest.java b/src/test/java/com/kingsrook/qbits/authentik/policy/PolicyFeaturePermissionsTest.java index 89996a3..321e308 100644 --- a/src/test/java/com/kingsrook/qbits/authentik/policy/PolicyFeaturePermissionsTest.java +++ b/src/test/java/com/kingsrook/qbits/authentik/policy/PolicyFeaturePermissionsTest.java @@ -45,7 +45,9 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; /******************************************************************************* ** Table features use generic permission suffixes from core's existing catalog - ** API. No test or production code depends on the unreleased metadata hook. + ** API. The metadata hook is absent from the pinned core RC.5 release. + ** STAND-IN(core=QRun-IO/qqq#1136, removeBy=4.1.0-RC.6) + ** Replace the synthetic feature catalog with metadata registration when upgrading. *******************************************************************************/ class PolicyFeaturePermissionsTest { -- 2.54.0