mirror of
https://github.com/keycloak/keycloak.git
synced 2026-05-28 04:13:22 -04:00
The existence of an organization attribute called id is not validated
Closes #44522 Signed-off-by: Martin Kanis <mkanis@redhat.com>
This commit is contained in:
parent
bf18942c34
commit
012cefb654
3 changed files with 49 additions and 4 deletions
|
|
@ -171,6 +171,14 @@ See the link:{upgradingguide_link}[{upgradingguide_name}] for details on how to
|
|||
For each expired user session there is a new user event `USER_SESSION_DELETED` fired.
|
||||
As part of this change, the process now deletes rows from the table in small batches, instead of issuing a delete statements that affects the whole table. This should allow for better response times when there are a lot of sessions in the table.
|
||||
|
||||
=== Organization custom attribute named 'id' behavior change
|
||||
|
||||
Organizations can have custom attributes named `id`. When both organization attributes and organization ID are included in tokens via the organization membership mapper configuration,
|
||||
the organization ID will override any custom `id` attribute value. Previously, the organization ID was added first and could be overridden by custom attributes.
|
||||
This ensures that the `id` field in the organization claims always contains the actual organization ID.
|
||||
|
||||
If you have a custom organization attribute named `id` and rely on its value in tokens, you should rename the attribute to avoid it being overridden by the organization ID.
|
||||
|
||||
// ------------------------ Deprecated features ------------------------ //
|
||||
== Deprecated features
|
||||
|
||||
|
|
|
|||
|
|
@ -162,12 +162,14 @@ public class OrganizationMembershipMapper extends AbstractOIDCProtocolMapper imp
|
|||
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
|
||||
if (isAddOrganizationId(model)) {
|
||||
claims.put(OAuth2Constants.ORGANIZATION_ID, o.getId());
|
||||
}
|
||||
// Add organization attributes first
|
||||
if (isAddOrganizationAttributes(model)) {
|
||||
claims.putAll(o.getAttributes());
|
||||
}
|
||||
// Add organization ID last so it overrides any custom "id" attribute
|
||||
if (isAddOrganizationId(model)) {
|
||||
claims.put(OAuth2Constants.ORGANIZATION_ID, o.getId());
|
||||
}
|
||||
|
||||
value.put(o.getAlias(), claims);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,8 +79,8 @@ public class OrganizationOIDCProtocolMapperTest extends AbstractOrganizationTest
|
|||
|
||||
@Before
|
||||
public void onBefore() {
|
||||
setMapperConfig(OIDCAttributeMapperHelper.JSON_TYPE, null);
|
||||
setMapperConfig(ProtocolMapperUtils.MULTIVALUED, null);
|
||||
setMapperConfig(OIDCAttributeMapperHelper.JSON_TYPE, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -984,6 +984,41 @@ public class OrganizationOIDCProtocolMapperTest extends AbstractOrganizationTest
|
|||
assertThat(organization, containsInAnyOrder("orga", "orgb"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testOrganizationAttributeNamedIdIsOverriddenByOrganizationId() throws Exception {
|
||||
// When an organization has a custom attribute called "id", the organization ID should override it in tokens
|
||||
OrganizationRepresentation orgRep = createOrganization();
|
||||
OrganizationResource organization = testRealm().organizations().get(orgRep.getId());
|
||||
addMember(organization);
|
||||
|
||||
// Add a custom attribute named "id" to the organization
|
||||
orgRep.singleAttribute("id", "custom-id-value");
|
||||
|
||||
try (Response response = organization.update(orgRep)) {
|
||||
assertEquals(Status.NO_CONTENT.getStatusCode(), response.getStatus());
|
||||
}
|
||||
|
||||
// Verify that organization ID overrides custom "id" attribute in tokens
|
||||
setMapperConfig(OrganizationMembershipMapper.ADD_ORGANIZATION_ID, Boolean.TRUE.toString());
|
||||
setMapperConfig(OrganizationMembershipMapper.ADD_ORGANIZATION_ATTRIBUTES, Boolean.TRUE.toString());
|
||||
|
||||
oauth.client("direct-grant", "password");
|
||||
oauth.scope("openid organization");
|
||||
AccessTokenResponse response = oauth.doPasswordGrantRequest(memberEmail, memberPassword);
|
||||
assertThat(response.getScope(), containsString("organization"));
|
||||
AccessToken accessToken = TokenVerifier.create(response.getAccessToken(), AccessToken.class).getToken();
|
||||
assertThat(accessToken.getOtherClaims().keySet(), hasItem(OAuth2Constants.ORGANIZATION));
|
||||
|
||||
Map<String, Map<String, String>> organizations = (Map<String, Map<String, String>>) accessToken.getOtherClaims().get(OAuth2Constants.ORGANIZATION);
|
||||
assertThat(organizations.keySet(), hasItem(organizationName));
|
||||
Map<String, String> orgClaims = organizations.get(organizationName);
|
||||
|
||||
// The "id" attribute should contain the organization ID, not the custom value
|
||||
assertThat(orgClaims.get("id"), equalTo(orgRep.getId()));
|
||||
assertThat(orgClaims.get("id"), not(equalTo("custom-id-value")));
|
||||
}
|
||||
|
||||
private AccessTokenResponse assertSuccessfulCodeGrant() {
|
||||
String code = oauth.parseLoginResponse().getCode();
|
||||
AccessTokenResponse response = oauth.doAccessTokenRequest(code);
|
||||
|
|
|
|||
Loading…
Reference in a new issue