Skip to content

Configurable groups claim key for SSO providers#9367

Open
wvandeun wants to merge 6 commits into
developfrom
wvd-sso-configure-groups-claim
Open

Configurable groups claim key for SSO providers#9367
wvandeun wants to merge 6 commits into
developfrom
wvd-sso-configure-groups-claim

Conversation

@wvandeun
Copy link
Copy Markdown
Contributor

@wvandeun wvandeun commented May 27, 2026

Why

Infrahub maps SSO users into Infrahub groups by reading a hardcoded top-level groups key from the identity provider's claim payload. Identity providers that emit group memberships under a different claim name (e.g. roles, memberships) cannot drive group mapping, forcing operators to reshape claims or give up on SSO-driven group assignment.

Goal: Let operators configure, per provider, which claim key Infrahub reads group memberships from.

Non-goals: No path/JSONPath expressions, namespace expansion, or case-folding — the key is matched literally. No changes to how groups map to Infrahub permissions once extracted.

Closes

What changed

Behavioral changes:

  • New per-provider groups_claim setting (default "groups") on both OAuth2 and OIDC providers. Set it via TOML (groups_claim = "roles") or env var (INFRAHUB_OIDC_PROVIDER1_GROUPS_CLAIM, INFRAHUB_OAUTH2_PROVIDER1_GROUPS_CLAIM).
  • The configured key is honored uniformly at all three extraction points: OAuth2 userinfo, OIDC userinfo, and the decoded OIDC id_token.
  • Empty / whitespace-only groups_claim is rejected at startup (config validation error).
  • Group extraction now strictly requires a list of strings; any miss (claim absent, value not a list, list contains a non-string) yields no groups and emits a structured WARN log identifying the provider, source, configured claim, and miss reason.

Implementation notes:

  • Extraction is centralized in a single extract_sso_groups() helper in backend/infrahub/auth.py, replacing the three previously-divergent inline .get("groups") lookups so behavior can't drift between sites.
  • groups_claim lives on the shared base settings classes (SecurityOAuth2BaseSettings, SecurityOIDCBaseSettings) so OAuth2 and OIDC share one definition.

What stayed the same:

  • Default behavior is unchanged: with no configuration, Infrahub still reads from groups.
  • No database schema or migration changes. No GraphQL schema changes. The fallback chain (userinfo → id_token → provider lookup) is preserved.

How to review

  • backend/infrahub/auth.py — the new extract_sso_groups() helper (validation + structured logging) is the core of the change.
  • backend/infrahub/config.py — the groups_claim field and its validator on the two base settings classes.
  • backend/infrahub/api/oidc.py / backend/infrahub/api/oauth2.py — call sites wired to the helper; note _get_id_token_groups now takes claim_key/provider_name.
  • Tests and docs/archive/guides/sso.mdx are straightforward to skim.

Area wanting extra scrutiny: the strict list-of-strings enforcement is intentionally stricter than the old code (which returned whatever .get() produced). Confirm this matches expectations for IdPs that may emit groups in other shapes.

How to test

uv run invoke backend.test-unit --tests backend/tests/unit/api/test_oidc.py
uv run invoke backend.test-unit --tests backend/tests/unit/api/test_oauth2.py
uv run invoke backend.test-unit --tests backend/tests/unit/api/test_sso_groups_claim.py
uv run invoke backend.test-unit --tests backend/tests/unit/config/test_config.py

Expected: all pass. The new tests cover the default groups behavior, a custom claim key, each miss reason (absent / not-a-list / non-string member), and rejection of empty groups_claim at config load.

Impact & rollout

  • Backward compatibility: Fully backward compatible — default remains groups. The only behavioral tightening is strict list-of-strings extraction; an IdP previously returning a non-list under groups would now yield no groups (and log a WARN) instead of passing the raw value through.
  • Performance: None.
  • Config/env changes: New optional groups_claim per provider (INFRAHUB_OIDC_PROVIDER<N>_GROUPS_CLAIM, INFRAHUB_OAUTH2_PROVIDER<N>_GROUPS_CLAIM).
  • Deployment notes: Safe to deploy; no coordinated release required.

Checklist

  • Tests added/updated
  • Changelog entry added (uv run towncrier create ...)
  • External docs updated (if user-facing or ops-facing change)
  • Internal .md docs updated (internal knowledge and AI code tools knowledge)
  • I have reviewed AI generated content

🤖 Generated with Claude Code


Summary by cubic

