Skip to content

Split space-delimited group claims into separate groups - #100

Closed
afonsojanu wants to merge 1 commit into
collective:mainfrom
afonsojanu:fix-space-delimited-group-claims
Closed

afonsojanu wants to merge 1 commit into
collective:mainfrom
afonsojanu:fix-space-delimited-group-claims

Conversation

@afonsojanu

Copy link
Copy Markdown

Closes #88.

_create_update_groups and user_can_login both handle a userinfo claim that can come back either as a list or as a single string, but the string branch in each just wraps the whole string in a one-element list rather than splitting it. Per the OAuth2/OIDC convention, a multi-value claim like a custom "affiliation" or "groups" claim that isn't covered by the default OpenIDSchema is returned by providers as a single space-delimited string, not JSON. That's exactly what #88 reports: a user with affiliations "staff" and "member" ends up in one group literally named "staff member" instead of two groups. The same pattern was independently spotted in a comment on #77, where it produced a group id like "member-reader-editor" after Plone's id normalization replaced the spaces with dashes.

The more concerning side of this is user_can_login: if allowed_groups contains a single-word group name and the provider returns the claim as "editor staff", the membership check compares "editor staff" against the allowed list and fails even though the user genuinely has the "staff" group, silently locking out users who should be allowed in.

I split on whitespace in both places instead of wrapping the string as-is. This only changes behavior for the string branch; a claim that already arrives as a list is untouched. Single-word strings keep working exactly as before, since "staff".split() is just ["staff"].

Tried reproducing this with a fresh user and plugin._create_update_groups directly rather than mocking out the OIDC flow, which surfaced an unrelated quirk worth noting: the user object passed into that method has a cached getGroups(), so even after adding group memberships, calling getGroups() on the same object still returns stale data. The tests fetch the user again through api.user.get() to see the real membership instead of trusting the method's own return value.

Ran the full test suite locally and it's green; the new tests fail on the old code and pass with this change.

OIDC providers commonly return a multi-value claim such as groups or
affiliation as a single space-delimited string, following the OAuth2
convention for scope-like claims, rather than a JSON array. See issue
#88 and the related report in #77's comments.

OIDCPlugin._create_update_groups and user_can_login both wrapped such
a string in a one-element list instead of splitting it on whitespace.
A claim of "staff member" ended up creating one bogus group literally
named "staff member" instead of two separate groups, and an
allowed_groups check against a single-word group name would silently
reject users who were actually members, because the claim was never
broken into individual group ids.

Both call sites now split the claim on whitespace before treating it
as a list of group ids, matching how a JSON array of the same values
would already have been handled. Added coverage for group creation
from a multi-value string claim and for the allowed_groups membership
check.
@afonsojanu

Copy link
Copy Markdown
Author

I'm closing out a lot of my older open PRs to bring the number down to something reasonable. This isn't about the fix being wrong, just trying to stop sitting on so many open at once.

@afonsojanu afonsojanu closed this Oct 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

claim used for group_ids won't be parsed correctly and will always be one string

1 participant