8000 Make OrganizationGroupMembershipMapper claim name configurable by sventorben · Pull Request #47852 · keycloak/keycloak · GitHub
[go: up one dir, main page]

Skip to content

Make OrganizationGroupMembershipMapper claim name configurable - #47852

Merged
stianst merged 2 commits into
keycloak:mainfrom
sventorben:gh-47851
May 4, 2026
Merged

stianst merged 2 commits into
keycloak:mainfrom
sventorben:gh-47851

Conversation

@sventorben
Copy link
Copy Markdown
Contributor

Summary

The OrganizationGroupMembershipMapper introduced in 26.6.0 hardcodes the token claim name to "organization". This PR brings it in line with OrganizationMembershipMapper, which already allows the claim name to be configured.

Changes

OrganizationGroupMembershipMapper

  • Adds TOKEN_CLAIM_NAME as a configurable property via OIDCAttributeMapperHelper.addTokenClaimNameConfig(properties) in getConfigProperties(), making the claim name visible and editable in the Admin UI.
  • Overrides getEffectiveModel() to inject "organization" as the default claim name when the config key is absent - ensuring backward compatibility for existing mapper instances that were created without this property.
  • Sets TOKEN_CLAIM_NAME to OAuth2Constants.ORGANIZATION in the static create() factory method so programmatically created mappers also have a correct default.
  • Passes the resolved ProtocolMapperModel (rather than a hardcoded string) to getOrInitializeOrganizationClaimAsMap().

OIDCAttributeMapperHelper

  • Changes getOrInitializeOrganizationClaimAsMap(IDToken, String) to getOrInitializeOrganizationClaimAsMap(IDToken, ProtocolMapperModel). This removes the raw string parameter and replaces the final setOtherClaims() call with mapClaim(), which correctly handles nested claim path notation (e.g. "custom.org.groups").

Backward Compatibility

Fully backward compatible. Existing deployments without an explicit claim name in the mapper config will continue to emit the claim under "organization" via the getEffectiveModel() fallback.

Closes #47851

@sventorben
sventorben requested a review from a team as a code owner April 8, 2026 14:19
@vramik
vramik self-requested a review April 13, 2026 16:56
@vramik vramik self-assigned this Apr 13, 2026
@vramik vramik left a comment
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.

@sventorben Thanks for the contribution! The approach looks good overall — aligning OrganizationGroupMembershipMapper with OrganizationMembershipMapper makes sense, and the getEffectiveModel() + mapClaim refactoring is a nice improvement.

Two concerns:

  1. Nested claim paths seems to be broken due to flat read in getOrInitializeOrganizationClaimAsMap

The issue description explicitly mentions support for nested claim paths (e.g., custom.org.groups). The write side now correctly uses mapClaim() which splits on dots and creates nested structure, but the read side at

https://github.com/keycloak/keycloak/blob/f61c6672d4/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAttributeMapperHelper.java#L291 still does a flat Map.get():

Object existingClaim = token.getOtherClaims().get(effectiveModel.getConfig().get(TOKEN_CLAIM_NAME));

When OrganizationMembershipMapper writes to a dotted claim name like "foo.organization", it creates otherClaims["foo"]["organization"] via mapClaim. Then OrganizationGroupMembershipMapper tries to read it back with otherClaims.get("foo.organization") — finds nothing, creates an empty map, and overwrites the nested path, losing the membership data.

Before this PR both sides were flat (get + setOtherClaims), so they were at least consistent. Now the write side uses path-aware mapClaim while the read side remains flat. The read should traverse the nested map using splitClaimPath. wdyt?

  1. Missing tests for custom claim name