Adds a per-provider groups_claim setting for OAuth2 and OIDC so IdPs using keys like roles or memberships can drive group mapping without rewrites. Centralizes group extraction with strict validation and structured WARN logs across userinfo and id_token.

  • New Features

    • groups_claim on each provider (default groups). Configure via TOML or env vars INFRAHUB_OIDC_PROVIDER<N)_GROUPS_CLAIM and INFRAHUB_OAUTH2_PROVIDER<N>_GROUPS_CLAIM.
    • Unified extract_sso_groups() for OAuth2 userinfo, OIDC userinfo, and OIDC id_token; enforces list-of-strings.
    • Validation: non-empty groups_claim required at startup; on miss/wrong type, returns [] and logs provider, source, claim, keys, and reason. Tests and docs updated.
  • Migration

    • No change if your IdP uses groups.
    • If it uses another key, set groups_claim (e.g., roles) and ensure the claim is a list of strings.

Written for commit 935b6ac. Summary will update on new commits.

Review in cubic

@github-actions github-actions Bot added type/documentation Improvements or additions to documentation group/backend Issue related to the backend (API Server, Git Agent) labels May 27, 2026
@wvandeun wvandeun requested a review from a team May 27, 2026 21:55
@wvandeun wvandeun self-assigned this May 27, 2026
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 11 files

Confidence score: 5/5

  • This PR looks low risk to merge: the only flagged item is low severity (3/10) and is primarily a maintainability concern rather than a functional bug.
  • In backend/infrahub/config.py, the new groups_claim logic is duplicated across two base classes, which could cause future drift if one copy is updated without the other.
  • Pay close attention to backend/infrahub/config.py - keep the duplicated groups_claim behavior aligned or extract a shared helper/mixin to prevent divergence.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="backend/infrahub/config.py">

<violation number="1" location="backend/infrahub/config.py:604">
P3: New groups_claim logic duplicated in two base classes. Easy drift later. Extract one shared mixin/helper and reuse it.</violation>
</file>

Shadow auto-approve: would not auto-approve because issues were found.

Re-trigger cubic

pkce_enabled: bool = Field(
default=True, description="Enable PKCE (RFC 7636) with S256 method for authorization code flow"
)
groups_claim: str = Field(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: New groups_claim logic duplicated in two base classes. Easy drift later. Extract one shared mixin/helper and reuse it.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/infrahub/config.py, line 604:

<comment>New groups_claim logic duplicated in two base classes. Easy drift later. Extract one shared mixin/helper and reuse it.</comment>

<file context>
@@ -601,6 +601,21 @@ class SecurityOIDCBaseSettings(BaseSettings):
     pkce_enabled: bool = Field(
         default=True, description="Enable PKCE (RFC 7636) with S256 method for authorization code flow"
     )
+    groups_claim: str = Field(
+        default="groups",
+        description=(
</file context>

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 1 file (changes from recent commits).

Shadow auto-approve: would not auto-approve. Auto-approval blocked by 1 unresolved issue from previous reviews.

Re-trigger cubic

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 27, 2026

Merging this PR will not alter performance

✅ 12 untouched benchmarks


Comparing wvd-sso-configure-groups-claim (935b6ac) with develop (630574c)

Open in CodSpeed

@wvandeun wvandeun force-pushed the wvd-sso-configure-groups-claim branch from e28bef5 to 355e88f Compare May 29, 2026 08:43
@github-actions github-actions Bot added group/frontend Issue related to the frontend (React) type/spec A specification for an upcoming change to the project labels May 29, 2026
@wvandeun wvandeun changed the base branch from stable to develop May 29, 2026 08:43
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions github-actions Bot removed group/frontend Issue related to the frontend (React) type/spec A specification for an upcoming change to the project labels May 29, 2026
@wvandeun wvandeun marked this pull request as ready for review May 29, 2026 08:49
@wvandeun wvandeun requested review from a team as code owners May 29, 2026 08:49
@wvandeun wvandeun removed request for a team May 29, 2026 08:50
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 2 files (changes from recent commits).

Shadow auto-approve: would not auto-approve. Auto-approval blocked by 1 unresolved issue from previous reviews.

Re-trigger cubic

userinfo_body = {
"sub": "u1",
"name": "Otto",
"email": "o@x.com",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we are using fake data, we should use a RFC 2606/BCP 32 reserved domain rather than domain owned by an actual company.

Comment on lines +274 to +276
*,
claim_key: str = "groups",
provider_name: str = "",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Every caller passes claim_key and provider_name explicitly. The defaults ("groups" / "") only exist for tests. Suggest dropping the defaults and making both required.

provider=provider_name,
source=source,
configured_claim=claim_key,
available_keys=sorted(payload.keys()),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The miss log dumps all top-level claim keys at WARN. Key names aren't usually PII, but URI-namespaced claims can embed tenant/customer identifiers (e.g. https://acme-corp.example/claims/...). Is WARN the right level for this, or would DEBUG be more appropriate?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

group/backend Issue related to the backend (API Server, Git Agent) type/documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants