OIDC groups claim from org teams (+ Parseable template) #5

Merged
franz merged 10 commits from feat/oidc-groups-claim into master 2026-06-29 16:22:15 +00:00
Owner

OIDC groups claim from org teams

Adds a standard, flat groups OIDC claim so relying parties that map group names to roles (Parseable, Grafana, Argo CD, Kubernetes) work against Forseti out of the box. This is phase 1 of "roles & groups": governance roles (owner/member) are untouched; teams become the group source, with clean seams left for a future role catalog.

What it does

  • New groups OAuth scope. When granted, the id_token (and userinfo) carry "groups": [...], a flat array of the user's team slugs in their active org.
  • Sourced from existing teams (org_teams); no new tables, no migration.
  • Present-but-empty ([]) when the scope is granted and the user has no teams, so apps can tell "no groups" from "claim not requested".
  • Capped at 200 slugs with a groups_truncated boolean (operator/audit signal).
  • Emitted into the id_token only, never the access token. Re-resolved on every consent path (including remembered/skip consent); not on the refresh-token grant (per OIDC Core 12.2, matching Entra/Okta/Keycloak). A group change propagates on the next authorization.
  • Team slugs are now immutable on rename (rename changes the display name only), with an application-level collision guard, so a downstream role mapping never breaks under a rename.
  • groups_count / groups_truncated recorded in the OAUTH_CONSENT_GRANTED audit event.

Also included

  • A Parseable client template in /admin/clients/new (openid profile email groups, redirect /api/v1/o/code), with setup guidance for the P_OIDC_* env vars and the team-slug-to-role mapping.
  • Corrected the misleading "populate groups provider-side" notes on the argocd/harbor/portainer/proxmox/jellyfin/open_webui/gitlab templates, which Forseti now satisfies directly.
  • Docs: scope reference, id_token groups/groups_truncated tables, a group-based-roles section (including the cross-org slug caveat), and org-internals.

Known sharp edge

A groups-only token carries no org discriminator, and slugs are unique per org, not globally. An app deriving roles from bare slugs for a multi-org user should also request org and key on (org, slug), or restrict the client to a single org. Documented in the integration guide.

Testing

Unit tests cover the slug projection, rename immutability/collision, the helper query, scope description, and claim emission (absent/empty/truncated). Integration tests for the full consent flow are added and compile; they self-skip without a live Hydra/Kratos stack, so run them against the stack before relying on the merge.

Follow-ups (out of scope)

Refresh-token hook, wiring membership removal to consent-session revocation, a custom role catalog (phase 2), and a Parseable logo asset (currently a letter tile).

## OIDC `groups` claim from org teams Adds a standard, flat `groups` OIDC claim so relying parties that map group names to roles (Parseable, Grafana, Argo CD, Kubernetes) work against Forseti out of the box. This is phase 1 of "roles & groups": governance roles (`owner`/`member`) are untouched; teams become the group source, with clean seams left for a future role catalog. ### What it does - New `groups` OAuth scope. When granted, the id_token (and userinfo) carry `"groups": [...]`, a flat array of the user's **team slugs** in their **active org**. - Sourced from existing teams (`org_teams`); no new tables, no migration. - Present-but-empty (`[]`) when the scope is granted and the user has no teams, so apps can tell "no groups" from "claim not requested". - Capped at 200 slugs with a `groups_truncated` boolean (operator/audit signal). - Emitted into the id_token only, never the access token. Re-resolved on every consent path (including remembered/skip consent); not on the refresh-token grant (per OIDC Core 12.2, matching Entra/Okta/Keycloak). A group change propagates on the next authorization. - Team **slugs are now immutable on rename** (rename changes the display name only), with an application-level collision guard, so a downstream role mapping never breaks under a rename. - `groups_count` / `groups_truncated` recorded in the `OAUTH_CONSENT_GRANTED` audit event. ### Also included - A **Parseable** client template in `/admin/clients/new` (`openid profile email groups`, redirect `/api/v1/o/code`), with setup guidance for the `P_OIDC_*` env vars and the team-slug-to-role mapping. - Corrected the misleading "populate groups provider-side" notes on the argocd/harbor/portainer/proxmox/jellyfin/open_webui/gitlab templates, which Forseti now satisfies directly. - Docs: scope reference, id_token `groups`/`groups_truncated` tables, a group-based-roles section (including the cross-org slug caveat), and org-internals. ### Known sharp edge A `groups`-only token carries no org discriminator, and slugs are unique per org, not globally. An app deriving roles from bare slugs for a multi-org user should also request `org` and key on `(org, slug)`, or restrict the client to a single org. Documented in the integration guide. ### Testing Unit tests cover the slug projection, rename immutability/collision, the helper query, scope description, and claim emission (absent/empty/truncated). Integration tests for the full consent flow are added and compile; they self-skip without a live Hydra/Kratos stack, so run them against the stack before relying on the merge. ### Follow-ups (out of scope) Refresh-token hook, wiring membership removal to consent-session revocation, a custom role catalog (phase 2), and a Parseable logo asset (currently a letter tile).
franz merged commit 3098b4cb18 into master 2026-06-29 16:22:15 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
franz/forseti!5
No description provided.