mirror of
https://github.com/keycloak/keycloak.git
synced 2026-05-26 13:50:48 +00:00
Organization Groups - Identity Provider Mappers (#46592)
Closes #45512 Signed-off-by: Martin Kanis <mkanis@redhat.com>
This commit is contained in:
+17
@@ -17,12 +17,18 @@
|
||||
|
||||
package org.keycloak.admin.client.resource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.ws.rs.DELETE;
|
||||
import jakarta.ws.rs.DefaultValue;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
|
||||
import org.keycloak.representations.idm.GroupRepresentation;
|
||||
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
||||
|
||||
public interface OrganizationIdentityProviderResource {
|
||||
@@ -33,4 +39,15 @@ public interface OrganizationIdentityProviderResource {
|
||||
|
||||
@DELETE
|
||||
Response delete();
|
||||
|
||||
@Path("groups")
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
List<GroupRepresentation> getGroups(@QueryParam("search") String search,
|
||||
@QueryParam("q") String searchQuery,
|
||||
@QueryParam("exact") @DefaultValue("false") Boolean exact,
|
||||
@QueryParam("first") Integer first,
|
||||
@QueryParam("max") Integer max,
|
||||
@QueryParam("briefRepresentation") @DefaultValue("true") boolean briefRepresentation,
|
||||
@QueryParam("subGroupsCount") @DefaultValue("false") boolean subGroupsCount);
|
||||
}
|
||||
|
||||
@@ -47,6 +47,8 @@ import jakarta.transaction.Transaction;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.Config.Scope;
|
||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||
import org.keycloak.broker.provider.ConfigConstants;
|
||||
import org.keycloak.broker.social.SocialIdentityProvider;
|
||||
import org.keycloak.broker.social.SocialIdentityProviderFactory;
|
||||
import org.keycloak.cache.AlternativeLookupProvider;
|
||||
@@ -70,6 +72,7 @@ import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.GroupProvider;
|
||||
import org.keycloak.models.GroupProviderFactory;
|
||||
import org.keycloak.models.IdentityProviderMapperModel;
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.models.KeycloakContext;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
@@ -950,6 +953,78 @@ public final class KeycloakModelUtils {
|
||||
return getGroupModel(session, realm, orgInternalGroup, split, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and retrieves the organization for an Identity Provider mapper.
|
||||
* This performs all necessary checks to ensure the IdP-organization relationship is valid:
|
||||
* - Organizations feature is enabled
|
||||
* - Organization exists and is enabled
|
||||
* - Bidirectional link exists (organization still has this IdP)
|
||||
*
|
||||
* @param session the Keycloak session
|
||||
* @param idpModel the identity provider model
|
||||
* @return the validated organization if all checks pass, null otherwise
|
||||
*/
|
||||
public static OrganizationModel getOrganizationForIdpMapper(KeycloakSession session, IdentityProviderModel idpModel) {
|
||||
String idpOrgId = idpModel.getOrganizationId();
|
||||
if (idpOrgId == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
OrganizationProvider orgProvider = session.getProvider(OrganizationProvider.class);
|
||||
if (orgProvider != null && orgProvider.isEnabled()) {
|
||||
OrganizationModel organization = orgProvider.getById(idpOrgId);
|
||||
|
||||
if (organization != null && organization.isEnabled() && organization.getIdentityProviders().anyMatch(idp -> idp.getAlias().equals(idpModel.getAlias()))) {
|
||||
return organization;
|
||||
}
|
||||
}
|
||||
|
||||
logger.warnf("Cannot obtain organization '%s' linked to IdP '%s'", idpModel.getAlias(), idpOrgId);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves and validates a group for use in an Identity Provider mapper.
|
||||
* This method handles organization-aware group lookup.
|
||||
*
|
||||
* When the IdP is linked to an organization, this method first attempts to find the group
|
||||
* within that organization's groups. If not found (or IdP not linked to org), it falls back
|
||||
* to searching realm groups.
|
||||
*
|
||||
* @param session the Keycloak session
|
||||
* @param realm the realm
|
||||
* @param mapperModel the mapper model configuration containing the group path
|
||||
* @param context the brokered identity context containing the IdP configuration
|
||||
* @return the group if found and valid, null otherwise (mapper should be skipped)
|
||||
*/
|
||||
public static GroupModel getGroupForIdpMapper(KeycloakSession session,
|
||||
RealmModel realm,
|
||||
IdentityProviderMapperModel mapperModel,
|
||||
BrokeredIdentityContext context) {
|
||||
String groupPath = mapperModel.getConfig().get(ConfigConstants.GROUP);
|
||||
GroupModel group = null;
|
||||
|
||||
// Check if IdP is linked to organization and validate the relationship
|
||||
OrganizationModel organization = getOrganizationForIdpMapper(session, context.getIdpConfig());
|
||||
|
||||
if (organization != null) {
|
||||
group = findGroupByPath(session, realm, organization, groupPath);
|
||||
}
|
||||
|
||||
// If not found in organization (or IdP not in org context), try as realm group
|
||||
if (group == null) {
|
||||
group = findGroupByPath(session, realm, groupPath);
|
||||
}
|
||||
|
||||
if (group == null) {
|
||||
logger.warnf("Unable to find group by path '%s' referenced by mapper '%s' on realm '%s'.", groupPath, mapperModel.getName(), realm.getName());
|
||||
return null;
|
||||
}
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
public static Stream<RoleModel> getClientScopeMappingsStream(ClientModel client, ScopeContainerModel container) {
|
||||
return container.getScopeMappingsStream()
|
||||
.filter(role -> role.getContainer() instanceof ClientModel &&
|
||||
|
||||
+2
-20
@@ -18,7 +18,6 @@
|
||||
package org.keycloak.broker.oidc.mappers;
|
||||
|
||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||
import org.keycloak.broker.provider.ConfigConstants;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.IdentityProviderMapperModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
@@ -26,22 +25,17 @@ import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:artur.baltabayev@bosch.io">Artur Baltabayev</a>,
|
||||
* <a href="mailto:daniel.fesenmeyer@bosch.io">Daniel Fesenmeyer</a>
|
||||
*/
|
||||
public abstract class AbstractClaimToGroupMapper extends AbstractClaimMapper {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(AbstractClaimToGroupMapper.class);
|
||||
|
||||
|
||||
@Override
|
||||
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user,
|
||||
IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
|
||||
GroupModel group = this.getGroup(session, realm, mapperModel);
|
||||
GroupModel group = KeycloakModelUtils.getGroupForIdpMapper(session, realm, mapperModel, context);
|
||||
if (group == null) {
|
||||
return;
|
||||
}
|
||||
@@ -55,7 +49,7 @@ public abstract class AbstractClaimToGroupMapper extends AbstractClaimMapper {
|
||||
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user,
|
||||
IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
|
||||
GroupModel group = this.getGroup(session, realm, mapperModel);
|
||||
GroupModel group = KeycloakModelUtils.getGroupForIdpMapper(session, realm, mapperModel, context);
|
||||
if (group == null) {
|
||||
return;
|
||||
}
|
||||
@@ -82,16 +76,4 @@ public abstract class AbstractClaimToGroupMapper extends AbstractClaimMapper {
|
||||
protected abstract boolean applies(final IdentityProviderMapperModel mapperModel,
|
||||
final BrokeredIdentityContext context);
|
||||
|
||||
private GroupModel getGroup(KeycloakSession session, final RealmModel realm, final IdentityProviderMapperModel mapperModel) {
|
||||
String groupPath = mapperModel.getConfig().get(ConfigConstants.GROUP);
|
||||
GroupModel group = KeycloakModelUtils.findGroupByPath(session, realm, groupPath);
|
||||
|
||||
if (group == null) {
|
||||
LOG.warnf("Unable to find group by path '%s' referenced by mapper '%s' on realm '%s'.", groupPath,
|
||||
mapperModel.getName(), realm.getName());
|
||||
}
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-29
@@ -18,7 +18,6 @@ package org.keycloak.broker.saml.mappers;
|
||||
|
||||
import org.keycloak.broker.provider.AbstractIdentityProviderMapper;
|
||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||
import org.keycloak.broker.provider.ConfigConstants;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.IdentityProviderMapperModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
@@ -26,8 +25,6 @@ import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Abstract class that handles the logic for importing and updating brokered users for all mappers that map a SAML
|
||||
* attribute into a {@code Keycloak} group.
|
||||
@@ -36,12 +33,9 @@ import org.jboss.logging.Logger;
|
||||
*/
|
||||
public abstract class AbstractAttributeToGroupMapper extends AbstractIdentityProviderMapper {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(AbstractAttributeToGroupMapper.class);
|
||||
|
||||
|
||||
@Override
|
||||
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
GroupModel group = this.getGroup(session, realm, mapperModel);
|
||||
GroupModel group = KeycloakModelUtils.getGroupForIdpMapper(session, realm, mapperModel, context);
|
||||
if (group == null) {
|
||||
return;
|
||||
}
|
||||
@@ -53,7 +47,7 @@ public abstract class AbstractAttributeToGroupMapper extends AbstractIdentityPro
|
||||
|
||||
@Override
|
||||
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
GroupModel group = this.getGroup(session, realm, mapperModel);
|
||||
GroupModel group = KeycloakModelUtils.getGroupForIdpMapper(session, realm, mapperModel, context);
|
||||
if (group == null) {
|
||||
return;
|
||||
}
|
||||
@@ -79,25 +73,4 @@ public abstract class AbstractAttributeToGroupMapper extends AbstractIdentityPro
|
||||
*/
|
||||
protected abstract boolean applies(final IdentityProviderMapperModel mapperModel, final BrokeredIdentityContext context);
|
||||
|
||||
/**
|
||||
* Obtains the {@link GroupModel} corresponding the group configured in the specified
|
||||
* {@link IdentityProviderMapperModel}.
|
||||
* If the group doesn't correspond to one of the realm's client group or to one of the realm's group, this method
|
||||
* returns {@code null}.
|
||||
*
|
||||
* @param session
|
||||
* @param realm a reference to the realm.
|
||||
* @param mapperModel a reference to the {@link IdentityProviderMapperModel} containing the configured group.
|
||||
* @return the {@link GroupModel} that corresponds to the mapper model group or {@code null}, if the group could not be found
|
||||
*/
|
||||
|
||||
private GroupModel getGroup(KeycloakSession session, final RealmModel realm, final IdentityProviderMapperModel mapperModel) {
|
||||
String groupPath = mapperModel.getConfig().get(ConfigConstants.GROUP);
|
||||
GroupModel group = KeycloakModelUtils.findGroupByPath(session, realm, groupPath);
|
||||
|
||||
if (group == null) {
|
||||
LOG.warnf("Unable to find group by path '%s' referenced by mapper '%s' on realm '%s'.", groupPath, mapperModel.getName(), realm.getName());
|
||||
}
|
||||
return group;
|
||||
}
|
||||
}
|
||||
|
||||
+51
@@ -21,11 +21,13 @@ import java.util.stream.Stream;
|
||||
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.DELETE;
|
||||
import jakarta.ws.rs.DefaultValue;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.core.Response.Status;
|
||||
@@ -38,6 +40,7 @@ import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.utils.ModelToRepresentation;
|
||||
import org.keycloak.models.utils.StripSecretsUtils;
|
||||
import org.keycloak.organization.OrganizationProvider;
|
||||
import org.keycloak.representations.idm.GroupRepresentation;
|
||||
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
||||
import org.keycloak.services.ErrorResponse;
|
||||
import org.keycloak.services.resources.KeycloakOpenAPI;
|
||||
@@ -48,6 +51,7 @@ import org.eclipse.microprofile.openapi.annotations.enums.SchemaType;
|
||||
import org.eclipse.microprofile.openapi.annotations.extensions.Extension;
|
||||
import org.eclipse.microprofile.openapi.annotations.media.Content;
|
||||
import org.eclipse.microprofile.openapi.annotations.media.Schema;
|
||||
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
|
||||
import org.eclipse.microprofile.openapi.annotations.parameters.RequestBody;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
|
||||
@@ -61,12 +65,14 @@ public class OrganizationIdentityProvidersResource {
|
||||
private final KeycloakSession session;
|
||||
private final OrganizationProvider organizationProvider;
|
||||
private final OrganizationModel organization;
|
||||
private final AdminEventBuilder adminEvent;
|
||||
|
||||
public OrganizationIdentityProvidersResource(KeycloakSession session, OrganizationModel organization, AdminEventBuilder adminEvent) {
|
||||
this.realm = session == null ? null : session.getContext().getRealm();
|
||||
this.session = session;
|
||||
this.organizationProvider = session == null ? null : session.getProvider(OrganizationProvider.class);
|
||||
this.organization = organization;
|
||||
this.adminEvent = adminEvent;
|
||||
}
|
||||
|
||||
@POST
|
||||
@@ -137,6 +143,51 @@ public class OrganizationIdentityProvidersResource {
|
||||
return toRepresentation(broker);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns organization groups for the identity provider with the specified alias.
|
||||
* It allows filtering and displaying only the organization groups that are valid for the given identity provider.
|
||||
*
|
||||
* Only returns groups if the identity provider is associated with the organization and the organization
|
||||
* is enabled. Otherwise, returns an error or empty stream.
|
||||
*
|
||||
* @param alias the identity provider alias
|
||||
* @param search a string to search for in group names
|
||||
* @param searchQuery a query to search for group attributes, in the format 'key1:value1 key2:value2'
|
||||
* @param exact if true, perform exact match on the search parameter
|
||||
* @param first the position of the first result (pagination offset)
|
||||
* @param max the maximum number of results to return
|
||||
* @param briefRepresentation if true, return brief group representation; otherwise return full representation
|
||||
* @return a stream of organization groups associated with the organization
|
||||
*/
|
||||
@Path("{alias}/groups")
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@NoCache
|
||||
@Tag(name = KeycloakOpenAPI.Admin.Tags.ORGANIZATIONS)
|
||||
@Operation(summary = "Returns organization groups for the identity provider",
|
||||
description = "Returns organization groups that can be used in identity provider mappers. " +
|
||||
"Only returns groups if the identity provider is associated with the organization.")
|
||||
@APIResponses(value = {
|
||||
@APIResponse(responseCode = "200", description = "", content = @Content(schema = @Schema(implementation = GroupRepresentation.class, type = SchemaType.ARRAY))),
|
||||
@APIResponse(responseCode = "404", description = "Not Found")
|
||||
})
|
||||
public Stream<GroupRepresentation> getGroups(
|
||||
@Parameter(description = "The alias of the identity provider") @PathParam("alias") String alias,
|
||||
@Parameter(description = "A string to search for in group names") @QueryParam("search") String search,
|
||||
@Parameter(description = "A query to search for group attributes, in the format 'key1:value1 key2:value2'") @QueryParam("q") String searchQuery,
|
||||
@Parameter(description = "If true, perform exact match on the search parameter") @QueryParam("exact") @DefaultValue("false") Boolean exact,
|
||||
@Parameter(description = "The position of the first result (pagination offset)") @QueryParam("first") Integer first,
|
||||
@Parameter(description = "The maximum number of results to return") @QueryParam("max") Integer max,
|
||||
@Parameter(description = "If true, return brief representation; otherwise return full representation") @QueryParam("briefRepresentation") @DefaultValue("true") boolean briefRepresentation,
|
||||
@Parameter(description = "If true, include subgroups count in the response") @QueryParam("subGroupsCount") @DefaultValue("false") boolean subGroupsCount) {
|
||||
|
||||
// Validate that the identity provider is associated with the organization
|
||||
getIdentityProvider(alias);
|
||||
|
||||
OrganizationGroupsResource groupsResource = new OrganizationGroupsResource(session, organization, adminEvent);
|
||||
return groupsResource.getGroups(search, searchQuery, exact, first, max, briefRepresentation, subGroupsCount);
|
||||
}
|
||||
|
||||
@Path("{alias}")
|
||||
@DELETE
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
|
||||
+3
@@ -348,6 +348,7 @@ public class IdentityProviderResource {
|
||||
}
|
||||
|
||||
IdentityProviderMapperModel model = RepresentationToModel.toModel(mapper);
|
||||
|
||||
try {
|
||||
// model = realm.addIdentityProviderMapper(model);
|
||||
model = session.identityProviders().createMapper(model);
|
||||
@@ -408,6 +409,7 @@ public class IdentityProviderResource {
|
||||
IdentityProviderMapperModel model = session.identityProviders().getMapperById(id);
|
||||
if (model == null) throw new NotFoundException("Model not found");
|
||||
model = RepresentationToModel.toModel(rep);
|
||||
|
||||
session.identityProviders().updateMapper(model);
|
||||
adminEvent.operation(OperationType.UPDATE).resource(ResourceType.IDENTITY_PROVIDER_MAPPER).resourcePath(session.getContext().getUri()).representation(rep).success();
|
||||
|
||||
@@ -518,4 +520,5 @@ public class IdentityProviderResource {
|
||||
IdentityProvider<?> provider = IdentityBrokerService.getIdentityProvider(session, identityProviderModel.getAlias());
|
||||
return provider.reloadKeys();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+387
@@ -0,0 +1,387 @@
|
||||
/*
|
||||
* Copyright 2026 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.testsuite.organization.mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.core.Response.Status;
|
||||
|
||||
import org.keycloak.admin.client.resource.OrganizationResource;
|
||||
import org.keycloak.broker.oidc.mappers.AdvancedClaimToGroupMapper;
|
||||
import org.keycloak.broker.provider.ConfigConstants;
|
||||
import org.keycloak.broker.provider.HardcodedGroupMapper;
|
||||
import org.keycloak.models.IdentityProviderMapperModel;
|
||||
import org.keycloak.models.IdentityProviderMapperSyncMode;
|
||||
import org.keycloak.representations.idm.GroupRepresentation;
|
||||
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
|
||||
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
||||
import org.keycloak.representations.idm.MemberRepresentation;
|
||||
import org.keycloak.representations.idm.OrganizationRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.organization.admin.AbstractOrganizationTest;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
|
||||
public class OrganizationGroupOidcIdpMapperTest extends AbstractOrganizationTest {
|
||||
|
||||
@Test
|
||||
public void testAdvancedClaimToGroupMapperWithOrganizationGroup() {
|
||||
// Create organization with IdP
|
||||
OrganizationRepresentation orgRep = createOrganization();
|
||||
OrganizationResource orgResource = testRealm().organizations().get(orgRep.getId());
|
||||
|
||||
// Create organization group
|
||||
GroupRepresentation orgGroup = new GroupRepresentation();
|
||||
orgGroup.setName("test-org-group");
|
||||
String groupId;
|
||||
try (Response response = orgResource.groups().addTopLevelGroup(orgGroup)) {
|
||||
assertThat(response.getStatus(), is(Status.CREATED.getStatusCode()));
|
||||
groupId = ApiUtil.getCreatedId(response);
|
||||
}
|
||||
|
||||
GroupRepresentation createdGroup = orgResource.groups().group(groupId).toRepresentation(false);
|
||||
String groupPath = createdGroup.getPath();
|
||||
|
||||
// Create AdvancedClaimToGroupMapper with organization group
|
||||
IdentityProviderRepresentation idp = orgResource.identityProviders().get(bc.getIDPAlias()).toRepresentation();
|
||||
|
||||
IdentityProviderMapperRepresentation mapper = new IdentityProviderMapperRepresentation();
|
||||
mapper.setName("org-group-mapper");
|
||||
mapper.setIdentityProviderMapper(AdvancedClaimToGroupMapper.PROVIDER_ID);
|
||||
mapper.setIdentityProviderAlias(idp.getAlias());
|
||||
mapper.setConfig(ImmutableMap.<String, String>builder()
|
||||
.put(IdentityProviderMapperModel.SYNC_MODE, IdentityProviderMapperSyncMode.FORCE.toString())
|
||||
.put(ConfigConstants.GROUP, groupPath)
|
||||
.put(AdvancedClaimToGroupMapper.CLAIM, "organization")
|
||||
.put(AdvancedClaimToGroupMapper.CLAIM_VALUE, orgRep.getName())
|
||||
.build());
|
||||
|
||||
String mapperId;
|
||||
try (Response response = testRealm().identityProviders().get(idp.getAlias()).addMapper(mapper)) {
|
||||
assertThat(response.getStatus(), is(Status.CREATED.getStatusCode()));
|
||||
mapperId = ApiUtil.getCreatedId(response);
|
||||
}
|
||||
|
||||
// Verify mapper was created
|
||||
IdentityProviderMapperRepresentation createdMapper = testRealm().identityProviders()
|
||||
.get(idp.getAlias())
|
||||
.getMapperById(mapperId);
|
||||
|
||||
assertNotNull("Mapper should be created", createdMapper);
|
||||
assertEquals("Mapper should reference org group", groupPath, createdMapper.getConfig().get(ConfigConstants.GROUP));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateMapperWithOrganizationSubgroup() {
|
||||
// Create organization with IdP
|
||||
OrganizationRepresentation orgRep = createOrganization();
|
||||
OrganizationResource orgResource = testRealm().organizations().get(orgRep.getId());
|
||||
|
||||
// Create parent organization group
|
||||
GroupRepresentation parentGroup = new GroupRepresentation();
|
||||
parentGroup.setName("parent-group");
|
||||
String parentId;
|
||||
try (Response response = orgResource.groups().addTopLevelGroup(parentGroup)) {
|
||||
assertThat(response.getStatus(), is(Status.CREATED.getStatusCode()));
|
||||
parentId = ApiUtil.getCreatedId(response);
|
||||
}
|
||||
|
||||
// Create child subgroup
|
||||
GroupRepresentation childGroup = new GroupRepresentation();
|
||||
childGroup.setName("child-group");
|
||||
try (Response response = orgResource.groups().group(parentId).addSubGroup(childGroup)) {
|
||||
assertThat(response.getStatus(), is(Status.CREATED.getStatusCode()));
|
||||
}
|
||||
|
||||
// Get the subgroup from the parent's children
|
||||
List<GroupRepresentation> children = orgResource.groups().group(parentId).getSubGroups(null, null, null, null);
|
||||
assertNotNull("Parent should have subgroups", children);
|
||||
assertThat("Parent should have 1 subgroup", children.size(), is(1));
|
||||
|
||||
String childGroupPath = children.get(0).getPath();
|
||||
|
||||
// Create mapper with child subgroup
|
||||
IdentityProviderRepresentation idp = orgResource.identityProviders().get(bc.getIDPAlias()).toRepresentation();
|
||||
|
||||
IdentityProviderMapperRepresentation mapper = new IdentityProviderMapperRepresentation();
|
||||
mapper.setName("subgroup-mapper");
|
||||
mapper.setIdentityProviderMapper(HardcodedGroupMapper.PROVIDER_ID);
|
||||
mapper.setIdentityProviderAlias(idp.getAlias());
|
||||
mapper.setConfig(ImmutableMap.<String, String>builder()
|
||||
.put(IdentityProviderMapperModel.SYNC_MODE, IdentityProviderMapperSyncMode.FORCE.toString())
|
||||
.put(ConfigConstants.GROUP, childGroupPath)
|
||||
.build());
|
||||
|
||||
String mapperId;
|
||||
try (Response response = testRealm().identityProviders().get(idp.getAlias()).addMapper(mapper)) {
|
||||
assertThat(response.getStatus(), is(Status.CREATED.getStatusCode()));
|
||||
mapperId = ApiUtil.getCreatedId(response);
|
||||
}
|
||||
|
||||
// Verify mapper was created with subgroup path
|
||||
IdentityProviderMapperRepresentation createdMapper = testRealm().identityProviders()
|
||||
.get(idp.getAlias())
|
||||
.getMapperById(mapperId);
|
||||
|
||||
assertNotNull("Mapper should be created", createdMapper);
|
||||
assertEquals("Mapper should reference org subgroup", childGroupPath, createdMapper.getConfig().get(ConfigConstants.GROUP));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetGroupsEndpointForNonOrganizationIdp() {
|
||||
// Create IdP NOT linked to organization
|
||||
IdentityProviderRepresentation nonOrgIdp = bc.setUpIdentityProvider();
|
||||
nonOrgIdp.setAlias("non-org-idp");
|
||||
try (Response response = testRealm().identityProviders().create(nonOrgIdp)) {
|
||||
assertThat(response.getStatus(), is(Status.CREATED.getStatusCode()));
|
||||
}
|
||||
getCleanup().addCleanup(() -> testRealm().identityProviders().get("non-org-idp").remove());
|
||||
|
||||
// Create organization with groups
|
||||
OrganizationRepresentation orgRep = createOrganization();
|
||||
OrganizationResource orgResource = testRealm().organizations().get(orgRep.getId());
|
||||
|
||||
GroupRepresentation orgGroup = new GroupRepresentation();
|
||||
orgGroup.setName("test-org-group");
|
||||
try (Response response = orgResource.groups().addTopLevelGroup(orgGroup)) {
|
||||
assertThat(response.getStatus(), is(Status.CREATED.getStatusCode()));
|
||||
}
|
||||
|
||||
// Try to get groups for non-org IdP - should return NOT_FOUND
|
||||
try {
|
||||
testRealm().organizations().get(orgRep.getId())
|
||||
.identityProviders().get("non-org-idp").getGroups(null, null, false, null, null, true, false);
|
||||
fail("Should have failed with NotFoundException");
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
// Expected
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserAddedToOrganizationGroupViaMapper() {
|
||||
// Create organization with group
|
||||
OrganizationRepresentation orgRep = createOrganization();
|
||||
OrganizationResource orgResource = testRealm().organizations().get(orgRep.getId());
|
||||
|
||||
GroupRepresentation orgGroup = new GroupRepresentation();
|
||||
orgGroup.setName("mapper-test-group");
|
||||
String groupId;
|
||||
try (Response response = orgResource.groups().addTopLevelGroup(orgGroup)) {
|
||||
assertThat(response.getStatus(), is(Status.CREATED.getStatusCode()));
|
||||
groupId = ApiUtil.getCreatedId(response);
|
||||
}
|
||||
|
||||
String groupPath = orgResource.groups().group(groupId).toRepresentation(false).getPath();
|
||||
|
||||
// Add hardcoded group mapper to the organization IdP
|
||||
IdentityProviderRepresentation idp = orgResource.identityProviders().get(bc.getIDPAlias()).toRepresentation();
|
||||
|
||||
IdentityProviderMapperRepresentation mapper = new IdentityProviderMapperRepresentation();
|
||||
mapper.setName("org-group-mapper");
|
||||
mapper.setIdentityProviderMapper(HardcodedGroupMapper.PROVIDER_ID);
|
||||
mapper.setIdentityProviderAlias(idp.getAlias());
|
||||
mapper.setConfig(ImmutableMap.<String, String>builder()
|
||||
.put(IdentityProviderMapperModel.SYNC_MODE, IdentityProviderMapperSyncMode.FORCE.toString())
|
||||
.put(ConfigConstants.GROUP, groupPath)
|
||||
.build());
|
||||
|
||||
try (Response response = testRealm().identityProviders().get(idp.getAlias()).addMapper(mapper)) {
|
||||
assertThat(response.getStatus(), is(Status.CREATED.getStatusCode()));
|
||||
}
|
||||
|
||||
// Authenticate via IdP - user should be added to org group
|
||||
assertBrokerRegistration(orgResource, bc.getUserLogin(), bc.getUserEmail());
|
||||
|
||||
// Verify user is member of the organization group
|
||||
UserRepresentation user = getUserRepresentation(bc.getUserEmail());
|
||||
assertNotNull(user);
|
||||
|
||||
List<MemberRepresentation> groupMembers = orgResource.groups().group(groupId).getMembers(null, null, false);
|
||||
assertThat(groupMembers, hasSize(1));
|
||||
assertThat(groupMembers.get(0).getId(), is(user.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserNotAddedToGroupAfterIdpUnlinkedFromOrganization() {
|
||||
OrganizationRepresentation orgRep = createOrganization();
|
||||
OrganizationResource orgResource = testRealm().organizations().get(orgRep.getId());
|
||||
|
||||
GroupRepresentation orgGroup = new GroupRepresentation();
|
||||
orgGroup.setName("unlink-test-group");
|
||||
String groupId;
|
||||
try (Response response = orgResource.groups().addTopLevelGroup(orgGroup)) {
|
||||
assertThat(response.getStatus(), is(Status.CREATED.getStatusCode()));
|
||||
groupId = ApiUtil.getCreatedId(response);
|
||||
}
|
||||
|
||||
String groupPath = orgResource.groups().group(groupId).toRepresentation(false).getPath();
|
||||
|
||||
// Add a HardcodedGroupMapper pointing to the org group
|
||||
IdentityProviderRepresentation idp = orgResource.identityProviders().get(bc.getIDPAlias()).toRepresentation();
|
||||
|
||||
IdentityProviderMapperRepresentation mapper = new IdentityProviderMapperRepresentation();
|
||||
mapper.setName("unlink-test-mapper");
|
||||
mapper.setIdentityProviderMapper(HardcodedGroupMapper.PROVIDER_ID);
|
||||
mapper.setIdentityProviderAlias(idp.getAlias());
|
||||
mapper.setConfig(ImmutableMap.<String, String>builder()
|
||||
.put(IdentityProviderMapperModel.SYNC_MODE, IdentityProviderMapperSyncMode.FORCE.toString())
|
||||
.put(ConfigConstants.GROUP, groupPath)
|
||||
.build());
|
||||
|
||||
try (Response response = testRealm().identityProviders().get(idp.getAlias()).addMapper(mapper)) {
|
||||
assertThat(response.getStatus(), is(Status.CREATED.getStatusCode()));
|
||||
}
|
||||
|
||||
// First login: user IS added to the org group while IdP is still linked
|
||||
assertBrokerRegistration(orgResource, bc.getUserLogin(), bc.getUserEmail());
|
||||
|
||||
List<MemberRepresentation> groupMembers = orgResource.groups().group(groupId).getMembers(null, null, false);
|
||||
assertThat(groupMembers, hasSize(1));
|
||||
|
||||
// Log out from both realms
|
||||
UserRepresentation user = getUserRepresentation(bc.getUserEmail());
|
||||
realmsResouce().realm(bc.consumerRealmName()).users().get(user.getId()).logout();
|
||||
realmsResouce().realm(bc.providerRealmName()).logoutAll();
|
||||
|
||||
// Unlink IdP from organization - the IdP still exists in the realm but is no longer org-linked
|
||||
try (Response response = orgResource.identityProviders().get(bc.getIDPAlias()).delete()) {
|
||||
assertThat(response.getStatus(), is(Status.NO_CONTENT.getStatusCode()));
|
||||
}
|
||||
|
||||
// Remove the user from the group so the second login can prove the mapper does not re-add them
|
||||
orgResource.groups().group(groupId).removeMember(user.getId());
|
||||
groupMembers = orgResource.groups().group(groupId).getMembers(null, null, false);
|
||||
assertThat(groupMembers, hasSize(0));
|
||||
|
||||
// Second login: bypass the org identity-first page (which hides the unlinked IdP) by
|
||||
// navigating directly with kc_idp_hint. The IdP still exists in the realm so login succeeds,
|
||||
// but the mapper cannot resolve the org group and the user is NOT re-added to it.
|
||||
oauth.clientId("broker-app");
|
||||
loginPage.open(bc.consumerRealmName());
|
||||
driver.navigate().to(driver.getCurrentUrl() + "&kc_idp_hint=" + bc.getIDPAlias());
|
||||
loginOrgIdp(bc.getUserLogin(), bc.getUserEmail(), false, true);
|
||||
|
||||
// Verify user was not re-added to the org group
|
||||
groupMembers = orgResource.groups().group(groupId).getMembers(null, null, false);
|
||||
assertThat(groupMembers, hasSize(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRealmGroupAllowedWithOrganizationIdp() {
|
||||
OrganizationRepresentation orgRep = createOrganization();
|
||||
OrganizationResource orgResource = testRealm().organizations().get(orgRep.getId());
|
||||
|
||||
// Create REALM group in the consumer realm
|
||||
GroupRepresentation realmGroup = new GroupRepresentation();
|
||||
realmGroup.setName("realm-test-group");
|
||||
try (Response response = realmsResouce().realm(bc.consumerRealmName()).groups().add(realmGroup)) {
|
||||
assertThat(response.getStatus(), is(Status.CREATED.getStatusCode()));
|
||||
}
|
||||
|
||||
String groupPath = realmsResouce().realm(bc.consumerRealmName()).getGroupByPath("/realm-test-group").getPath();
|
||||
|
||||
// Add mapper with REALM group to organization IdP
|
||||
IdentityProviderRepresentation idp = orgResource.identityProviders().get(bc.getIDPAlias()).toRepresentation();
|
||||
|
||||
IdentityProviderMapperRepresentation mapper = new IdentityProviderMapperRepresentation();
|
||||
mapper.setName("realm-group-mapper");
|
||||
mapper.setIdentityProviderMapper(HardcodedGroupMapper.PROVIDER_ID);
|
||||
mapper.setIdentityProviderAlias(idp.getAlias());
|
||||
mapper.setConfig(ImmutableMap.<String, String>builder()
|
||||
.put(IdentityProviderMapperModel.SYNC_MODE, IdentityProviderMapperSyncMode.FORCE.toString())
|
||||
.put(ConfigConstants.GROUP, groupPath)
|
||||
.build());
|
||||
|
||||
try (Response response = testRealm().identityProviders().get(idp.getAlias()).addMapper(mapper)) {
|
||||
assertThat(response.getStatus(), is(Status.CREATED.getStatusCode()));
|
||||
}
|
||||
|
||||
// Authenticate via IdP - realm groups are always allowed
|
||||
assertBrokerRegistration(orgResource, bc.getUserLogin(), bc.getUserEmail());
|
||||
|
||||
// Verify user is member of the realm group
|
||||
UserRepresentation user = getUserRepresentation(bc.getUserEmail());
|
||||
assertNotNull(user);
|
||||
|
||||
List<GroupRepresentation> userGroups = realmsResouce().realm(bc.consumerRealmName()).users().get(user.getId()).groups();
|
||||
assertThat(userGroups, hasSize(1));
|
||||
assertThat(userGroups.get(0).getPath(), is(groupPath));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHardcodedGroupMapperDoesNotAssignOrganizationGroupMembershipWhenOrganizationIsDisabled() {
|
||||
OrganizationRepresentation orgRep = createOrganization();
|
||||
OrganizationResource orgResource = testRealm().organizations().get(orgRep.getId());
|
||||
|
||||
GroupRepresentation orgGroup = new GroupRepresentation();
|
||||
orgGroup.setName("disabled-org-test-group");
|
||||
String groupId;
|
||||
try (Response response = orgResource.groups().addTopLevelGroup(orgGroup)) {
|
||||
assertThat(response.getStatus(), is(Status.CREATED.getStatusCode()));
|
||||
groupId = ApiUtil.getCreatedId(response);
|
||||
}
|
||||
|
||||
String groupPath = orgResource.groups().group(groupId).toRepresentation(false).getPath();
|
||||
|
||||
IdentityProviderRepresentation idp = orgResource.identityProviders().get(bc.getIDPAlias()).toRepresentation();
|
||||
|
||||
IdentityProviderMapperRepresentation mapper = new IdentityProviderMapperRepresentation();
|
||||
mapper.setName("disabled-org-test-mapper");
|
||||
mapper.setIdentityProviderMapper(HardcodedGroupMapper.PROVIDER_ID);
|
||||
mapper.setIdentityProviderAlias(idp.getAlias());
|
||||
mapper.setConfig(ImmutableMap.<String, String>builder()
|
||||
.put(IdentityProviderMapperModel.SYNC_MODE, IdentityProviderMapperSyncMode.IMPORT.toString())
|
||||
.put(ConfigConstants.GROUP, groupPath)
|
||||
.build());
|
||||
|
||||
try (Response response = testRealm().identityProviders().get(idp.getAlias()).addMapper(mapper)) {
|
||||
assertThat(response.getStatus(), is(Status.CREATED.getStatusCode()));
|
||||
}
|
||||
|
||||
// First login: org is enabled, user IS added to org group
|
||||
assertBrokerRegistration(orgResource, bc.getUserLogin(), bc.getUserEmail());
|
||||
|
||||
List<MemberRepresentation> groupMembers = orgResource.groups().group(groupId).getMembers(null, null, false);
|
||||
assertThat(groupMembers, hasSize(1));
|
||||
|
||||
// When org is disabled, the IdP appears disabled, blocking further broker logins.
|
||||
orgRep.setEnabled(false);
|
||||
try (Response ignored = orgResource.update(orgRep)) {
|
||||
assertThat(ignored.getStatus(), is(Status.NO_CONTENT.getStatusCode()));
|
||||
}
|
||||
|
||||
// Verify the org-linked IdP now appears disabled (org-aware wrapper)
|
||||
IdentityProviderRepresentation updatedIdp = testRealm().identityProviders().get(bc.getIDPAlias()).toRepresentation();
|
||||
assertThat("IdP should appear disabled when org is disabled", updatedIdp.isEnabled(), is(false));
|
||||
|
||||
// Group membership assigned while the org was enabled is unaffected by the org being disabled
|
||||
groupMembers = orgResource.groups().group(groupId).getMembers(null, null, false);
|
||||
assertThat(groupMembers, hasSize(1));
|
||||
}
|
||||
}
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright 2026 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.testsuite.organization.mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.core.Response.Status;
|
||||
|
||||
import org.keycloak.admin.client.resource.OrganizationResource;
|
||||
import org.keycloak.broker.provider.ConfigConstants;
|
||||
import org.keycloak.broker.provider.HardcodedGroupMapper;
|
||||
import org.keycloak.broker.saml.SAMLIdentityProviderConfig;
|
||||
import org.keycloak.models.IdentityProviderMapperModel;
|
||||
import org.keycloak.models.IdentityProviderMapperSyncMode;
|
||||
import org.keycloak.models.IdentityProviderSyncMode;
|
||||
import org.keycloak.representations.idm.GroupRepresentation;
|
||||
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
|
||||
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
||||
import org.keycloak.representations.idm.MemberRepresentation;
|
||||
import org.keycloak.representations.idm.OrganizationRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.broker.BrokerConfiguration;
|
||||
import org.keycloak.testsuite.broker.KcSamlBrokerConfiguration;
|
||||
import org.keycloak.testsuite.organization.admin.AbstractOrganizationTest;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
public class OrganizationGroupSamlIdpMapperTest extends AbstractOrganizationTest {
|
||||
|
||||
@Override
|
||||
protected BrokerConfiguration createBrokerConfiguration() {
|
||||
return new KcSamlBrokerConfiguration() {
|
||||
@Override
|
||||
public RealmRepresentation createProviderRealm() {
|
||||
RealmRepresentation realmRep = super.createProviderRealm();
|
||||
realmRep.setOrganizationsEnabled(true);
|
||||
return realmRep;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIDPClientIdInProviderRealm() {
|
||||
return "saml-broker";
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityProviderRepresentation setUpIdentityProvider(IdentityProviderSyncMode syncMode) {
|
||||
IdentityProviderRepresentation broker = super.setUpIdentityProvider(syncMode);
|
||||
broker.getConfig().put(SAMLIdentityProviderConfig.ENTITY_ID, getIDPClientIdInProviderRealm());
|
||||
return broker;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHardcodedGroupMapperAssignsOrganizationGroupMembershipWithSamlIdp() {
|
||||
OrganizationRepresentation orgRep = createOrganization();
|
||||
OrganizationResource orgResource = testRealm().organizations().get(orgRep.getId());
|
||||
|
||||
GroupRepresentation orgGroup = new GroupRepresentation();
|
||||
orgGroup.setName("saml-test-group");
|
||||
String groupId;
|
||||
try (Response response = orgResource.groups().addTopLevelGroup(orgGroup)) {
|
||||
assertThat(response.getStatus(), is(Status.CREATED.getStatusCode()));
|
||||
groupId = ApiUtil.getCreatedId(response);
|
||||
}
|
||||
|
||||
String groupPath = orgResource.groups().group(groupId).toRepresentation(false).getPath();
|
||||
|
||||
// Create HardcodedGroupMapper pointing to the org group
|
||||
IdentityProviderRepresentation idp = orgResource.identityProviders().get(bc.getIDPAlias()).toRepresentation();
|
||||
|
||||
IdentityProviderMapperRepresentation mapper = new IdentityProviderMapperRepresentation();
|
||||
mapper.setName("saml-hardcoded-group-mapper");
|
||||
mapper.setIdentityProviderMapper(HardcodedGroupMapper.PROVIDER_ID);
|
||||
mapper.setIdentityProviderAlias(idp.getAlias());
|
||||
mapper.setConfig(ImmutableMap.<String, String>builder()
|
||||
.put(IdentityProviderMapperModel.SYNC_MODE, IdentityProviderMapperSyncMode.IMPORT.toString())
|
||||
.put(ConfigConstants.GROUP, groupPath)
|
||||
.build());
|
||||
|
||||
try (Response response = testRealm().identityProviders().get(idp.getAlias()).addMapper(mapper)) {
|
||||
assertThat(response.getStatus(), is(Status.CREATED.getStatusCode()));
|
||||
}
|
||||
|
||||
// Authenticate via SAML IdP - user should be added to the org group via the mapper
|
||||
assertBrokerRegistration(orgResource, bc.getUserLogin(), bc.getUserEmail());
|
||||
|
||||
UserRepresentation user = getUserRepresentation(bc.getUserEmail());
|
||||
assertNotNull(user);
|
||||
|
||||
List<MemberRepresentation> groupMembers = orgResource.groups().group(groupId).getMembers(null, null, false);
|
||||
assertThat(groupMembers, hasSize(1));
|
||||
assertThat(groupMembers.get(0).getId(), is(user.getId()));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user