It seems that there are no tests verifying that a non-default claim name actually works — neither for OrganizationGroupMembershipMapper nor for the existing OrganizationMembershipMapper (which should have been done in the past - it'd be great to add it as well).

A test should:

  • Configure both mappers with a custom claim name (e.g., "my_orgs")
  • Verify the token contains the claim at "my_orgs" and not at "organization"
  • Verify composition still works (groups are merged into the membership data)
  • Ideally also a test with a dotted claim name to cover the nested path scenario

@keycloak-github-bot keycloak-github-bot Bot left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unreported flaky test detected, please review

@keycloak-github-bot
Copy link
Copy Markdown

Unreported flaky test detected

If the flaky tests below are affected by the changes, please review and update the changes accordingly. Otherwise, a maintainer should report the flaky tests prior to merging the PR.

org.keycloak.testsuite.organization.mapper.OrganizationGroupMembershipOIDCMapperTest#testNestedGroupsWithRelativePaths

Keycloak CI - Base IT (3)

java.lang.ClassCastException: class java.lang.String cannot be cast to class java.util.Map (java.lang.String and java.util.Map are in module java.base of loader 'bootstrap')
	at org.keycloak.testsuite.organization.mapper.OrganizationGroupMembershipOIDCMapperTest.testNestedGroupsWithRelativePaths(OrganizationGroupMembershipOIDCMapperTest.java:156)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
	at java.base/java.lang.reflect.Method.invoke(Method.java:565)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
...

Report flaky test

org.keycloak.testsuite.organization.mapper.OrganizationGroupMembershipOIDCMapperTest#testCompositionWithStringTypeOrgMapper

Keycloak CI - Base IT (3)

java.lang.ClassCastException: class java.util.ArrayList cannot be cast to class java.util.Map (java.util.ArrayList and java.util.Map are in module java.base of loader 'bootstrap')
	at org.keycloak.testsuite.organization.mapper.OrganizationGroupMembershipOIDCMapperTest.testCompositionWithStringTypeOrgMapper(OrganizationGroupMembershipOIDCMapperTest.java:295)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
	at java.base/java.lang.reflect.Method.invoke(Method.java:565)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
...

Report flaky test

org.keycloak.testsuite.organization.mapper.OrganizationGroupMembershipOIDCMapperTest#testCompositionWithAttributesAndId

Keycloak CI - Base IT (3)

java.lang.AssertionError: 

Expected: iterable with items ["id", "key", "groups"] in any order
     but: no item matches: "groups" in ["key", "id"]
	at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
...

Report flaky test

org.keycloak.testsuite.organization.mapper.OrganizationOIDCProtocolMapperTest#testMapDifferentOrganizationWhenReAuthenticating

Keycloak CI - Base IT (3)

java.lang.AssertionError: 

Expected: a collection containing "organization"
     but: mismatches were: [was "my_orgs"]
	at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
...

Report flaky test

org.keycloak.testsuite.organization.mapper.OrganizationOIDCProtocolMapperTest#testOrganizationsClaimSingleValued

Keycloak CI - Base IT (3)

java.lang.AssertionError: 

Expected: a collection containing "organization"
     but: mismatches were: [was "my_orgs"]
	at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
...

Report flaky test

org.keycloak.testsuite.organization.mapper.OrganizationOIDCProtocolMapperTest#testPasswordGrantWithAllOrganizationsAndRefresh

Keycloak CI - Base IT (3)

java.lang.AssertionError: 

Expected: a collection containing "organization"
     but: mismatches were: [was "my_orgs"]
	at org.hamcrest.MatcherAssert.assertThat(MatcherAs
F438
sert.java:20)
...

Report flaky test

org.keycloak.testsuite.organization.mapper.OrganizationOIDCProtocolMapperTest#testUserInfoEndpoint

Keycloak CI - Base IT (3)

java.lang.NullPointerException: Cannot invoke "java.util.List.size()" because "organizations" is null
	at org.keycloak.testsuite.organization.mapper.OrganizationOIDCProtocolMapperTest.testUserInfoEndpoint(OrganizationOIDCProtocolMapperTest.java:518)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
	at java.base/java.lang.reflect.Method.invoke(Method.java:565)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
...

Report flaky test

org.keycloak.testsuite.organization.mapper.OrganizationOIDCProtocolMapperTest#testOrganizationScopeSpecifyDisabledOrganization

Keycloak CI - Base IT (3)

java.lang.AssertionError: 

Expected: a collection containing "organization"
     but: mismatches were: [was "my_orgs"]
	at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
...

Report flaky test

org.keycloak.testsuite.organization.mapper.OrganizationOIDCProtocolMapperTest#testRefreshTokenWithAllOrganizationsAskingForAny

Keycloak CI - Base IT (3)

java.lang.AssertionError: 

Expected: a collection containing "organization"
     but: mismatches were: [was "my_orgs"]
	at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
...

Report flaky test

org.keycloak.testsuite.organization.mapper.OrganizationOIDCProtocolMapperTest#testCustomOrganizationScopeName

Keycloak CI - Base IT (3)

java.lang.AssertionError: 

Expected: a collection containing "organization"
     but: mismatches were: [was "my_orgs"]
	at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
...

Report flaky test

org.keycloak.testsuite.organization.mapper.OrganizationOIDCProtocolMapperTest#testOrganizationScopeSelectDisabledOrganization

Keycloak CI - Base IT (3)

java.lang.AssertionError: 

Expected: a collection containing "organization"
     but: mismatches were: [was "my_orgs"]
	at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
...

Report flaky test

org.keycloak.testsuite.organization.mapper.OrganizationOIDCProtocolMapperTest#testOrganizationScopeMapsSpecificOrganization

Keycloak CI - Base IT (3)

java.lang.AssertionError: 

Expected: a collection containing "organization"
     but: mismatches were: [was "my_orgs"]
	at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
...

Report flaky test

org.keycloak.testsuite.organization.mapper.OrganizationOIDCProtocolMapperTest#testOrganizationNotAddedByGroupMapper

Keycloak CI - Base IT (3)

java.lang.AssertionError: 

Expected: a collection containing "organization"
     but: mismatches were: [was "my_orgs"]
	at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
...

Report flaky test

org.keycloak.testsuite.organization.mapper.OrganizationOIDCProtocolMapperTest#testCustomOrganizationScopeNameAllOrganizations

Keycloak CI - Base IT (3)

java.lang.AssertionError: 

Expected: a collection containing "organization"
     but: mismatches were: [was "my_orgs"]
	at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
...

Report flaky test

org.keycloak.testsuite.organization.mapper.OrganizationOIDCProtocolMapperTest#testIntrospectionEndpoint

Keycloak CI - Base IT (3)

java.lang.NullPointerException: Cannot invoke "java.util.List.size()" because "organizations" is null
	at org.keycloak.testsuite.organization.mapper.OrganizationOIDCProtocolMapperTest.testIntrospectionEndpoint(OrganizationOIDCProtocolMapperTest.java:560)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
	at java.base/java.lang.reflect.Method.invoke(Method.java:565)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
...

Report flaky test

org.keycloak.testsuite.organization.mapper.OrganizationOIDCProtocolMapperTest#testOrganizationsClaimAsList

Keycloak CI - Base IT (3)

java.lang.AssertionError: 

Expected: a collection containing "organization"
     but: mismatches were: [was "my_orgs"]
	at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
...

Report flaky test

org.keycloak.testsuite.organization.mapper.OrganizationOIDCProtocolMapperTest#testRefreshTokenWithSingleOrganizationsAskingDifferentOrganization

Keycloak CI - Base IT (3)

java.lang.AssertionError: 

Expected: a collection containing "organization"
     but: mismatches were: [was "my_orgs"]
	at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
...

Report flaky test

org.keycloak.testsuite.organization.mapper.OrganizationOIDCProtocolMapperTest#testRefreshTokenScopeWithOrganizationSelectionAskingForDifferentOrganization

Keycloak CI - Base IT (3)

java.lang.AssertionError: 

Expected: a collection containing "organization"
     but: mismatches were: [was "my_orgs"]
	at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
...

Report flaky test

org.keycloak.testsuite.organization.mapper.OrganizationOIDCProtocolMapperTest#testMultipleTabsTrackingDifferentOrganizationSelectionHoldAcrossTokenRefresh

Keycloak CI - Base IT (3)

java.lang.NullPointerException: Cannot invoke "java.util.List.size()" because "organizations" is null
	at org.keycloak.testsuite.organization.mapper.OrganizationOIDCProtocolMapperTest.testMultipleTabsTrackingDifferentOrganizationSelectionHoldAcrossTokenRefresh(OrganizationOIDCProtocolMapperTest.java:473)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
	at java.base/java.lang.reflect.Method.invoke(Method.java:565)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
...

Report flaky test

org.keycloak.testsuite.organization.mapper.OrganizationOIDCProtocolMapperTest#testOrganizationScopeAnyMapsSingleOrganization

Keycloak CI - Base IT (3)

java.lang.AssertionError: 

Expected: a collection containing "organization"
     but: mismatches were: [was "my_orgs"]
	at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
...

Report flaky test

org.keycloak.testsuite.organization.mapper.OrganizationOIDCProtocolMapperTest#testRefreshTokenScopeWithSingleOrganizationAskingForAll

Keycloak CI - Base IT (3)

java.lang.AssertionError: 

Expected: a collection containing "organization"
     but: mismatches were: [was "my_orgs"]
	at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
...

Report flaky test

org.keycloak.testsuite.organization.mapper.OrganizationOIDCProtocolMapperTest#testIncludeOrganizationAttributes

Keycloak CI - Base IT (3)

java.lang.AssertionError: 

Expected: a collection containing "organization"
     but: mismatches were: [was "my_orgs"]
	at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
...

Report flaky test

org.keycloak.testsuite.organization.mapper.OrganizationOIDCProtocolMapperTest#testIncludeOrganizationId

Keycloak CI - Base IT (3)

java.lang.AssertionError: 

Expected: a collection containing "organization"
     but: mismatches were: [was "my_orgs"]
	at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
...

Report flaky test

org.keycloak.testsuite.organization.mapper.OrganizationOIDCProtocolMapperTest#testPasswordGrantType

Keycloak CI - Base IT (3)

java.lang.AssertionError: 

Expected: a collection containing "organization"
     but: mismatches were: [was "my_orgs"]
	at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
...

Report flaky test

org.keycloak.testsuite.organization.mapper.OrganizationOIDCProtocolMapperTest#testOrganizationsClaimMappedIfScopeInTokenDisabled

Keycloak CI - Base IT (3)

java.lang.AssertionError: 

Expected: a collection containing "organization"
     but: mismatches were: [was "my_orgs"]
	at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
...

Report flaky test

org.keycloak.testsuite.organization.mapper.OrganizationOIDCProtocolMapperTest#testRefreshTokenScopeWithOrganizationSelectionAskingForAll

Keycloak CI - Base IT (3)

java.lang.AssertionError: 

Expected: a collection containing "organization"
     but: mismatches were: [was "my_orgs"]
	at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
...

Report flaky test

org.keycloak.testsuite.organization.mapper.OrganizationOIDCProtocolMapperTest#testRefreshTokenWithAllOrganizationsAskingForSpecificOrganization

Keycloak CI - Base IT (3)

java.lang.AssertionError: 

Expected: a collection containing "organization"
     but: mismatches were: [was "my_orgs"]
	at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
...

Report flaky test

org.keycloak.testsuite.organization.mapper.OrganizationOIDCProtocolMapperTest#testSelectDifferentOrganizationWhenReAuthenticating

Keycloak CI - Base IT (3)

java.lang.AssertionError: 

Expected: a collection containing "organization"
     but: mismatches were: [was "my_orgs"]
	at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
...

Report flaky test

org.keycloak.testsuite.organization.mapper.OrganizationOIDCProtocolMapperTest#testOrganizationAttributeNamedIdIsOverriddenByOrganizationId

Keycloak CI - Base IT (3)

java.lang.AssertionError: 

Expected: a collection containing "organization"
     but: mismatches were: [was "my_orgs"]
	at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
...

Report flaky test

org.keycloak.testsuite.organization.mapper.OrganizationOIDCProtocolMapperTest#testSelectOrganizationMapDifferentOrganizationWhenReAuthenticating

Keycloak CI - Base IT (3)

java.lang.AssertionError: 

Expected: a collection containing "organization"
     but: mismatches were: [was "my_orgs"]
	at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
...

Report flaky test

org.keycloak.testsuite.organization.mapper.OrganizationOIDCProtocolMapperTest#testRefreshTokenScopeWithOrganizationSelectionAskingForSameOrganization

Keycloak CI - Base IT (3)

java.lang.AssertionError: 

Expected: a collection containing "organization"
     but: mismatches were: [was "my_orgs"]
	at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
...

Report flaky test

org.keycloak.testsuite.organization.mapper.OrganizationOIDCProtocolMapperTest#testRefreshTokenWithSingleOrganizationsAskingAllOrganizations

Keycloak CI - Base IT (3)

java.lang.AssertionError: 

Expected: a collection containing "organization"
     but: mismatches were: [was "my_orgs"]
	at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
...

Report flaky test

org.keycloak.testsuite.organization.mapper.OrganizationOIDCProtocolMapperTest#testMultipleTabsWithMixedScopeFormats

Keycloak CI - Base IT (3)

java.lang.NullPointerException: Cannot invoke "java.util.List.size()" because "organizations" is null
	at org.keycloak.testsuite.organization.mapper.OrganizationOIDCProtocolMapperTest.testMultipleTabsWithMixedScopeFormats(OrganizationOIDCProtocolMapperTest.java:910)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
	at java.base/java.lang.reflect.Method.invoke(Method.java:565)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
...

Report flaky test

org.keycloak.testsuite.organization.mapper.OrganizationOIDCProtocolMapperTest#testMultipleOrganizationScopes

Keycloak CI - Base IT (3)

java.lang.AssertionError
	at org.junit.Assert.fail(Assert.java:87)
	at org.junit.Assert.assertTrue(Assert.java:42)
	at org.junit.Assert.assertNotNull(Assert.java:713)
	at org.junit.Assert.assertNotNull(Assert.java:723)
...

Report flaky test

org.keycloak.testsuite.organization.mapper.OrganizationOIDCProtocolMapperTest#testOrganizationScopeAnyAskUserToSelectOrganization

Keycloak CI - Base IT (3)

java.lang.AssertionError: 

Expected: a collection containing "organization"
     but: mismatches were: [was "my_orgs"]
	at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
...

Report flaky test

org.keycloak.testsuite.organization.mapper.OrganizationOIDCProtocolMapperTest#testCustomOrganizationScopeNameMultipleSpecific

Keycloak CI - Base IT (3)

java.lang.AssertionError: 

Expected: a collection containing "organization"
     but: mismatches were: [was "my_orgs"]
	at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
...

Report flaky test

org.keycloak.testsuite.organization.mapper.OrganizationOIDCProtocolMapperTest#testOrganizationScopeMapsAllOrganizations

Keycloak CI - Base IT (3)

java.lang.AssertionError: 

Expected: a collection containing "organization"
     but: mismatches were: [was "my_orgs"]
	at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
...

Report flaky test

org.keycloak.testsuite.organization.mapper.OrganizationOIDCProtocolMapperTest#testForceSelectingOrganizationWhenReAuthenticatingUsingDifferentClient

Keycloak CI - Base IT (3)

java.lang.AssertionError: 

Expected: a collection containing "organization"
     but: mismatches were: [was "my_orgs"]
	at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
...

Report flaky test

@vramik
vramik commented Apr 21, 2026 •
Copy link
Copy Markdown
Contributor

@sventorben perfect, thank you for the update.

From the bot report it seems it needs one last thing to make tests independent of each other. In OrganizationOIDCProtocolMapperTest.onBefore() there are reset MULTIVALUED and JSON_TYPE, but not TOKEN_CLAIM_NAME.

Could you add

setMapperConfig(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, null);

there?

@pedroigor
pedroigor force-pushed the gh-47851 branch 2 times, most recently from c47393a to 822d453 Compare April 28, 2026 16:11
@pedroigor
Copy link
Copy Markdown
Contributor

@vramik @sventorben Updated the PR to avoid the claim name config in the group mapper so that we just resolve the org claim name from the organization mapper mapping orgs to tokens.

@pedroigor
Copy link
Copy Markdown
Contributor

One thing perhaps worth documenting is that there should not be more than one organization-membership mapper configured to a client. The last change I did assumes there is only one but we do not impose any validation on this constraint.

@pedroigor
pedroigor force-pushed the gh-47851 branch 2 times, most recently from 3341672 to e97fd07 Compare April 28, 2026 18:09
@vramik
vramik commented Apr 29, 2026
Copy link
Copy Markdown
Contributor

One thing perhaps worth documenting is that there should not be more than one organization-membership mapper configured to a client. The last change I did assumes there is only one but we do not impose any validation on this constraint.

+1 do you prefer to do it within this one or rather follow-up?

The OrganizationGroupMembershipMapper introduced in 26.6.0 hardcoded
the token claim name to "organization", unlike OrganizationMembershipMapper
which already exposes the claim name as a configurable property.

- Add TOKEN_CLAIM_NAME config property to OrganizationGroupMembershipMapper
  via OIDCAttributeMapperHelper.addTokenClaimNameConfig()
- Override getEffectiveModel() to default the claim name to
  OAuth2Constants.ORGANIZATION when not set, preserving backward
  compatibility for existing mapper configurations
- Set TOKEN_CLAIM_NAME default in the static create() factory method
- Refactor OIDCAttributeMapperHelper.getOrInitializeOrganizationClaimAsMap()
  to accept a ProtocolMapperModel instead of a raw String, delegating
  to mapClaim() for correct claim placement (including nested path support)

Closes keycloak#47851

Signed-off-by: Sven-Torben Janus <sven-torben.janus@conciso.de>
The read side of getOrInitializeOrganizationClaimAsMap was doing a flat
Map.get() on the dotted claim name, while the write side (mapClaim) already
creates a nested structure by splitting on dots. This caused the group mapper
to find nothing when the claim name contained a dot, overwriting the
membership data written by OrganizationMembershipMapper.

Fix by splitting the claim path via splitClaimPath() and traversing the
nested map with a new private getNestedClaimValue() helper in
OIDCAttributeMapperHelper. The helper belongs there rather than in JsonUtils
because it operates on Map<String,Object>, not JsonNode.

Also add integration tests covering:
- Custom flat claim name ("my_orgs") for both OrganizationMembershipMapper
  and OrganizationGroupMembershipMapper, verifying the claim appears at the
  configured name and not at "organization"
- Dotted claim name ("custom.org") for OrganizationGroupMembershipMapper,
  verifying the token contains nested otherClaims["custom"]["org"] and that
  group composition is preserved

Signed-off-by: Sven-Torben Janus <sven-torben.janus@conciso.de>
@pedroigor
Copy link
Copy Markdown
Contributor

@vramik Updated the docs with a "notice".

@stianst
stianst merged commit 67ef87b into keycloak:main May 4, 2026
83 of 85 checks passed
@stianst stianst mentioned this pull request Jun 1, 2026
sashyo pushed a commit to tide-foundation/keycloak-IGA that referenced this pull request Jul 20, 2026
…oak#47852)

* Make OrganizationGroupMembershipMapper claim name configurable

The OrganizationGroupMembershipMapper introduced in 26.6.0 hardcoded
the token claim name to "organization", unlike OrganizationMembershipMapper
which already exposes the claim name as a configurable property.

- Add TOKEN_CLAIM_NAME config property to OrganizationGroupMembershipMapper
  via OIDCAttributeMapperHelper.addTokenClaimNameConfig()
- Override getEffectiveModel() to default the claim name to
  OAuth2Constants.ORGANIZATION when not set, preserving backward
  compatibility for existing mapper configurations
- Set TOKEN_CLAIM_NAME default in the static create() factory method
- Refactor OIDCAttributeMapperHelper.getOrInitializeOrganizationClaimAsMap()
  to accept a ProtocolMapperModel instead of a raw String, delegating
  to mapClaim() for correct claim placement (including nested path support)

Closes keycloak#47851

Signed-off-by: Sven-Torben Janus <sven-torben.janus@conciso.de>

* Fix nested claim path read and add custom claim name tests

The read side of getOrInitializeOrganizationClaimAsMap was doing a flat
Map.get() on the dotted claim name, while the write side (mapClaim) already
creates a nested structure by splitting on dots. This caused the group mapper
to find nothing when the claim name contained a dot, overwriting the
membership data written by OrganizationMembershipMapper.

Fix by splitting the claim path via splitClaimPath() and traversing the
nested map with a new private getNestedClaimValue() helper in
OIDCAttributeMapperHelper. The helper belongs there rather than in JsonUtils
because it operates on Map<String,Object>, not JsonNode.

Also add integration tests covering:
- Custom flat claim name ("my_orgs") for both OrganizationMembershipMapper
  and OrganizationGroupMembershipMapper, verifying the claim appears at the
  configured name and not at "organization"
- Dotted claim name ("custom.org") for OrganizationGroupMembershipMapper,
  verifying the token contains nested otherClaims["custom"]["org"] and that
  group composition is preserved

Signed-off-by: Sven-Torben Janus <sven-torben.janus@conciso.de>

---------

Signed-off-by: Sven-Torben Janus <sven-torben.janus@conciso.de>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OrganizationGroupMembershipMapper should support configurable token claim name

4 participants

0