Only set organization to client session when re-authenticating if user is member of the mapped organization

Closes #37169

Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
Pedro Igor 2025-02-14 12:38:22 -03:00 committed by GitHub
parent 9bd12dcacd
commit 44f18467d5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 77 additions and 5 deletions

View File

@ -163,7 +163,7 @@ public class OrganizationAuthenticator extends IdentityProviderAuthenticator {
if (alias.isEmpty()) {
OrganizationModel organization = Organizations.resolveOrganization(session, user, domain);
if (organization != null) {
if (isSSOAuthentication(authSession) && organization != null) {
// make sure the organization selected by the user is available from the client session when running mappers and issuing tokens
authSession.setClientNote(OrganizationModel.ORGANIZATION_ATTRIBUTE, organization.getId());
}

View File

@ -36,6 +36,7 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.OrganizationModel;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.models.utils.RepresentationToModel;
@ -122,8 +123,8 @@ public class OrganizationMembershipMapper extends AbstractOIDCProtocolMapper imp
KeycloakContext context = session.getContext();
RealmModel realm = context.getRealm();
ProtocolMapperModel effectiveModel = getEffectiveModel(session, realm, model);
Object claim = resolveValue(effectiveModel, organizations.toList());
UserModel user = userSession.getUser();
Object claim = resolveValue(effectiveModel, user, organizations.toList());
if (claim == null) {
return;
@ -144,7 +145,7 @@ public class OrganizationMembershipMapper extends AbstractOIDCProtocolMapper imp
}
private Object resolveValue(ProtocolMapperModel model, List<OrganizationModel> organizations) {
private Object resolveValue(ProtocolMapperModel model, UserModel user, List<OrganizationModel> organizations) {
if (organizations.isEmpty()) {
return null;
}
@ -156,7 +157,7 @@ public class OrganizationMembershipMapper extends AbstractOIDCProtocolMapper imp
Map<String, Map<String, Object>> value = new HashMap<>();
for (OrganizationModel o : organizations) {
if (o == null || !o.isEnabled()) {
if (o == null || !o.isEnabled() || user == null || !o.isMember(user)) {
continue;
}

View File

@ -731,6 +731,10 @@ public class TokenManager {
return null;
}
if (user != null && orgScope.resolveOrganizations(user, scopeParam, session).findAny().isEmpty()) {
return null;
}
return orgScope.toClientScope(name, user, session);
}

View File

@ -861,6 +861,53 @@ public class OrganizationOIDCProtocolMapperTest extends AbstractOrganizationTest
assertScopeAndClaims(scopeName, orgA);
}
@Test
public void testClaimNotMappedIfUserNotMemberWhenDefaultClientScope() {
OrganizationRepresentation orgARep = createOrganization("orga", true);
OrganizationResource orgA = testRealm().organizations().get(orgARep.getId());
MemberRepresentation member = addMember(orgA, "member@" + orgARep.getDomains().iterator().next().getName());
orgA.members().member(member.getId()).delete().close();
ClientRepresentation clientRep = testRealm().clients().findByClientId("broker-app").get(0);
ClientResource client = testRealm().clients().get(clientRep.getId());
ClientScopeRepresentation orgScopeRep = client.getOptionalClientScopes().stream().filter(scope -> "organization".equals(scope.getName())).findAny().orElse(null);
client.removeOptionalClientScope(orgScopeRep.getId());
client.addDefaultClientScope(orgScopeRep.getId());
getCleanup().addCleanup(() -> {
client.removeDefaultClientScope(orgScopeRep.getId());
client.addOptionalClientScope(orgScopeRep.getId());
});
// resolve organization based on the organization scope value
oauth.clientId("broker-app");
oauth.scope(null);
loginPage.open(bc.consumerRealmName());
loginPage.loginUsername(member.getEmail());
loginPage.login(memberPassword);
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
AccessTokenResponse response = oauth.doAccessTokenRequest(code, KcOidcBrokerConfiguration.CONSUMER_BROKER_APP_SECRET);
assertThat(response.getScope(), containsString(orgScopeRep.getName()));
AccessToken accessToken = oauth.verifyToken(response.getAccessToken());
assertThat(accessToken.getScope(), containsString(orgScopeRep.getName()));
assertThat(accessToken.getOtherClaims().keySet(), not(hasItem(OAuth2Constants.ORGANIZATION)));
}
@Test
public void testClaimNotMappedIfUserNotMemberWhenScopeOrgAliasRequested() {
OrganizationRepresentation orgARep = createOrganization("orga", true);
assertClaimNotMapped("organization:" + orgARep.getAlias(), orgARep, false);
}
@Test
public void testClaimNotMappedIfUserNotMemberWhenScopeOrgAllRequested() {
assertClaimNotMapped("organization:*", createOrganization("orga", true), false);
}
@Test
public void testClaimNotMappedIfUserNotMemberWhenScopeOrgRequested() {
assertClaimNotMapped("organization", createOrganization("orga", true), true);
}
private AccessTokenResponse assertSuccessfulCodeGrant() {
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
AccessTokenResponse response = oauth.doAccessTokenRequest(code, KcOidcBrokerConfiguration.CONSUMER_BROKER_APP_SECRET);
@ -926,4 +973,24 @@ public class OrganizationOIDCProtocolMapperTest extends AbstractOrganizationTest
orgScopeResource.getProtocolMappers().update(orgMapper.getId(), orgMapper);
}
private void assertClaimNotMapped(String orgScope, OrganizationRepresentation orgARep, boolean grantScope) {
OrganizationResource orgA = testRealm().organizations().get(orgARep.getId());
MemberRepresentation member = addMember(orgA, "member@" + orgARep.getDomains().iterator().next().getName());
orgA.members().member(member.getId()).delete().close();
driver.manage().timeouts().pageLoadTimeout(Duration.ofDays(1));
// resolve organization based on the organization scope value
oauth.clientId("broker-app");
oauth.scope(orgScope);
loginPage.open(bc.consumerRealmName());
loginPage.loginUsername(member.getEmail());
loginPage.login(memberPassword);
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
AccessTokenResponse response = oauth.doAccessTokenRequest(code, KcOidcBrokerConfiguration.CONSUMER_BROKER_APP_SECRET);
assertThat(response.getScope(), grantScope ? containsString(orgScope) : not(containsString(orgScope)));
AccessToken accessToken = oauth.verifyToken(response.getAccessToken());
assertThat(accessToken.getScope(), grantScope ? containsString(orgScope) : not(containsString(orgScope)));
assertThat(accessToken.getOtherClaims().keySet(), not(hasItem(OAuth2Constants.ORGANIZATION)));
}
}