Avoid login failures in case of non-existing group or role references and update references in case of renaming or moving

- no longer throw an exception, when a role or group cannot be found, log a warning instead
- update mapper references in case of the following events:
   - moving a group
   - renaming a group
   - renaming a role
   - renaming a client's Client ID (may affect role qualifiers)
- in case a role or group is removed, the reference still will not be changed
- extend and refactor integration tests in order to check the new behavior

Closes #11236
This commit is contained in:
danielFesenmeyer 2022-07-21 19:44:27 +02:00 committed by Marek Posolda
parent 761929d174
commit f80a8fbed0
45 changed files with 1702 additions and 507 deletions

View file

@ -444,6 +444,7 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, ClientSc
if (group.getParentId() != null) {
group.getParent().removeChild(group);
}
GroupModel previousParent = group.getParent();
group.setParent(toParent);
if (toParent != null) toParent.addChild(group);
else session.groups().addTopLevelGroup(realm, group);
@ -452,6 +453,33 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, ClientSc
// DuplicateModelException {@link PersistenceExceptionConverter} is not called if the
// ConstraintViolationException is not thrown in method called directly from EntityManager
em.flush();
String newPath = KeycloakModelUtils.buildGroupPath(group);
String previousPath = KeycloakModelUtils.buildGroupPath(group, previousParent);
GroupModel.GroupPathChangeEvent event =
new GroupModel.GroupPathChangeEvent() {
@Override
public RealmModel getRealm() {
return realm;
}
@Override
public String getNewPath() {
return newPath;
}
@Override
public String getPreviousPath() {
return previousPath;
}
@Override
public KeycloakSession getKeycloakSession() {
return session;
}
};
session.getKeycloakSessionFactory().publish(event);
}
@Override

View file

@ -33,6 +33,7 @@ import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator;
import org.keycloak.models.map.storage.QueryParameters;
import org.keycloak.models.map.storage.criteria.DefaultModelCriteria;
import org.keycloak.models.utils.KeycloakModelUtils;
import java.util.Map;
import java.util.Objects;
@ -259,6 +260,8 @@ public class MapGroupProvider implements GroupProvider {
public void moveGroup(RealmModel realm, GroupModel group, GroupModel toParent) {
LOG.tracef("moveGroup(%s, %s, %s)%s", realm, group, toParent, getShortStackTrace());
GroupModel previousParent = group.getParent();
if (toParent != null && group.getId().equals(toParent.getId())) {
return;
}
@ -282,6 +285,33 @@ public class MapGroupProvider implements GroupProvider {
}
group.setParent(toParent);
if (toParent != null) toParent.addChild(group);
String newPath = KeycloakModelUtils.buildGroupPath(group);
String previousPath = KeycloakModelUtils.buildGroupPath(group, previousParent);
GroupModel.GroupPathChangeEvent event =
new GroupModel.GroupPathChangeEvent() {
@Override
public RealmModel getRealm() {
return realm;
}
@Override
public String getNewPath() {
return newPath;
}
@Override
public String getPreviousPath() {
return previousPath;
}
@Override
public KeycloakSession getKeycloakSession() {
return session;
}
};
session.getKeycloakSessionFactory().publish(event);
}
@Override

View file

@ -17,6 +17,8 @@
package org.keycloak.broker.provider;
import org.jboss.logging.Logger;
import org.keycloak.broker.provider.mappersync.ConfigSyncEventListener;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
@ -28,6 +30,11 @@ import org.keycloak.models.UserModel;
* @version $Revision: 1 $
*/
public abstract class AbstractIdentityProviderMapper implements IdentityProviderMapper {
private static final Logger LOG = Logger.getLogger(AbstractIdentityProviderMapper.class);
private static volatile KeycloakSessionFactory keycloakSessionFactory;
@Override
public void close() {
@ -45,7 +52,24 @@ public abstract class AbstractIdentityProviderMapper implements IdentityProvider
@Override
public void postInit(KeycloakSessionFactory factory) {
registerConfigSyncEventListenerOnce(factory);
}
private void registerConfigSyncEventListenerOnce(KeycloakSessionFactory factory) {
/*
* Make sure that the config sync listener is registered only once for a session factory. It would also be
* possible to register it only once per VM, but that does not work fine in integration tests.
*/
if (keycloakSessionFactory != factory) {
synchronized (AbstractIdentityProviderMapper.class) {
if (keycloakSessionFactory != factory) {
keycloakSessionFactory = factory;
LOG.infof("Registering %s", ConfigSyncEventListener.class);
factory.register(new ConfigSyncEventListener());
}
}
}
}
@Override

View file

@ -0,0 +1,71 @@
/*
* Copyright 2022 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.broker.provider.mappersync;
import org.jboss.logging.Logger;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.provider.ProviderEvent;
import org.keycloak.utils.StringUtil;
import java.util.Map;
import java.util.function.Consumer;
/**
* Abstract base class for updating a single reference (specified via a single config property).
*
* @author <a href="mailto:daniel.fesenmeyer@bosch.io">Daniel Fesenmeyer</a>
*/
public abstract class AbstractConfigPropertySynchronizer<T extends ProviderEvent> implements ConfigSynchronizer<T> {
private static final Logger LOG = Logger.getLogger(AbstractConfigPropertySynchronizer.class);
protected abstract String getConfigPropertyName();
protected abstract void updateConfigPropertyIfNecessary(T event, String currentPropertyValue,
Consumer<String> propertyUpdater);
@Override
public final void handleEvent(T event, IdentityProviderMapperModel idpMapper) {
Map<String, String> config = idpMapper.getConfig();
if (config == null) {
return;
}
String configPropertyName = getConfigPropertyName();
String configuredValue = config.get(configPropertyName);
if (StringUtil.isBlank(configuredValue)) {
return;
}
Consumer<String> propertyUpdater = value -> config.put(configPropertyName, value);
updateConfigPropertyIfNecessary(event, configuredValue, propertyUpdater);
final String newConfiguredValue = config.get(configPropertyName);
if (!configuredValue.equals(newConfiguredValue)) {
RealmModel realm = extractRealm(event);
LOG.infof(
"Reference of type '%s' changed from '%s' to '%s' in realm '%s'. Adjusting the reference from mapper '%s' of IDP '%s'.",
configPropertyName, configuredValue, newConfiguredValue, realm.getName(), idpMapper.getName(),
idpMapper.getIdentityProviderAlias());
realm.updateIdentityProviderMapper(idpMapper);
}
}
}

View file

@ -0,0 +1,80 @@
/*
* Copyright 2022 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.broker.provider.mappersync;
import org.jboss.logging.Logger;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.provider.ProviderEvent;
import org.keycloak.provider.ProviderEventListener;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* Event listener which synchronizes mapper configs, when references change.
*
* @author <a href="mailto:daniel.fesenmeyer@bosch.io">Daniel Fesenmeyer</a>
*/
public final class ConfigSyncEventListener implements ProviderEventListener {
private static final Logger LOG = Logger.getLogger(ConfigSyncEventListener.class);
private static final List<ConfigSynchronizer<? extends ProviderEvent>> SYNCHRONIZERS =
Arrays.asList(GroupConfigPropertyByPathSynchronizer.INSTANCE,
RoleConfigPropertyByClientIdSynchronizer.INSTANCE, RoleConfigPropertyByRoleNameSynchronizer.INSTANCE);
@Override
public void onEvent(ProviderEvent event) {
List<IdentityProviderMapperModel> realmMappers = null;
for (ConfigSynchronizer<? extends ProviderEvent> s : SYNCHRONIZERS) {
ConfigSynchronizer<ProviderEvent> configSynchronizer = (ConfigSynchronizer<ProviderEvent>) s;
if (eventMatchesSynchronizer(event, configSynchronizer)) {
LOG.debugf("Synchronizer %s matches event: %s", configSynchronizer, event);
if (realmMappers == null) {
/*
* an event always refers to just one realm, so we can use an arbitrary synchronizer to extract the
* realm
*/
RealmModel realm = configSynchronizer.extractRealm(event);
realmMappers = realm.getIdentityProviderMappersStream()
.collect(Collectors.toList());
}
realmMappers.forEach(idpMapper -> {
LOG.debugf("Apply synchronizer %s to event %s and mapper with name %s", configSynchronizer, event,
idpMapper.getName());
configSynchronizer.handleEvent(event, idpMapper);
});
} else {
LOG.debugf("Synchronizer %s does not match event: %s", configSynchronizer, event);
}
}
}
private static boolean eventMatchesSynchronizer(ProviderEvent event,
ConfigSynchronizer<? extends ProviderEvent> synchronizer) {
Class<? extends ProviderEvent> handledClass = synchronizer.getEventClass();
return (handledClass.isAssignableFrom(event.getClass()));
}
}

View file

@ -0,0 +1,35 @@
/*
* Copyright 2022 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.broker.provider.mappersync;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.provider.ProviderEvent;
/**
* Interface for updating references in mapper configs, when references (like group path) change.
*
* @author <a href="mailto:daniel.fesenmeyer@bosch.io">Daniel Fesenmeyer</a>
*/
public interface ConfigSynchronizer<T extends ProviderEvent> {
Class<T> getEventClass();
RealmModel extractRealm(T event);
void handleEvent(T event, IdentityProviderMapperModel idpMapper);
}

View file

@ -0,0 +1,67 @@
/*
* Copyright 2022 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.broker.provider.mappersync;
import org.keycloak.broker.provider.ConfigConstants;
import org.keycloak.models.GroupModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import java.util.function.Consumer;
/**
* Updates a group reference in a mapper config, when the path of a group changes.
*
* @author <a href="mailto:daniel.fesenmeyer@bosch.io">Daniel Fesenmeyer</a>
*/
public class GroupConfigPropertyByPathSynchronizer extends AbstractConfigPropertySynchronizer<GroupModel.GroupPathChangeEvent> {
public static final GroupConfigPropertyByPathSynchronizer INSTANCE = new GroupConfigPropertyByPathSynchronizer();
private GroupConfigPropertyByPathSynchronizer() {
// noop
}
@Override
public Class<GroupModel.GroupPathChangeEvent> getEventClass() {
return GroupModel.GroupPathChangeEvent.class;
}
@Override
public RealmModel extractRealm(GroupModel.GroupPathChangeEvent event) {
return event.getRealm();
}
@Override
public String getConfigPropertyName() {
return ConfigConstants.GROUP;
}
@Override
protected void updateConfigPropertyIfNecessary(GroupModel.GroupPathChangeEvent event,
String currentPropertyValue, Consumer<String> propertyUpdater) {
String configuredGroupPath = KeycloakModelUtils.normalizeGroupPath(currentPropertyValue);
String previousGroupPath = event.getPreviousPath();
if (previousGroupPath.equals(configuredGroupPath)) {
String newPath = event.getNewPath();
propertyUpdater.accept(newPath);
}
}
}

View file

@ -0,0 +1,75 @@
/*
* Copyright 2022 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.broker.provider.mappersync;
import org.keycloak.broker.provider.ConfigConstants;
import org.keycloak.models.ClientModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import java.util.function.Consumer;
/**
* Updates a role reference in a mapper config, when a client ID changes.
*
* @author <a href="mailto:daniel.fesenmeyer@bosch.io">Daniel Fesenmeyer</a>
*/
public class RoleConfigPropertyByClientIdSynchronizer
extends AbstractConfigPropertySynchronizer<ClientModel.ClientIdChangeEvent> {
public static final RoleConfigPropertyByClientIdSynchronizer INSTANCE =
new RoleConfigPropertyByClientIdSynchronizer();
private RoleConfigPropertyByClientIdSynchronizer() {
// noop
}
@Override
public Class<ClientModel.ClientIdChangeEvent> getEventClass() {
return ClientModel.ClientIdChangeEvent.class;
}
@Override
public RealmModel extractRealm(ClientModel.ClientIdChangeEvent event) {
return event.getUpdatedClient().getRealm();
}
@Override
public String getConfigPropertyName() {
return ConfigConstants.ROLE;
}
@Override
protected void updateConfigPropertyIfNecessary(ClientModel.ClientIdChangeEvent event, String currentPropertyValue,
Consumer<String> propertyUpdater) {
String[] parsedConfiguredRoleQualifier = KeycloakModelUtils.parseRole(currentPropertyValue);
String configuredClientId = parsedConfiguredRoleQualifier[0];
if (configuredClientId == null) {
// a realm role is configured for the mapper, event is not relevant
return;
}
String configuredRoleName = parsedConfiguredRoleQualifier[1];
if (configuredClientId.equals(event.getPreviousClientId())) {
String newRoleQualifier = KeycloakModelUtils.buildRoleQualifier(event.getNewClientId(), configuredRoleName);
propertyUpdater.accept(newRoleQualifier);
}
}
}

View file

@ -0,0 +1,69 @@
/*
* Copyright 2022 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.broker.provider.mappersync;
import org.keycloak.broker.provider.ConfigConstants;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import java.util.function.Consumer;
/**
* Updates a role reference a in mapper config, when a role name changes.
*
* @author <a href="mailto:daniel.fesenmeyer@bosch.io">Daniel Fesenmeyer</a>
*/
public class RoleConfigPropertyByRoleNameSynchronizer
extends AbstractConfigPropertySynchronizer<RoleModel.RoleNameChangeEvent> {
public static final RoleConfigPropertyByRoleNameSynchronizer INSTANCE =
new RoleConfigPropertyByRoleNameSynchronizer();
private RoleConfigPropertyByRoleNameSynchronizer() {
// noop
}
@Override
public Class<RoleModel.RoleNameChangeEvent> getEventClass() {
return RoleModel.RoleNameChangeEvent.class;
}
@Override
public RealmModel extractRealm(RoleModel.RoleNameChangeEvent event) {
return event.getRealm();
}
@Override
public String getConfigPropertyName() {
return ConfigConstants.ROLE;
}
@Override
protected void updateConfigPropertyIfNecessary(RoleModel.RoleNameChangeEvent event, String currentPropertyValue,
Consumer<String> propertyUpdater) {
String previousRoleQualifier =
KeycloakModelUtils.buildRoleQualifier(event.getClientId(), event.getPreviousName());
if (previousRoleQualifier.equals(currentPropertyValue)) {
String newRoleQualifier = KeycloakModelUtils.buildRoleQualifier(event.getClientId(), event.getNewName());
propertyUpdater.accept(newRoleQualifier);
}
}
}

View file

@ -79,13 +79,17 @@ import java.util.function.Function;
/**
* Set of helper methods, which are useful in various model implementations.
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>,
* <a href="mailto:daniel.fesenmeyer@bosch.io">Daniel Fesenmeyer</a>
*/
public final class KeycloakModelUtils {
public static final String AUTH_TYPE_CLIENT_SECRET = "client-secret";
public static final String AUTH_TYPE_CLIENT_SECRET_JWT = "client-secret-jwt";
public static final String GROUP_PATH_SEPARATOR = "/";
private static final char CLIENT_ROLE_SEPARATOR = '.';
private KeycloakModelUtils() {
}
@ -541,7 +545,7 @@ public final class KeycloakModelUtils {
}
/**
* Given the {@code pathParts} of a group with the given {@code groupName}, format the {@pathParts} in order to ignore
* Given the {@code pathParts} of a group with the given {@code groupName}, format the {@code segments} in order to ignore
* group names containing a {@code /} character.
*
* @param segments the path segments
@ -550,7 +554,7 @@ public final class KeycloakModelUtils {
* @return a new array of strings with the correct segments in case the group has a name containing slashes
*/
private static String[] formatPathSegments(String[] segments, int index, String groupName) {
String[] nameSegments = groupName.split("/");
String[] nameSegments = groupName.split(GROUP_PATH_SEPARATOR);
if (nameSegments.length > 1 && segments.length >= nameSegments.length) {
for (int i = 0; i < nameSegments.length; i++) {
@ -582,13 +586,13 @@ public final class KeycloakModelUtils {
if (path == null) {
return null;
}
if (path.startsWith("/")) {
if (path.startsWith(GROUP_PATH_SEPARATOR)) {
path = path.substring(1);
}
if (path.endsWith("/")) {
if (path.endsWith(GROUP_PATH_SEPARATOR)) {
path = path.substring(0, path.length() - 1);
}
String[] split = path.split("/");
String[] split = path.split(GROUP_PATH_SEPARATOR);
if (split.length == 0) return null;
return realm.getTopLevelGroupsStream().map(group -> {
@ -610,6 +614,42 @@ public final class KeycloakModelUtils {
}).filter(Objects::nonNull).findFirst().orElse(null);
}
private static void buildGroupPath(StringBuilder sb, String groupName, GroupModel parent) {
if (parent != null) {
buildGroupPath(sb, parent.getName(), parent.getParent());
}
sb.append(GROUP_PATH_SEPARATOR).append(groupName);
}
public static String buildGroupPath(GroupModel group) {
StringBuilder sb = new StringBuilder();
buildGroupPath(sb, group.getName(), group.getParent());
return sb.toString();
}
public static String buildGroupPath(GroupModel group, GroupModel otherParentGroup) {
StringBuilder sb = new StringBuilder();
buildGroupPath(sb, group.getName(), otherParentGroup);
return sb.toString();
}
public static String normalizeGroupPath(final String groupPath) {
if (groupPath == null) {
return null;
}
String normalized = groupPath;
if (!normalized.startsWith(GROUP_PATH_SEPARATOR)) {
normalized = GROUP_PATH_SEPARATOR + normalized;
}
if (normalized.endsWith(GROUP_PATH_SEPARATOR)) {
normalized = normalized.substring(0, normalized.length() - 1);
}
return normalized;
}
/**
* @param client {@link ClientModel}
* @param container {@link ScopeContainerModel}
@ -629,8 +669,12 @@ public final class KeycloakModelUtils {
// Used in various role mappers
public static RoleModel getRoleFromString(RealmModel realm, String roleName) {
if (roleName == null) {
return null;
}
// Check client roles for all possible splits by dot
int scopeIndex = roleName.lastIndexOf('.');
int scopeIndex = roleName.lastIndexOf(CLIENT_ROLE_SEPARATOR);
while (scopeIndex >= 0) {
String appName = roleName.substring(0, scopeIndex);
ClientModel client = realm.getClientByClientId(appName);
@ -639,7 +683,7 @@ public final class KeycloakModelUtils {
return client.getRole(role);
}
scopeIndex = roleName.lastIndexOf('.', scopeIndex - 1);
scopeIndex = roleName.lastIndexOf(CLIENT_ROLE_SEPARATOR, scopeIndex - 1);
}
// determine if roleName is a realm role
@ -648,7 +692,7 @@ public final class KeycloakModelUtils {
// Used for hardcoded role mappers
public static String[] parseRole(String role) {
int scopeIndex = role.lastIndexOf('.');
int scopeIndex = role.lastIndexOf(CLIENT_ROLE_SEPARATOR);
if (scopeIndex > -1) {
String appName = role.substring(0, scopeIndex);
role = role.substring(scopeIndex + 1);
@ -661,6 +705,14 @@ public final class KeycloakModelUtils {
}
}
public static String buildRoleQualifier(String clientId, String roleName) {
if (clientId == null) {
return roleName;
}
return clientId + CLIENT_ROLE_SEPARATOR + roleName;
}
/**
* Check to see if a flow is currently in use
*

View file

@ -114,20 +114,10 @@ public class ModelToRepresentation {
private static final Logger LOG = Logger.getLogger(ModelToRepresentation.class);
public static void buildGroupPath(StringBuilder sb, GroupModel group) {
if (group.getParent() != null) {
buildGroupPath(sb, group.getParent());
}
sb.append('/').append(group.getName());
}
public static String buildGroupPath(GroupModel group) {
StringBuilder sb = new StringBuilder();
buildGroupPath(sb, group);
return sb.toString();
return KeycloakModelUtils.buildGroupPath(group);
}
public static GroupRepresentation groupToBriefRepresentation(GroupModel g) {
return toRepresentation(g, false);
}

View file

@ -503,8 +503,10 @@ public class RepresentationToModel {
}
}
public static void updateClient(ClientRepresentation rep, ClientModel resource) {
if (rep.getClientId() != null) resource.setClientId(rep.getClientId());
public static void updateClient(ClientRepresentation rep, ClientModel resource, KeycloakSession session) {
String newClientId = rep.getClientId();
String previousClientId = resource.getClientId();
if (newClientId != null) resource.setClientId(newClientId);
if (rep.getName() != null) resource.setName(rep.getName());
if (rep.getDescription() != null) resource.setDescription(rep.getDescription());
if (rep.isEnabled() != null) resource.setEnabled(rep.isEnabled());
@ -543,7 +545,7 @@ public class RepresentationToModel {
if ("saml".equals(rep.getProtocol())
&& (rep.getAttributes() == null
|| !rep.getAttributes().containsKey("saml.artifact.binding.identifier"))) {
resource.setAttribute("saml.artifact.binding.identifier", computeArtifactBindingIdentifierString(rep.getClientId()));
resource.setAttribute("saml.artifact.binding.identifier", computeArtifactBindingIdentifierString(newClientId));
}
if (rep.getAuthenticationFlowBindingOverrides() != null) {
@ -566,12 +568,12 @@ public class RepresentationToModel {
List<String> redirectUris = rep.getRedirectUris();
if (redirectUris != null) {
resource.setRedirectUris(new HashSet<String>(redirectUris));
resource.setRedirectUris(new HashSet<>(redirectUris));
}
List<String> webOrigins = rep.getWebOrigins();
if (webOrigins != null) {
resource.setWebOrigins(new HashSet<String>(webOrigins));
resource.setWebOrigins(new HashSet<>(webOrigins));
}
if (rep.getRegisteredNodes() != null) {
@ -594,6 +596,31 @@ public class RepresentationToModel {
}
resource.updateClient();
if (!Objects.equals(newClientId, previousClientId)) {
ClientModel.ClientIdChangeEvent event = new ClientModel.ClientIdChangeEvent() {
@Override
public ClientModel getUpdatedClient() {
return resource;
}
@Override
public String getPreviousClientId() {
return previousClientId;
}
@Override
public String getNewClientId() {
return newClientId;
}
@Override
public KeycloakSession getKeycloakSession() {
return session;
}
};
session.getKeycloakSessionFactory().publish(event);
}
}
public static void updateClientProtocolMappers(ClientRepresentation rep, ClientModel resource) {

View file

@ -0,0 +1,77 @@
/*
* Copyright 2016 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.models;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.arrayWithSize;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import org.junit.Test;
import org.keycloak.models.utils.KeycloakModelUtils;
/**
* @author <a href="mailto:daniel.fesenmeyer@bosch.io">Daniel Fesenmeyer</a>
*/
public class KeycloakModelUtilsTest {
@Test
public void normalizeGroupPath() {
assertEquals("/test", KeycloakModelUtils.normalizeGroupPath("test"));
assertEquals("/test/x", KeycloakModelUtils.normalizeGroupPath("test/x/"));
assertEquals("", KeycloakModelUtils.normalizeGroupPath(""));
assertNull(KeycloakModelUtils.normalizeGroupPath(null));
}
@Test
public void buildRealmRoleQualifier() {
assertEquals("realm-role", KeycloakModelUtils.buildRoleQualifier(null, "realm-role"));
}
@Test
public void buildClientRoleQualifier() {
assertEquals("my.client.id.role-name",
KeycloakModelUtils.buildRoleQualifier("my.client.id", "role-name"));
}
@Test
public void parseRealmRoleQualifier() {
String[] clientIdAndRoleName = KeycloakModelUtils.parseRole("realm-role");
assertParsedRoleQualifier(clientIdAndRoleName, null, "realm-role");
}
@Test
public void parseClientRoleQualifier() {
String[] clientIdAndRoleName = KeycloakModelUtils.parseRole("my.client.id.role-name");
assertParsedRoleQualifier(clientIdAndRoleName, "my.client.id", "role-name");
}
private static void assertParsedRoleQualifier(String[] clientIdAndRoleName, String expectedClientId,
String expectedRoleName) {
assertThat(clientIdAndRoleName, arrayWithSize(2));
String clientId = clientIdAndRoleName[0];
assertEquals(expectedClientId, clientId);
String roleName = clientIdAndRoleName[1];
assertEquals(expectedRoleName, roleName);
}
}

View file

@ -66,6 +66,13 @@ public interface ClientModel extends ClientScopeModel, RoleContainerModel, Prot
KeycloakSession getKeycloakSession();
}
interface ClientIdChangeEvent extends ProviderEvent {
ClientModel getUpdatedClient();
String getPreviousClientId();
String getNewClientId();
KeycloakSession getKeycloakSession();
}
interface ClientRemovedEvent extends ProviderEvent {
ClientModel getClient();
KeycloakSession getKeycloakSession();

View file

@ -58,6 +58,13 @@ public interface GroupModel extends RoleMapperModel {
KeycloakSession getKeycloakSession();
}
interface GroupPathChangeEvent extends ProviderEvent {
RealmModel getRealm();
String getNewPath();
String getPreviousPath();
KeycloakSession getKeycloakSession();
}
Comparator<GroupModel> COMPARE_BY_NAME = Comparator.comparing(GroupModel::getName);
String getId();

View file

@ -17,6 +17,7 @@
package org.keycloak.models;
import org.keycloak.provider.ProviderEvent;
import org.keycloak.storage.SearchableModelField;
import java.util.List;
import java.util.Map;
@ -41,6 +42,18 @@ public interface RoleModel {
public static final SearchableModelField<RoleModel> COMPOSITE_ROLE = new SearchableModelField<>("compositeRoles", Boolean.class);
}
interface RoleNameChangeEvent extends ProviderEvent {
RealmModel getRealm();
String getNewName();
String getPreviousName();
/**
* @return the Client ID of the client, for a client role; {@code null}, for a realm role
*/
String getClientId();
KeycloakSession getKeycloakSession();
}
String getName();
String getDescription();

View file

@ -17,9 +17,9 @@
package org.keycloak.broker.oidc.mappers;
import org.jboss.logging.Logger;
import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.broker.provider.ConfigConstants;
import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.models.GroupModel;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.KeycloakSession;
@ -27,13 +27,24 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils;
/**
* @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(realm, mapperModel);
if (group == null) {
return;
}
if (applies(mapperModel, context)) {
user.joinGroup(group);
}
@ -44,8 +55,11 @@ public abstract class AbstractClaimToGroupMapper extends AbstractClaimMapper {
IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
GroupModel group = this.getGroup(realm, mapperModel);
String groupId = mapperModel.getConfig().get(ConfigConstants.GROUP);
if (group == null) {
return;
}
String groupId = group.getId();
if (!context.hasMapperAssignedGroup(groupId)) {
if (applies(mapperModel, context)) {
context.addMapperAssignedGroup(groupId);
@ -67,22 +81,16 @@ public abstract class AbstractClaimToGroupMapper extends AbstractClaimMapper {
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 exist, this method throws an {@link IdentityBrokerException}.
*
* @param realm a reference to the realm.
* @param mapperModel a reference to the {@link IdentityProviderMapperModel} containing the configured group.
* @return the {@link GroupModel}
* @throws IdentityBrokerException if the group doesn't exist.
*/
private GroupModel getGroup(final RealmModel realm, final IdentityProviderMapperModel mapperModel) {
GroupModel group = KeycloakModelUtils.findGroupByPath(realm, mapperModel.getConfig().get(ConfigConstants.GROUP));
String groupPath = mapperModel.getConfig().get(ConfigConstants.GROUP);
GroupModel group = KeycloakModelUtils.findGroupByPath(realm, groupPath);
if (group == null) {
throw new IdentityBrokerException("Unable to find group: " + group.getId());
LOG.warnf("Unable to find group by path '%s' referenced by mapper '%s' on realm '%s'.", groupPath,
mapperModel.getName(), realm.getName());
}
return group;
}
}

View file

@ -16,9 +16,9 @@
*/
package org.keycloak.broker.oidc.mappers;
import org.jboss.logging.Logger;
import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.broker.provider.ConfigConstants;
import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
@ -30,13 +30,20 @@ import org.keycloak.models.utils.KeycloakModelUtils;
* Abstract class that handles the logic for importing and updating brokered users for all mappers that map an OIDC
* claim into a {@code Keycloak} role.
*
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>,
* <a href="mailto:daniel.fesenmeyer@bosch.io">Daniel Fesenmeyer</a>
*/
public abstract class AbstractClaimToRoleMapper extends AbstractClaimMapper {
private static final Logger LOG = Logger.getLogger(AbstractClaimToRoleMapper.class);
@Override
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
RoleModel role = this.getRole(realm, mapperModel);
RoleModel role = getRole(realm, mapperModel);
if (role == null) {
return;
}
if (applies(mapperModel, context)) {
user.grantRole(role);
}
@ -44,7 +51,11 @@ public abstract class AbstractClaimToRoleMapper extends AbstractClaimMapper {
@Override
public void updateBrokeredUserLegacy(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
RoleModel role = this.getRole(realm, mapperModel);
RoleModel role = getRole(realm, mapperModel);
if (role == null) {
return;
}
if (!applies(mapperModel, context)) {
user.deleteRoleMapping(role);
}
@ -52,7 +63,11 @@ public abstract class AbstractClaimToRoleMapper extends AbstractClaimMapper {
@Override
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
RoleModel role = this.getRole(realm, mapperModel);
RoleModel role = getRole(realm, mapperModel);
if (role == null) {
return;
}
String roleName = mapperModel.getConfig().get(ConfigConstants.ROLE);
// KEYCLOAK-8730 if a previous mapper has already granted the same role, skip the checks so we don't accidentally remove a valid role.
if (!context.hasMapperGrantedRole(roleName)) {
@ -77,22 +92,24 @@ public abstract class AbstractClaimToRoleMapper extends AbstractClaimMapper {
protected abstract boolean applies(final IdentityProviderMapperModel mapperModel, final BrokeredIdentityContext context);
/**
* Obtains the {@link RoleModel} corresponding the role configured in the specified {@link IdentityProviderMapperModel}.
* If the role doesn't correspond to one of the realm's client roles or to one of the realm's roles, this method throws
* an {@link IdentityBrokerException} to convey that an invalid role was configured.
* Obtains the {@link RoleModel} corresponding the role configured in the specified
* {@link IdentityProviderMapperModel}.
* If the role doesn't correspond to one of the realm's client roles or to one of the realm's roles, this method
* returns {@code null}.
*
* @param realm a reference to the realm.
* @param mapperModel a reference to the {@link IdentityProviderMapperModel} containing the configured role.
* @return the {@link RoleModel} that corresponds to the mapper model role.
* @throws IdentityBrokerException if the role name doesn't correspond to one of the realm's client roles or to one
* of the realm's roles.
* @return the {@link RoleModel} that corresponds to the mapper model role; {@code null}, when role was not found
*/
private RoleModel getRole(final RealmModel realm, final IdentityProviderMapperModel mapperModel) {
String roleName = mapperModel.getConfig().get(ConfigConstants.ROLE);
RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName);
if (role == null) {
throw new IdentityBrokerException("Unable to find role: " + roleName);
LOG.warnf("Unable to find role '%s' referenced by mapper '%s' on realm '%s'.", roleName,
mapperModel.getName(), realm.getName());
}
return role;
}
}

View file

@ -26,11 +26,8 @@ import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.provider.ProviderConfigProperty;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.keycloak.utils.RegexUtils.valueMatchesRegex;

View file

@ -17,6 +17,7 @@
package org.keycloak.broker.provider;
import org.jboss.logging.Logger;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.models.KeycloakSession;
@ -37,8 +38,12 @@ import java.util.Set;
* @version $Revision: 1 $
*/
public class HardcodedRoleMapper extends AbstractIdentityProviderMapper {
protected static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
private static final Set<IdentityProviderSyncMode> IDENTITY_PROVIDER_SYNC_MODES = new HashSet<>(Arrays.asList(IdentityProviderSyncMode.values()));
protected static final List<ProviderConfigProperty> configProperties = new ArrayList<>();
private static final Logger LOG = Logger.getLogger(HardcodedRoleMapper.class);
private static final Set<IdentityProviderSyncMode> IDENTITY_PROVIDER_SYNC_MODES =
new HashSet<>(Arrays.asList(IdentityProviderSyncMode.values()));
static {
ProviderConfigProperty property;
@ -91,10 +96,22 @@ public class HardcodedRoleMapper extends AbstractIdentityProviderMapper {
}
private void grantUserRole(RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel) {
RoleModel role = getRole(realm, mapperModel);
if (role != null) {
user.grantRole(role);
}
}
private RoleModel getRole(final RealmModel realm, final IdentityProviderMapperModel mapperModel) {
String roleName = mapperModel.getConfig().get(ConfigConstants.ROLE);
RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName);
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
user.grantRole(role);
if (role == null) {
LOG.warnf("Unable to find role '%s' referenced by mapper '%s' on realm '%s'.", roleName,
mapperModel.getName(), realm.getName());
}
return role;
}
@Override

View file

@ -16,10 +16,10 @@
*/
package org.keycloak.broker.saml.mappers;
import org.jboss.logging.Logger;
import org.keycloak.broker.provider.AbstractIdentityProviderMapper;
import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.broker.provider.ConfigConstants;
import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
@ -31,13 +31,20 @@ import org.keycloak.models.utils.KeycloakModelUtils;
* Abstract class that handles the logic for importing and updating brokered users for all mappers that map a SAML
* attribute into a {@code Keycloak} role.
*
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>,
* <a href="mailto:daniel.fesenmeyer@bosch.io">Daniel Fesenmeyer</a>
*/
public abstract class AbstractAttributeToRoleMapper extends AbstractIdentityProviderMapper {
private static final Logger LOG = Logger.getLogger(AbstractAttributeToRoleMapper.class);
@Override
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
RoleModel role = this.getRole(realm, mapperModel);
if (role == null) {
return;
}
if (this.applies(mapperModel, context)) {
user.grantRole(role);
}
@ -46,6 +53,10 @@ public abstract class AbstractAttributeToRoleMapper extends AbstractIdentityProv
@Override
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
RoleModel role = this.getRole(realm, mapperModel);
if (role == null) {
return;
}
String roleName = mapperModel.getConfig().get(ConfigConstants.ROLE);
// KEYCLOAK-8730 if a previous mapper has already granted the same role, skip the checks so we don't accidentally remove a valid role.
if (!context.hasMapperGrantedRole(roleName)) {
@ -69,21 +80,22 @@ public abstract class AbstractAttributeToRoleMapper extends AbstractIdentityProv
protected abstract boolean applies(final IdentityProviderMapperModel mapperModel, final BrokeredIdentityContext context);
/**
* Obtains the {@link RoleModel} corresponding the role configured in the specified {@link IdentityProviderMapperModel}.
* If the role doesn't correspond to one of the realm's client roles or to one of the realm's roles, this method throws
* an {@link IdentityBrokerException} to convey that an invalid role was configured.
* Obtains the {@link RoleModel} corresponding the role configured in the specified
* {@link IdentityProviderMapperModel}.
* If the role doesn't correspond to one of the realm's client roles or to one of the realm's roles, this method
* returns {@code null}.
*
* @param realm a reference to the realm.
* @param mapperModel a reference to the {@link IdentityProviderMapperModel} containing the configured role.
* @return the {@link RoleModel} that corresponds to the mapper model role.
* @throws IdentityBrokerException if the role name doesn't correspond to one of the realm's client roles or to one
* of the realm's roles.
* @return the {@link RoleModel} that corresponds to the mapper model role or {@code null}, if the role could not be
* found
*/
private RoleModel getRole(final RealmModel realm, final IdentityProviderMapperModel mapperModel) {
String roleName = mapperModel.getConfig().get(ConfigConstants.ROLE);
RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName);
if (role == null) {
throw new IdentityBrokerException("Unable to find role: " + roleName);
LOG.warnf("Unable to find role '%s' for mapper '%s' on realm '%s'.", roleName, mapperModel.getName(),
realm.getName());
}
return role;
}

View file

@ -148,7 +148,7 @@ public abstract class AbstractClientRegistrationProvider implements ClientRegist
throw new ErrorResponseException(ErrorCodes.INVALID_CLIENT_METADATA, "Client Identifier modified", Response.Status.BAD_REQUEST);
}
RepresentationToModel.updateClient(rep, client);
RepresentationToModel.updateClient(rep, client, session);
RepresentationToModel.updateClientProtocolMappers(rep, client);
if (rep.getDefaultRoles() != null) {

View file

@ -777,7 +777,7 @@ public class ClientResource {
rep.setAuthorizationServicesEnabled(false);
}
RepresentationToModel.updateClient(rep, client);
RepresentationToModel.updateClient(rep, client, session);
RepresentationToModel.updateClientProtocolMappers(rep, client);
updateAuthorizationSettings(rep);
}

View file

@ -26,6 +26,7 @@ import org.keycloak.models.Constants;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.ManagementPermissionReference;
@ -114,7 +115,7 @@ public class GroupResource {
}
}
updateGroup(rep, group);
updateGroup(rep, group, realm, session);
adminEvent.operation(OperationType.UPDATE).resourcePath(session.getContext().getUri()).representation(rep).success();
return Response.noContent().build();
@ -167,7 +168,7 @@ public class GroupResource {
adminEvent.operation(OperationType.UPDATE);
} else {
child = realm.createGroup(groupName, group);
updateGroup(rep, child);
updateGroup(rep, child, realm, session);
URI uri = session.getContext().getUri().getBaseUriBuilder()
.path(session.getContext().getUri().getMatchedURIs().get(2))
.path(child.getId()).build();
@ -182,8 +183,42 @@ public class GroupResource {
return builder.type(MediaType.APPLICATION_JSON_TYPE).entity(childRep).build();
}
public static void updateGroup(GroupRepresentation rep, GroupModel model) {
if (rep.getName() != null) model.setName(rep.getName());
public static void updateGroup(GroupRepresentation rep, GroupModel model, RealmModel realm, KeycloakSession session) {
String newName = rep.getName();
if (newName != null) {
String existingName = model.getName();
if (!newName.equals(existingName)) {
String previousPath = KeycloakModelUtils.buildGroupPath(model);
model.setName(newName);
String newPath = KeycloakModelUtils.buildGroupPath(model);
GroupModel.GroupPathChangeEvent event =
new GroupModel.GroupPathChangeEvent() {
@Override
public RealmModel getRealm() {
return realm;
}
@Override
public String getNewPath() {
return newPath;
}
@Override
public String getPreviousPath() {
return previousPath;
}
@Override
public KeycloakSession getKeycloakSession() {
return session;
}
};
session.getKeycloakSessionFactory().publish(event);
}
}
if (rep.getAttributes() != null) {
Set<String> attrsToRemove = new HashSet<>(model.getAttributes().keySet());

View file

@ -163,7 +163,7 @@ public class GroupsResource {
adminEvent.operation(OperationType.UPDATE).resourcePath(session.getContext().getUri());
} else {
child = realm.createGroup(groupName);
GroupResource.updateGroup(rep, child);
GroupResource.updateGroup(rep, child, realm, session);
URI uri = session.getContext().getUri().getAbsolutePathBuilder()
.path(child.getId()).build();
builder.status(201).location(uri);

View file

@ -139,7 +139,7 @@ public class RoleByIdResource extends RoleResource {
public void updateRole(final @PathParam("role-id") String id, final RoleRepresentation rep) {
RoleModel role = getRoleModel(id);
auth.roles().requireManage(role);
updateRole(rep, role);
updateRole(rep, role, realm, session);
if (role.isClientRole()) {
adminEvent.resource(ResourceType.CLIENT_ROLE);

View file

@ -269,7 +269,7 @@ public class RoleContainerResource extends RoleResource {
throw new NotFoundException("Could not find role");
}
try {
updateRole(rep, role);
updateRole(rep, role, realm, session);
if (role.isClientRole()) {
adminEvent.resource(ResourceType.CLIENT_ROLE);

View file

@ -20,6 +20,7 @@ package org.keycloak.services.resources.admin;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.utils.ModelToRepresentation;
@ -57,8 +58,45 @@ public abstract class RoleResource {
}
}
protected void updateRole(RoleRepresentation rep, RoleModel role) {
role.setName(rep.getName());
protected void updateRole(RoleRepresentation rep, RoleModel role, RealmModel realm,
KeycloakSession session) {
String newName = rep.getName();
String previousName = role.getName();
if (!Objects.equals(previousName, newName)) {
role.setName(newName);
session.getKeycloakSessionFactory().publish(new RoleModel.RoleNameChangeEvent() {
@Override
public RealmModel getRealm() {
return realm;
}
@Override
public String getNewName() {
return newName;
}
@Override
public String getPreviousName() {
return previousName;
}
@Override
public String getClientId() {
if (!role.isClientRole()) {
return null;
}
return ((ClientModel) role.getContainer()).getClientId();
}
@Override
public KeycloakSession getKeycloakSession() {
return session;
}
});
}
role.setDescription(rep.getDescription());
if (rep.getAttributes() != null) {

View file

@ -3,10 +3,8 @@ package org.keycloak.testsuite.broker;
import static org.keycloak.models.IdentityProviderMapperSyncMode.FORCE;
import static org.keycloak.models.IdentityProviderMapperSyncMode.IMPORT;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
@ -14,7 +12,12 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:artur.baltabayev@bosch.io">Artur Baltabayev</a>,
* <a href="mailto:daniel.fesenmeyer@bosch.io">Daniel Fesenmeyer</a>
*/
public abstract class AbstractAdvancedGroupMapperTest extends AbstractGroupMapperTest {
private static final String CLAIMS_OR_ATTRIBUTES = "[\n" +
@ -41,24 +44,12 @@ public abstract class AbstractAdvancedGroupMapperTest extends AbstractGroupMappe
private String newValueForAttribute2 = "";
@Before
public void addMapperTestGroupToConsumerRealm() {
GroupRepresentation mapperTestGroup = new GroupRepresentation();
mapperTestGroup.setName(MAPPER_TEST_GROUP_NAME);
mapperTestGroup.setPath(MAPPER_TEST_GROUP_PATH);
adminClient.realm(bc.consumerRealmName()).groups().add(mapperTestGroup);
}
@Test
public void allValuesMatch() {
createAdvancedGroupMapper(CLAIMS_OR_ATTRIBUTES, false);
createUserInProviderRealm(ImmutableMap.<String, List<String>>builder()
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("value 1").build())
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2, ImmutableList.<String>builder().add("value 2").build())
.build());
createAdvancedGroupMapper(CLAIMS_OR_ATTRIBUTES, false, MAPPER_TEST_GROUP_PATH);
createUserInProviderRealm(createMatchingAttributes());
logInAsUserInIDPForFirstTime();
logInAsUserInIDPForFirstTimeAndAssertSuccess();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
assertThatUserHasBeenAssignedToGroup(user);
@ -66,13 +57,13 @@ public abstract class AbstractAdvancedGroupMapperTest extends AbstractGroupMappe
@Test
public void valuesMismatch() {
createAdvancedGroupMapper(CLAIMS_OR_ATTRIBUTES, false);
createAdvancedGroupMapper(CLAIMS_OR_ATTRIBUTES, false, MAPPER_TEST_GROUP_PATH);
createUserInProviderRealm(ImmutableMap.<String, List<String>>builder()
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("value 1").build())
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2, ImmutableList.<String>builder().add("value mismatch").build())
.build());
logInAsUserInIDPForFirstTime();
logInAsUserInIDPForFirstTimeAndAssertSuccess();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
assertThatUserHasNotBeenAssignedToGroup(user);
@ -80,13 +71,13 @@ public abstract class AbstractAdvancedGroupMapperTest extends AbstractGroupMappe
@Test
public void valuesMatchIfNoClaimsSpecified() {
createAdvancedGroupMapper("[]", false);
createAdvancedGroupMapper("[]", false, MAPPER_TEST_GROUP_PATH);
createUserInProviderRealm(ImmutableMap.<String, List<String>>builder()
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("some value").build())
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2, ImmutableList.<String>builder().add("some value").build())
.build());
logInAsUserInIDPForFirstTime();
logInAsUserInIDPForFirstTimeAndAssertSuccess();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
assertThatUserHasBeenAssignedToGroup(user);
@ -94,13 +85,10 @@ public abstract class AbstractAdvancedGroupMapperTest extends AbstractGroupMappe
@Test
public void allValuesMatchRegex() {
createAdvancedGroupMapper(CLAIMS_OR_ATTRIBUTES_REGEX, true);
createUserInProviderRealm(ImmutableMap.<String, List<String>>builder()
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("value 1").build())
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2, ImmutableList.<String>builder().add("value 2").build())
.build());
createAdvancedGroupMapper(CLAIMS_OR_ATTRIBUTES_REGEX, true, MAPPER_TEST_GROUP_PATH);
createUserInProviderRealm(createMatchingAttributes());
logInAsUserInIDPForFirstTime();
logInAsUserInIDPForFirstTimeAndAssertSuccess();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
assertThatUserHasBeenAssignedToGroup(user);
@ -108,13 +96,13 @@ public abstract class AbstractAdvancedGroupMapperTest extends AbstractGroupMappe
@Test
public void valuesMismatchRegex() {
createAdvancedGroupMapper(CLAIMS_OR_ATTRIBUTES_REGEX, true);
createAdvancedGroupMapper(CLAIMS_OR_ATTRIBUTES_REGEX, true, MAPPER_TEST_GROUP_PATH);
createUserInProviderRealm(ImmutableMap.<String, List<String>>builder()
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("mismatch").build())
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2, ImmutableList.<String>builder().add("value 2").build())
.build());
logInAsUserInIDPForFirstTime();
logInAsUserInIDPForFirstTimeAndAssertSuccess();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
assertThatUserHasNotBeenAssignedToGroup(user);
@ -123,7 +111,7 @@ public abstract class AbstractAdvancedGroupMapperTest extends AbstractGroupMappe
@Test
public void updateBrokeredUserMismatchLeavesGroup() {
newValueForAttribute2 = "value mismatch";
UserRepresentation user = createMapperAndLoginAsUserTwiceWithMapper(FORCE, false);
UserRepresentation user = createMapperAndLoginAsUserTwiceWithMapper(FORCE, false, MAPPER_TEST_GROUP_PATH);
assertThatUserHasNotBeenAssignedToGroup(user);
}
@ -131,7 +119,7 @@ public abstract class AbstractAdvancedGroupMapperTest extends AbstractGroupMappe
@Test
public void updateBrokeredUserMismatchDoesNotLeaveGroupInImportMode() {
newValueForAttribute2 = "value mismatch";
UserRepresentation user = createMapperAndLoginAsUserTwiceWithMapper(IMPORT, false);
UserRepresentation user = createMapperAndLoginAsUserTwiceWithMapper(IMPORT, false, MAPPER_TEST_GROUP_PATH);
assertThatUserHasBeenAssignedToGroup(user);
}
@ -139,24 +127,31 @@ public abstract class AbstractAdvancedGroupMapperTest extends AbstractGroupMappe
@Test
public void updateBrokeredUserMatchDoesntLeaveGroup() {
newValueForAttribute2 = "value 2";
UserRepresentation user = createMapperAndLoginAsUserTwiceWithMapper(FORCE, false);
UserRepresentation user = createMapperAndLoginAsUserTwiceWithMapper(FORCE, false, MAPPER_TEST_GROUP_PATH);
assertThatUserHasBeenAssignedToGroup(user);
}
@Test
public void tryToUpdateBrokeredUserWithMissingGroupDoesNotBreakLogin() {
newValueForAttribute2 = "value 2";
UserRepresentation user =
createMapperAndLoginAsUserTwiceWithMapper(FORCE, true, MAPPER_TEST_NOT_EXISTING_GROUP_PATH);
assertThatUserDoesNotHaveGroups(user);
}
@Test
public void updateBrokeredUserIsAssignedToGroupInForceModeWhenCreatingTheMapperAfterFirstLogin() {
newValueForAttribute2 = "value 2";
UserRepresentation user = createMapperAndLoginAsUserTwiceWithMapper(FORCE, true);
UserRepresentation user = createMapperAndLoginAsUserTwiceWithMapper(FORCE, true, MAPPER_TEST_GROUP_PATH);
assertThatUserHasBeenAssignedToGroup(user);
}
public UserRepresentation createMapperAndLoginAsUserTwiceWithMapper(IdentityProviderMapperSyncMode syncMode, boolean createAfterFirstLogin) {
return loginAsUserTwiceWithMapper(syncMode, createAfterFirstLogin, ImmutableMap.<String, List<String>>builder()
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("value 1").build())
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2, ImmutableList.<String>builder().add("value 2").build())
.build());
public UserRepresentation createMapperAndLoginAsUserTwiceWithMapper(IdentityProviderMapperSyncMode syncMode,
boolean createAfterFirstLogin, String groupPath) {
return loginAsUserTwiceWithMapper(syncMode, createAfterFirstLogin, createMatchingAttributes(), groupPath);
}
@Override
@ -171,16 +166,44 @@ public abstract class AbstractAdvancedGroupMapperTest extends AbstractGroupMappe
adminClient.realm(bc.providerRealmName()).users().get(user.getId()).update(user);
}
@Override
protected void createMapperInIdp(IdentityProviderRepresentation idp, IdentityProviderMapperSyncMode syncMode) {
createMapperInIdp(idp, CLAIMS_OR_ATTRIBUTES, false, syncMode);
protected String createMapperInIdp(IdentityProviderRepresentation idp, IdentityProviderMapperSyncMode syncMode,
String groupPath) {
return createMapperInIdp(idp, CLAIMS_OR_ATTRIBUTES, false, syncMode, groupPath);
}
protected void createAdvancedGroupMapper(String claimsOrAttributeRepresentation, boolean areClaimsOrAttributeValuesRegexes) {
@Override
protected String setupScenarioWithMatchingGroup() {
String mapperId = createAdvancedGroupMapper(CLAIMS_OR_ATTRIBUTES, false, MAPPER_TEST_GROUP_PATH);
createUserInProviderRealm(createMatchingAttributes());
return mapperId;
}
@Override
protected void setupScenarioWithNonExistingGroup() {
createAdvancedGroupMapper(CLAIMS_OR_ATTRIBUTES, false, MAPPER_TEST_NOT_EXISTING_GROUP_PATH);
createUserInProviderRealm(createMatchingAttributes());
}
protected String createAdvancedGroupMapper(String claimsOrAttributeRepresentation,
boolean areClaimsOrAttributeValuesRegexes, String groupPath) {
IdentityProviderRepresentation idp = setupIdentityProvider();
createMapperInIdp(idp, claimsOrAttributeRepresentation, areClaimsOrAttributeValuesRegexes, IMPORT);
return createMapperInIdp(idp, claimsOrAttributeRepresentation, areClaimsOrAttributeValuesRegexes, IMPORT,
groupPath);
}
abstract protected void createMapperInIdp(
IdentityProviderRepresentation idp, String claimsOrAttributeRepresentation, boolean areClaimsOrAttributeValuesRegexes, IdentityProviderMapperSyncMode syncMode);
abstract protected String createMapperInIdp(
IdentityProviderRepresentation idp, String claimsOrAttributeRepresentation,
boolean areClaimsOrAttributeValuesRegexes, IdentityProviderMapperSyncMode syncMode, String groupPath);
private static Map<String, List<String>> createMatchingAttributes() {
return ImmutableMap.<String, List<String>> builder()
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME,
ImmutableList.<String> builder().add("value 1").build())
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2,
ImmutableList.<String> builder().add("value 2").build())
.build();
}
}

View file

@ -1,21 +1,23 @@
package org.keycloak.testsuite.broker;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.junit.Test;
import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import java.util.List;
import static org.keycloak.models.IdentityProviderMapperSyncMode.FORCE;
import static org.keycloak.models.IdentityProviderMapperSyncMode.IMPORT;
import org.junit.Test;
import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.representations.idm.UserRepresentation;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.util.List;
import java.util.Map;
/**
* @author hmlnarik,
* <a href="mailto:external.benjamin.weimer@bosch-si.com">Benjamin Weimer</a>,
* <a href="mailto:external.martin.idel@bosch.io">Martin Idel</a>
* <a href="mailto:external.martin.idel@bosch.io">Martin Idel</a>,
* <a href="mailto:daniel.fesenmeyer@bosch.io">Daniel Fesenmeyer</a>
*/
public abstract class AbstractAdvancedRoleMapperTest extends AbstractRoleMapperTest {
@ -46,134 +48,145 @@ public abstract class AbstractAdvancedRoleMapperTest extends AbstractRoleMapperT
@Test
public void allValuesMatch() {
createAdvancedRoleMapper(CLAIMS_OR_ATTRIBUTES, false);
createUserInProviderRealm(ImmutableMap.<String, List<String>>builder()
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("value 1").build())
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2, ImmutableList.<String>builder().add("value 2").build())
.build());
createUserInProviderRealm(createMatchingUserConfig());
logInAsUserInIDPForFirstTime();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
assertThatRoleHasBeenAssignedInConsumerRealmTo(user);
assertThatRoleHasBeenAssignedInConsumerRealm();
}
@Test
public void valuesMismatch() {
createAdvancedRoleMapper(CLAIMS_OR_ATTRIBUTES, false);
createUserInProviderRealm(ImmutableMap.<String, List<String>>builder()
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("value 1").build())
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2, ImmutableList.<String>builder().add("value mismatch").build())
createUserInProviderRealm(ImmutableMap.<String, List<String>> builder()
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME,
ImmutableList.<String> builder().add("value 1").build())
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2,
ImmutableList.<String> builder().add("value mismatch").build())
.build());
logInAsUserInIDPForFirstTime();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
assertThatRoleHasNotBeenAssignedInConsumerRealmTo(user);
assertThatRoleHasNotBeenAssignedInConsumerRealm();
}
@Test
public void valuesMatchIfNoClaimsSpecified() {
createAdvancedRoleMapper("[]", false);
createUserInProviderRealm(ImmutableMap.<String, List<String>>builder()
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("some value").build())
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2, ImmutableList.<String>builder().add("some value").build())
createUserInProviderRealm(ImmutableMap.<String, List<String>> builder()
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME,
ImmutableList.<String> builder().add("some value").build())
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2,
ImmutableList.<String> builder().add("some value").build())
.build());
logInAsUserInIDPForFirstTime();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
assertThatRoleHasBeenAssignedInConsumerRealmTo(user);
assertThatRoleHasBeenAssignedInConsumerRealm();
}
@Test
public void allValuesMatchRegex() {
createAdvancedRoleMapper(CLAIMS_OR_ATTRIBUTES_REGEX, true);
createUserInProviderRealm(ImmutableMap.<String, List<String>>builder()
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("value 1").build())
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2, ImmutableList.<String>builder().add("value 2").build())
.build());
createUserInProviderRealm(createMatchingUserConfig());
logInAsUserInIDPForFirstTime();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
assertThatRoleHasBeenAssignedInConsumerRealmTo(user);
assertThatRoleHasBeenAssignedInConsumerRealm();
}
@Test
public void valuesMismatchRegex() {
createAdvancedRoleMapper(CLAIMS_OR_ATTRIBUTES_REGEX, true);
createUserInProviderRealm(ImmutableMap.<String, List<String>>builder()
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("mismatch").build())
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2, ImmutableList.<String>builder().add("value 2").build())
createUserInProviderRealm(ImmutableMap.<String, List<String>> builder()
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME,
ImmutableList.<String> builder().add("mismatch").build())
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2,
ImmutableList.<String> builder().add("value 2").build())
.build());
logInAsUserInIDPForFirstTime();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
assertThatRoleHasNotBeenAssignedInConsumerRealmTo(user);
assertThatRoleHasNotBeenAssignedInConsumerRealm();
}
@Test
public void updateBrokeredUserMismatchDeletesRole() {
newValueForAttribute2 = "value mismatch";
UserRepresentation user = createMapperAndLoginAsUserTwiceWithMapper(FORCE, false);
createMapperAndLoginAsUserTwiceWithMapper(FORCE, false);
assertThatRoleHasNotBeenAssignedInConsumerRealmTo(user);
assertThatRoleHasNotBeenAssignedInConsumerRealm();
}
@Test
public void updateBrokeredUserMismatchDoesNotDeleteRoleInImportMode() {
newValueForAttribute2 = "value mismatch";
UserRepresentation user = createMapperAndLoginAsUserTwiceWithMapper(IMPORT, false);
createMapperAndLoginAsUserTwiceWithMapper(IMPORT, false);
assertThatRoleHasBeenAssignedInConsumerRealmTo(user);
assertThatRoleHasBeenAssignedInConsumerRealm();
}
@Test
public void updateBrokeredUserMatchDoesntDeleteRole() {
newValueForAttribute2 = "value 2";
UserRepresentation user = createMapperAndLoginAsUserTwiceWithMapper(FORCE, false);
createMapperAndLoginAsUserTwiceWithMapper(FORCE, false);
assertThatRoleHasBeenAssignedInConsumerRealmTo(user);
assertThatRoleHasBeenAssignedInConsumerRealm();
}
@Test
public void updateBrokeredUserAssignsRoleInForceModeWhenCreatingTheMapperAfterFirstLogin() {
newValueForAttribute2 = "value 2";
UserRepresentation user = createMapperAndLoginAsUserTwiceWithMapper(FORCE, true);
createMapperAndLoginAsUserTwiceWithMapper(FORCE, true);
assertThatRoleHasBeenAssignedInConsumerRealmTo(user);
assertThatRoleHasBeenAssignedInConsumerRealm();
}
public UserRepresentation createMapperAndLoginAsUserTwiceWithMapper(IdentityProviderMapperSyncMode syncMode, boolean createAfterFirstLogin) {
return loginAsUserTwiceWithMapper(syncMode, createAfterFirstLogin, ImmutableMap.<String, List<String>>builder()
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("value 1").build())
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2, ImmutableList.<String>builder().add("value 2").build())
.build());
public void createMapperAndLoginAsUserTwiceWithMapper(IdentityProviderMapperSyncMode syncMode,
boolean createAfterFirstLogin) {
loginAsUserTwiceWithMapper(syncMode, createAfterFirstLogin, createMatchingUserConfig());
}
@Override
protected void updateUser() {
UserRepresentation user = findUser(bc.providerRealmName(), bc.getUserLogin(), bc.getUserEmail());
ImmutableMap<String, List<String>> matchingAttributes = ImmutableMap.<String, List<String>>builder()
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("value 1").build())
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2, ImmutableList.<String>builder().add(newValueForAttribute2).build())
.put("some.other.attribute", ImmutableList.<String>builder().add("some value").build())
ImmutableMap<String, List<String>> matchingAttributes = ImmutableMap.<String, List<String>> builder()
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME,
ImmutableList.<String> builder().add("value 1").build())
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2,
ImmutableList.<String> builder().add(newValueForAttribute2).build())
.put("some.other.attribute", ImmutableList.<String> builder().add("some value").build())
.build();
user.setAttributes(matchingAttributes);
adminClient.realm(bc.providerRealmName()).users().get(user.getId()).update(user);
}
@Override
protected void createMapperInIdp(IdentityProviderRepresentation idp, IdentityProviderMapperSyncMode syncMode) {
createMapperInIdp(idp, CLAIMS_OR_ATTRIBUTES, false, syncMode);
protected void createMapperInIdp(IdentityProviderMapperSyncMode syncMode, String roleValue) {
createMapperInIdp(CLAIMS_OR_ATTRIBUTES, false, syncMode, roleValue);
}
protected void createAdvancedRoleMapper(String claimsOrAttributeRepresentation, boolean areClaimsOrAttributeValuesRegexes) {
IdentityProviderRepresentation idp = setupIdentityProvider();
createMapperInIdp(idp, claimsOrAttributeRepresentation, areClaimsOrAttributeValuesRegexes, IMPORT);
@Override
protected Map<String, List<String>> createUserConfigForRole(String roleValue) {
return createMatchingUserConfig();
}
abstract protected void createMapperInIdp(
IdentityProviderRepresentation idp, String claimsOrAttributeRepresentation, boolean areClaimsOrAttributeValuesRegexes, IdentityProviderMapperSyncMode syncMode);
private static Map<String, List<String>> createMatchingUserConfig() {
return ImmutableMap.<String, List<String>> builder()
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME,
ImmutableList.<String> builder().add("value 1").build())
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2,
ImmutableList.<String> builder().add("value 2").build())
.build();
}
protected void createAdvancedRoleMapper(String claimsOrAttributeRepresentation,
boolean areClaimsOrAttributeValuesRegexes) {
setupIdentityProvider();
createMapperInIdp(claimsOrAttributeRepresentation, areClaimsOrAttributeValuesRegexes, IMPORT,
CLIENT_ROLE_MAPPER_REPRESENTATION);
}
abstract protected void createMapperInIdp(String claimsOrAttributeRepresentation,
boolean areClaimsOrAttributeValuesRegexes, IdentityProviderMapperSyncMode syncMode, String roleValue);
}

View file

@ -265,6 +265,11 @@ public abstract class AbstractBaseBrokerTest extends AbstractKeycloakTest {
updateAccountInformation();
}
protected void logInAsUserInIDPForFirstTimeAndAssertSuccess() {
logInAsUserInIDPForFirstTime();
assertLoggedInAccountManagement();
}
protected void updateAccountInformation() {
waitForPage(driver, "update account information", false);

View file

@ -1,38 +1,134 @@
package org.keycloak.testsuite.broker;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;
import static org.keycloak.testsuite.broker.BrokerTestTools.getConsumerRoot;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.admin.client.CreatedResponseUtil;
import org.keycloak.broker.provider.ConfigConstants;
import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.ws.rs.core.Response;
/**
* @author <a href="mailto:artur.baltabayev@bosch.io">Artur Baltabayev</a>,
* <a href="mailto:daniel.fesenmeyer@bosch.io">Daniel Fesenmeyer</a>
*/
public abstract class AbstractGroupMapperTest extends AbstractIdentityProviderMapperTest {
public static final String MAPPER_TEST_GROUP_NAME = "mapper-test";
public static final String MAPPER_TEST_GROUP_PATH = "/" + MAPPER_TEST_GROUP_NAME;
public static final String MAPPER_TEST_GROUP_PATH = buildGroupPath(MAPPER_TEST_GROUP_NAME);
protected abstract void createMapperInIdp(
IdentityProviderRepresentation idp, IdentityProviderMapperSyncMode syncMode);
public static final String MAPPER_TEST_NOT_EXISTING_GROUP_PATH = buildGroupPath("mapper-test-not-existing");
protected String mapperGroupId;
protected abstract String createMapperInIdp(
IdentityProviderRepresentation idp, IdentityProviderMapperSyncMode syncMode, String groupPath);
/**
* Sets up a scenario with a matching group.
* @return the ID of the mapper
*/
protected abstract String setupScenarioWithMatchingGroup();
protected abstract void setupScenarioWithNonExistingGroup();
protected void updateUser() {
}
@Before
public void addMapperTestGroupToConsumerRealm() {
GroupRepresentation mapperTestGroup = new GroupRepresentation();
mapperTestGroup.setName(MAPPER_TEST_GROUP_NAME);
Response response = adminClient.realm(bc.consumerRealmName()).groups().add(mapperTestGroup);
mapperGroupId = CreatedResponseUtil.getCreatedId(response);
}
@Test
public void tryToCreateBrokeredUserWithNonExistingGroupDoesNotBreakLogin() {
setupScenarioWithNonExistingGroup();
logInAsUserInIDPForFirstTimeAndAssertSuccess();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
assertThatUserDoesNotHaveGroups(user);
}
@Test
public void mapperStillWorksWhenGroupIsMoved() {
final String mapperId = setupScenarioWithMatchingGroup();
String newParentGroupName = "new-parent";
GroupRepresentation newParentGroup = new GroupRepresentation();
newParentGroup.setName(newParentGroupName);
String newParentGroupId = CreatedResponseUtil.getCreatedId(realm.groups().add(newParentGroup));
GroupRepresentation mappedGroup = realm.groups().group(mapperGroupId).toRepresentation();
realm.groups().group(newParentGroupId).subGroup(mappedGroup).close();
String expectedNewGroupPath = buildGroupPath(newParentGroupName, MAPPER_TEST_GROUP_NAME);
// mapper should have been updated to the new path of the group
IdentityProviderMapperRepresentation mapper =
realm.identityProviders().get(bc.getIDPAlias()).getMapperById(mapperId);
Map<String, String> config = mapper.getConfig();
assertThat(config.get(ConfigConstants.GROUP), equalTo(expectedNewGroupPath));
logInAsUserInIDPForFirstTimeAndAssertSuccess();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
assertThatUserHasBeenAssignedToGroup(user, expectedNewGroupPath);
}
@Test
public void mapperStillWorksWhenGroupIsRenamed() {
final String mapperId = setupScenarioWithMatchingGroup();
String newGroupName = "new-name-" + MAPPER_TEST_GROUP_NAME;
GroupRepresentation mappedGroup = realm.groups().group(mapperGroupId).toRepresentation();
mappedGroup.setName(newGroupName);
realm.groups().group(mapperGroupId).update(mappedGroup);
String expectedNewGroupPath = buildGroupPath(newGroupName);
// mapper should have been updated to the new path of the group
IdentityProviderMapperRepresentation mapper =
realm.identityProviders().get(bc.getIDPAlias()).getMapperById(mapperId);
Map<String, String> config = mapper.getConfig();
assertThat(config.get(ConfigConstants.GROUP), equalTo(expectedNewGroupPath));
logInAsUserInIDPForFirstTimeAndAssertSuccess();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
assertThatUserHasBeenAssignedToGroup(user, expectedNewGroupPath);
}
protected UserRepresentation loginAsUserTwiceWithMapper(
IdentityProviderMapperSyncMode syncMode, boolean createAfterFirstLogin,
Map<String, List<String>> userConfig) {
Map<String, List<String>> userConfig, String groupPath) {
final IdentityProviderRepresentation idp = setupIdentityProvider();
if (!createAfterFirstLogin) {
createMapperInIdp(idp, syncMode);
createMapperInIdp(idp, syncMode, groupPath);
}
createUserInProviderRealm(userConfig);
logInAsUserInIDPForFirstTime();
logInAsUserInIDPForFirstTimeAndAssertSuccess();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
if (!createAfterFirstLogin) {
@ -42,34 +138,46 @@ public abstract class AbstractGroupMapperTest extends AbstractIdentityProviderMa
}
if (createAfterFirstLogin) {
createMapperInIdp(idp, syncMode);
createMapperInIdp(idp, syncMode, groupPath);
}
logoutFromRealm(getConsumerRoot(), bc.consumerRealmName());
updateUser();
logInAsUserInIDP();
assertLoggedInAccountManagement();
user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
return user;
}
protected void assertThatUserHasBeenAssignedToGroup(UserRepresentation user) {
List<String> groupNames = new ArrayList<>();
assertThatUserHasBeenAssignedToGroup(user, MAPPER_TEST_GROUP_PATH);
}
realm.users().get(user.getId()).groups().forEach(group -> {
groupNames.add(group.getName());
});
assertTrue(groupNames.contains(MAPPER_TEST_GROUP_NAME));
protected void assertThatUserHasBeenAssignedToGroup(UserRepresentation user, String groupPath) {
assertThat(getUserGroupPaths(user), contains(groupPath));
}
protected void assertThatUserHasNotBeenAssignedToGroup(UserRepresentation user) {
List<String> groupNames = new ArrayList<>();
assertThat(getUserGroupPaths(user), not(contains(MAPPER_TEST_GROUP_PATH)));
}
realm.users().get(user.getId()).groups().forEach(group -> {
groupNames.add(group.getName());
});
protected void assertThatUserDoesNotHaveGroups(UserRepresentation user) {
assertThat(getUserGroupPaths(user), empty());
}
assertFalse(groupNames.contains(MAPPER_TEST_GROUP_NAME));
protected static String buildGroupPath(String firstSegment, String... furtherSegments) {
String separator = KeycloakModelUtils.GROUP_PATH_SEPARATOR;
StringBuilder sb = new StringBuilder(separator).append(firstSegment);
for (String furtherSegment : furtherSegments) {
sb.append(separator).append(furtherSegment);
}
return sb.toString();
}
private List<String> getUserGroupPaths(UserRepresentation user) {
return realm.users().get(user.getId()).groups().stream().map(GroupRepresentation::getPath)
.collect(Collectors.toList());
}
}

View file

@ -1,82 +1,269 @@
package org.keycloak.testsuite.broker;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.aMapWithSize;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasEntry;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.in;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertThat;
import static org.keycloak.testsuite.broker.BrokerTestTools.getConsumerRoot;
import static org.keycloak.testsuite.util.WaitUtils.pause;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.admin.client.CreatedResponseUtil;
import org.keycloak.admin.client.resource.IdentityProviderResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.broker.provider.ConfigConstants;
import org.keycloak.models.Constants;
import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.util.ClientBuilder;
import org.keycloak.testsuite.util.RoleBuilder;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.openqa.selenium.firefox.FirefoxDriver;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author hmlnarik,
* <a href="mailto:external.benjamin.weimer@bosch-si.com">Benjamin Weimer</a>,
* <a href="mailto:external.martin.idel@bosch.io">Martin Idel</a>,
* <a href="mailto:daniel.fesenmeyer@bosch.io">Daniel Fesenmeyer</a>
*/
public abstract class AbstractRoleMapperTest extends AbstractIdentityProviderMapperTest {
private static final String CLIENT = "realm-management";
private static final String CLIENT_ROLE = "view-realm";
public static final String ROLE_USER = "user";
public static final String CLIENT_ROLE_MAPPER_REPRESENTATION = CLIENT + "." + CLIENT_ROLE;
protected static final String CLIENT_ID = "mapper-test-client";
protected static final String CLIENT_ROLE = "test-role";
protected static final String CLIENT_ROLE_MAPPER_REPRESENTATION = createClientRoleString(CLIENT_ID, CLIENT_ROLE);
protected static final String ROLE_USER = "user";
protected abstract void createMapperInIdp(IdentityProviderRepresentation idp, IdentityProviderMapperSyncMode syncMode);
private static final String REALM_ROLE = "test-realm-role";
protected String clientUuid;
private Map<String, String> mapperIdsWithName;
protected static String createClientRoleString(final String clientId, final String roleName) {
return clientId + "." + roleName;
}
protected abstract void createMapperInIdp(IdentityProviderMapperSyncMode syncMode, String roleValue);
protected abstract Map<String, List<String>> createUserConfigForRole(String roleValue);
protected void updateUser() {
}
protected UserRepresentation loginAsUserTwiceWithMapper(
IdentityProviderMapperSyncMode syncMode, boolean createAfterFirstLogin, Map<String, List<String>> userConfig) {
final IdentityProviderRepresentation idp = setupIdentityProvider();
if (!createAfterFirstLogin) {
createMapperInIdp(idp, syncMode);
@Before
public void init() {
mapperIdsWithName = new ConcurrentHashMap<>();
RealmResource consumerRealmResource = adminClient.realm(bc.consumerRealmName());
clientUuid = CreatedResponseUtil.getCreatedId(
consumerRealmResource.clients().create(ClientBuilder.create().clientId(CLIENT_ID).build()));
consumerRealmResource.clients().get(clientUuid).roles().create(RoleBuilder.create().name(CLIENT_ROLE).build());
consumerRealmResource.roles().create(RoleBuilder.create().name(REALM_ROLE).build());
}
createUserInProviderRealm(userConfig);
createUserRoleAndGrantToUserInProviderRealm();
@Test
public void tryToCreateBrokeredUserWithNonExistingClientRoleDoesNotBreakLogin() {
String clientRoleStringWithMissingRole = createClientRoleString(CLIENT_ID, "does-not-exist");
setup(clientRoleStringWithMissingRole);
assertLoginSucceedsWithoutRoleAssignment();
}
/**
* This test checks that the mapper can also be applied to realm roles (other tests mostly use client roles).
*/
@Test
public void mapperCanBeAppliedToRealmRoles() {
setup(REALM_ROLE);
logInAsUserInIDPForFirstTimeAndAssertSuccess();
assertThatRoleHasBeenAssignedInConsumerRealm(REALM_ROLE);
}
@Test
public void mapperStillWorksWhenClientRoleIsRenamed() {
setup(CLIENT_ROLE_MAPPER_REPRESENTATION);
String newRoleName = "new-name-" + CLIENT_ROLE;
RoleRepresentation mappedRole = realm.clients().get(clientUuid).roles().get(CLIENT_ROLE).toRepresentation();
mappedRole.setName(newRoleName);
realm.clients().get(clientUuid).roles().get(CLIENT_ROLE).update(mappedRole);
String expectedNewClientRoleName = createClientRoleString(CLIENT_ID, newRoleName);
// mapper(s) should have been updated to the new client role name
assertMappersAreConfiguredWithRole(expectedNewClientRoleName);
logInAsUserInIDPForFirstTimeAndAssertSuccess();
assertThatRoleHasBeenAssignedInConsumerRealm(CLIENT_ID, newRoleName);
}
@Test
public void mapperStillWorksWhenClientIdIsChanged() {
setup(CLIENT_ROLE_MAPPER_REPRESENTATION);
String newClientId = "new-name-" + CLIENT_ID;
ClientRepresentation mappedClient = realm.clients().get(clientUuid).toRepresentation();
mappedClient.setClientId(newClientId);
realm.clients().get(clientUuid).update(mappedClient);
String expectedNewClientRoleName = createClientRoleString(newClientId, CLIENT_ROLE);
// mapper(s) should have been updated to the new client role name
assertMappersAreConfiguredWithRole(expectedNewClientRoleName);
logInAsUserInIDPForFirstTimeAndAssertSuccess();
assertThatRoleHasBeenAssignedInConsumerRealm(newClientId, CLIENT_ROLE);
}
@Test
public void mapperStillWorksWhenRealmRoleIsRenamed() {
setup(REALM_ROLE);
String newRoleName = "new-name-" + REALM_ROLE;
RoleRepresentation mappedRole = realm.roles().get(REALM_ROLE).toRepresentation();
mappedRole.setName(newRoleName);
realm.roles().get(REALM_ROLE).update(mappedRole);
// mapper(s) should have been updated to the new realm role name
assertMappersAreConfiguredWithRole(newRoleName);
logInAsUserInIDPForFirstTimeAndAssertSuccess();
assertThatRoleHasBeenAssignedInConsumerRealm(newRoleName);
}
private void assertMappersAreConfiguredWithRole(String expectedRoleQualifier) {
mapperIdsWithName.forEach((mapperId, mapperName) -> {
try {
IdentityProviderMapperRepresentation mapper =
realm.identityProviders().get(bc.getIDPAlias()).getMapperById(mapperId);
Map<String, String> config = mapper.getConfig();
assertThat(config.get(ConfigConstants.ROLE), equalTo(expectedRoleQualifier));
} catch (final AssertionError | RuntimeException t) {
throw new AssertionError(
String.format(
"Failed assertion for mapper with ID '%s' and name '%s'. See the cause for details.",
mapperId, mapperName),
t);
}
});
}
protected final void persistMapper(IdentityProviderMapperRepresentation idpMapper) {
String idpAlias = bc.getIDPAlias();
IdentityProviderResource idpResource = realm.identityProviders().get(idpAlias);
idpMapper.setIdentityProviderAlias(idpAlias);
String mapperId = CreatedResponseUtil.getCreatedId(idpResource.addMapper(idpMapper));
mapperIdsWithName.put(mapperId, idpMapper.getName());
}
protected void loginAsUserTwiceWithMapper(IdentityProviderMapperSyncMode syncMode, boolean createAfterFirstLogin,
Map<String, List<String>> userConfig) {
setupIdentityProvider();
if (!createAfterFirstLogin) {
createMapperInIdp(syncMode, CLIENT_ROLE_MAPPER_REPRESENTATION);
}
setupUser(userConfig);
logInAsUserInIDPForFirstTime();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
if (!createAfterFirstLogin) {
assertThatRoleHasBeenAssignedInConsumerRealmTo(user);
assertThatRoleHasBeenAssignedInConsumerRealm();
} else {
assertThatRoleHasNotBeenAssignedInConsumerRealmTo(user);
assertThatRoleHasNotBeenAssignedInConsumerRealm();
}
if (createAfterFirstLogin) {
createMapperInIdp(idp, syncMode);
createMapperInIdp(syncMode, CLIENT_ROLE_MAPPER_REPRESENTATION);
}
logoutFromRealm(getConsumerRoot(), bc.consumerRealmName());
updateUser();
logInAsUserInIDP();
user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
return user;
}
private void setup(String roleValue) {
setupIdentityProvider();
createMapperInIdp(IdentityProviderMapperSyncMode.IMPORT, roleValue);
setupUser(createUserConfigForRole(roleValue));
}
private void setupUser(Map<String, List<String>> userConfig) {
createUserInProviderRealm(userConfig);
createUserRoleAndGrantToUserInProviderRealm();
}
protected void createUserRoleAndGrantToUserInProviderRealm() {
RoleRepresentation userRole = new RoleRepresentation(ROLE_USER,null, false);
RoleRepresentation userRole = new RoleRepresentation(ROLE_USER, null, false);
adminClient.realm(bc.providerRealmName()).roles().create(userRole);
RoleRepresentation role = adminClient.realm(bc.providerRealmName()).roles().get(ROLE_USER).toRepresentation();
UserResource userResource = adminClient.realm(bc.providerRealmName()).users().get(userId);
userResource.roles().realmLevel().add(Collections.singletonList(role));
}
protected void assertThatRoleHasBeenAssignedInConsumerRealmTo(UserRepresentation user) {
assertThat(user.getClientRoles().get(CLIENT), contains(CLIENT_ROLE));
private void assertLoginSucceedsWithoutRoleAssignment() {
logInAsUserInIDPForFirstTimeAndAssertSuccess();
assertThatNoRolesHaveBeenAssignedInConsumerRealm();
}
protected void assertThatRoleHasNotBeenAssignedInConsumerRealmTo(UserRepresentation user) {
assertThat(user.getClientRoles().get(CLIENT), not(contains(CLIENT_ROLE)));
protected void assertThatRoleHasBeenAssignedInConsumerRealm() {
assertThatRoleHasBeenAssignedInConsumerRealm(CLIENT_ID, CLIENT_ROLE);
}
protected void assertThatRoleHasNotBeenAssignedInConsumerRealm() {
UserRepresentation user = getConsumerUser();
assertThat(user.getClientRoles().get(CLIENT_ID), not(contains(CLIENT_ROLE)));
}
protected void assertThatRoleHasBeenAssignedInConsumerRealm(String clientId, String roleName) {
UserRepresentation user = getConsumerUser();
assertThat(user.getClientRoles().get(clientId), contains(roleName));
}
protected void assertThatRoleHasBeenAssignedInConsumerRealm(String roleName) {
UserRepresentation user = getConsumerUser();
assertThat(roleName, is(in(user.getRealmRoles())));
}
/**
* Check that just initial (default) roles are assigned to the user.
*/
protected void assertThatNoRolesHaveBeenAssignedInConsumerRealm() {
UserRepresentation user = getConsumerUser();
Map<String, List<String>> clientRoles = user.getClientRoles();
assertThat(clientRoles, Matchers.anyOf(aMapWithSize(0),
hasEntry(Constants.BROKER_SERVICE_CLIENT_ID, Collections.singletonList(Constants.READ_TOKEN_ROLE))));
List<String> realmRoles = user.getRealmRoles();
assertThat(realmRoles, hasSize(1));
assertThat(realmRoles, contains(Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + bc.consumerRealmName()));
}
private UserRepresentation getConsumerUser() {
return findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
}
}

View file

@ -3,27 +3,27 @@ package org.keycloak.testsuite.broker;
import static org.keycloak.models.IdentityProviderMapperSyncMode.FORCE;
import static org.keycloak.models.IdentityProviderMapperSyncMode.LEGACY;
import java.util.List;
import org.junit.Test;
import org.keycloak.admin.client.resource.IdentityProviderResource;
import org.keycloak.broker.provider.ConfigConstants;
import org.keycloak.broker.saml.mappers.AttributeToRoleMapper;
import org.keycloak.broker.saml.mappers.UserAttributeMapper;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:external.martin.idel@bosch.io">Martin Idel</a>
*/
public class AttributeToRoleMapperTest extends AbstractRoleMapperTest {
private static final String ROLE_ATTR_NAME = "Role";
@Override
protected BrokerConfiguration getBrokerConfiguration() {
return new KcSamlBrokerConfiguration();
@ -31,53 +31,53 @@ public class AttributeToRoleMapperTest extends AbstractRoleMapperTest {
@Test
public void mapperGrantsRoleOnFirstLogin() {
UserRepresentation user = createMapperThenLoginAsUserTwiceWithAttributeToRoleMapper(FORCE);
createMapperThenLoginAsUserTwiceWithAttributeToRoleMapper();
assertThatRoleHasBeenAssignedInConsumerRealmTo(user);
assertThatRoleHasBeenAssignedInConsumerRealm();
}
@Test
public void updateBrokeredUserGrantsRoleInLegacyMode() {
UserRepresentation user = loginAsUserThenCreateMapperAndLoginAgainWithAttributeToRoleMapper(LEGACY);
loginAsUserThenCreateMapperAndLoginAgainWithAttributeToRoleMapper(LEGACY);
assertThatRoleHasBeenAssignedInConsumerRealmTo(user);
assertThatRoleHasBeenAssignedInConsumerRealm();
}
@Test
public void updateBrokeredUserGrantsRoleInForceMode() {
UserRepresentation user = loginAsUserThenCreateMapperAndLoginAgainWithAttributeToRoleMapper(FORCE);
loginAsUserThenCreateMapperAndLoginAgainWithAttributeToRoleMapper(FORCE);
assertThatRoleHasBeenAssignedInConsumerRealmTo(user);
assertThatRoleHasBeenAssignedInConsumerRealm();
}
private UserRepresentation createMapperThenLoginAsUserTwiceWithAttributeToRoleMapper(IdentityProviderMapperSyncMode syncMode) {
return loginAsUserTwiceWithMapper(syncMode, false,
ImmutableMap.<String, List<String>>builder()
.put("Role", ImmutableList.<String>builder().add(CLIENT_ROLE_MAPPER_REPRESENTATION).build())
.build());
private void createMapperThenLoginAsUserTwiceWithAttributeToRoleMapper() {
loginAsUserTwiceWithMapper(IdentityProviderMapperSyncMode.FORCE, false,
createUserConfigForRole(CLIENT_ROLE_MAPPER_REPRESENTATION));
}
private UserRepresentation loginAsUserThenCreateMapperAndLoginAgainWithAttributeToRoleMapper(IdentityProviderMapperSyncMode syncMode) {
return loginAsUserTwiceWithMapper(syncMode, true,
ImmutableMap.<String, List<String>>builder()
.put("Role", ImmutableList.<String>builder().add(CLIENT_ROLE_MAPPER_REPRESENTATION).build())
.build());
private void loginAsUserThenCreateMapperAndLoginAgainWithAttributeToRoleMapper(
IdentityProviderMapperSyncMode syncMode) {
loginAsUserTwiceWithMapper(syncMode, true, createUserConfigForRole(CLIENT_ROLE_MAPPER_REPRESENTATION));
}
@Override
protected void createMapperInIdp(IdentityProviderRepresentation idp, IdentityProviderMapperSyncMode syncMode) {
protected void createMapperInIdp(IdentityProviderMapperSyncMode syncMode, String roleValue) {
IdentityProviderMapperRepresentation samlAttributeToRoleMapper = new IdentityProviderMapperRepresentation();
samlAttributeToRoleMapper.setName("user-role-mapper");
samlAttributeToRoleMapper.setIdentityProviderMapper(AttributeToRoleMapper.PROVIDER_ID);
samlAttributeToRoleMapper.setConfig(ImmutableMap.<String,String>builder()
samlAttributeToRoleMapper.setConfig(ImmutableMap.<String, String> builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(UserAttributeMapper.ATTRIBUTE_NAME, "Role")
.put(UserAttributeMapper.ATTRIBUTE_NAME, ROLE_ATTR_NAME)
.put(ATTRIBUTE_VALUE, ROLE_USER)
.put("role", CLIENT_ROLE_MAPPER_REPRESENTATION)
.put(ConfigConstants.ROLE, roleValue)
.build());
IdentityProviderResource idpResource = realm.identityProviders().get(idp.getAlias());
samlAttributeToRoleMapper.setIdentityProviderAlias(bc.getIDPAlias());
idpResource.addMapper(samlAttributeToRoleMapper).close();
persistMapper(samlAttributeToRoleMapper);
}
protected Map<String, List<String>> createUserConfigForRole(String roleValue) {
return ImmutableMap.<String, List<String>> builder()
.put(ROLE_ATTR_NAME, ImmutableList.<String> builder().add(roleValue).build())
.build();
}
}

View file

@ -59,11 +59,31 @@ public class BrokerTestTools {
return identityProviderRepresentation;
}
public static void waitForPage(WebDriver driver, final String title, final boolean isHtmlTitle) {
public static void waitForPage(final WebDriver driver, final String title, final boolean isHtmlTitle) {
waitForPageToLoad();
WebDriverWait wait = new WebDriverWait(driver, 5);
ExpectedCondition<Boolean> condition = (WebDriver input) -> isHtmlTitle ? input.getTitle().toLowerCase().contains(title) : PageUtils.getPageTitle(input).toLowerCase().contains(title);
ExpectedCondition<Boolean> condition = new ExpectedCondition<Boolean>() {
private String actualTitle = null;
public Boolean apply(final WebDriver input) {
if (input == null) {
return false;
}
actualTitle = isHtmlTitle ? input.getTitle() : PageUtils.getPageTitle(input);
if (actualTitle == null) {
return false;
}
return actualTitle.toLowerCase().contains(title.toLowerCase());
}
public String toString() {
return String.format("value to contain (ignoring case) \"%s\". Current value: \"%s\"", title,
this.actualTitle);
}
};
wait.until(condition);
}

View file

@ -1,30 +1,30 @@
package org.keycloak.testsuite.broker;
import java.util.Collections;
import java.util.List;
import static org.keycloak.models.IdentityProviderMapperSyncMode.FORCE;
import static org.keycloak.models.IdentityProviderMapperSyncMode.IMPORT;
import static org.keycloak.models.IdentityProviderMapperSyncMode.LEGACY;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.admin.client.resource.IdentityProviderResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.broker.oidc.mappers.ExternalKeycloakRoleToRoleMapper;
import org.keycloak.broker.provider.ConfigConstants;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import com.google.common.collect.ImmutableMap;
import static org.keycloak.models.IdentityProviderMapperSyncMode.*;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:external.martin.idel@bosch.io">Martin Idel</a>
* @author <a href="mailto:external.martin.idel@bosch.io">Martin Idel</a>,
* <a href="mailto:daniel.fesenmeyer@bosch.io">Daniel Fesenmeyer</a>
*/
public class ExternalKeycloakRoleToRoleMapperTest extends AbstractRoleMapperTest {
private RealmResource realm;
private boolean deleteRoleFromUser = true;
@Override
@ -35,67 +35,66 @@ public class ExternalKeycloakRoleToRoleMapperTest extends AbstractRoleMapperTest
@Before
public void setupRealm() {
super.addClients();
realm = adminClient.realm(bc.consumerRealmName());
}
@Test
public void mapperGrantsRoleOnFirstLogin() {
UserRepresentation user = createMapperThenLoginAsUserTwiceWithExternalKeycloakRoleToRoleMapper(IMPORT);
createMapperThenLoginAsUserTwiceWithExternalKeycloakRoleToRoleMapper(IMPORT);
assertThatRoleHasBeenAssignedInConsumerRealmTo(user);
assertThatRoleHasBeenAssignedInConsumerRealm();
}
@Test
public void updateBrokeredUserDoesNotGrantRoleInLegacyMode() {
UserRepresentation user = loginAsUserThenCreateMapperAndLoginAgainWithExternalKeycloakRoleToRoleMapper(LEGACY);
loginAsUserThenCreateMapperAndLoginAgainWithExternalKeycloakRoleToRoleMapper(LEGACY);
assertThatRoleHasNotBeenAssignedInConsumerRealmTo(user);
assertThatRoleHasNotBeenAssignedInConsumerRealm();
}
@Test
public void updateBrokeredUserGrantsRoleInForceMode() {
UserRepresentation user = loginAsUserThenCreateMapperAndLoginAgainWithExternalKeycloakRoleToRoleMapper(FORCE);
loginAsUserThenCreateMapperAndLoginAgainWithExternalKeycloakRoleToRoleMapper(FORCE);
assertThatRoleHasBeenAssignedInConsumerRealmTo(user);
assertThatRoleHasBeenAssignedInConsumerRealm();
}
@Test
public void updateBrokeredUserMatchDeletesRoleInForceMode() {
UserRepresentation user = createMapperThenLoginAsUserTwiceWithExternalKeycloakRoleToRoleMapper(FORCE);
createMapperThenLoginAsUserTwiceWithExternalKeycloakRoleToRoleMapper(FORCE);
assertThatRoleHasNotBeenAssignedInConsumerRealmTo(user);
assertThatRoleHasNotBeenAssignedInConsumerRealm();
}
@Test
public void updateBrokeredUserMatchDoesNotDeleteRoleInLegacyMode() {
UserRepresentation user = createMapperThenLoginAsUserTwiceWithExternalKeycloakRoleToRoleMapper(LEGACY);
createMapperThenLoginAsUserTwiceWithExternalKeycloakRoleToRoleMapper(LEGACY);
assertThatRoleHasBeenAssignedInConsumerRealmTo(user);
assertThatRoleHasBeenAssignedInConsumerRealm();
}
private UserRepresentation createMapperThenLoginAsUserTwiceWithExternalKeycloakRoleToRoleMapper(IdentityProviderMapperSyncMode syncMode) {
return loginAsUserTwiceWithMapper(syncMode, false, ImmutableMap.<String, List<String>>builder().build());
private void createMapperThenLoginAsUserTwiceWithExternalKeycloakRoleToRoleMapper(
IdentityProviderMapperSyncMode syncMode) {
loginAsUserTwiceWithMapper(syncMode, false, Collections.emptyMap());
}
private UserRepresentation loginAsUserThenCreateMapperAndLoginAgainWithExternalKeycloakRoleToRoleMapper(IdentityProviderMapperSyncMode syncMode) {
private void loginAsUserThenCreateMapperAndLoginAgainWithExternalKeycloakRoleToRoleMapper(
IdentityProviderMapperSyncMode syncMode) {
deleteRoleFromUser = false;
return loginAsUserTwiceWithMapper(syncMode, true, ImmutableMap.<String, List<String>>builder().build());
loginAsUserTwiceWithMapper(syncMode, true, Collections.emptyMap());
}
@Override
protected void createMapperInIdp(IdentityProviderRepresentation idp, IdentityProviderMapperSyncMode syncMode) {
protected void createMapperInIdp(IdentityProviderMapperSyncMode syncMode, String roleValue) {
IdentityProviderMapperRepresentation externalRoleToRoleMapper = new IdentityProviderMapperRepresentation();
externalRoleToRoleMapper.setName("external-keycloak-role-mapper");
externalRoleToRoleMapper.setIdentityProviderMapper(ExternalKeycloakRoleToRoleMapper.PROVIDER_ID);
externalRoleToRoleMapper.setConfig(ImmutableMap.<String,String>builder()
externalRoleToRoleMapper.setConfig(ImmutableMap.<String, String> builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put("external.role", ROLE_USER)
.put("role", CLIENT_ROLE_MAPPER_REPRESENTATION)
.put(ConfigConstants.ROLE, roleValue)
.build());
IdentityProviderResource idpResource = realm.identityProviders().get(idp.getAlias());
externalRoleToRoleMapper.setIdentityProviderAlias(bc.getIDPAlias());
idpResource.addMapper(externalRoleToRoleMapper).close();
persistMapper(externalRoleToRoleMapper);
}
@Override
@ -106,4 +105,9 @@ public class ExternalKeycloakRoleToRoleMapperTest extends AbstractRoleMapperTest
userResource.roles().realmLevel().remove(Collections.singletonList(role));
}
}
@Override
protected Map<String, List<String>> createUserConfigForRole(String roleValue) {
return Collections.emptyMap();
}
}

View file

@ -4,27 +4,25 @@ import static org.keycloak.models.IdentityProviderMapperSyncMode.FORCE;
import static org.keycloak.models.IdentityProviderMapperSyncMode.IMPORT;
import static org.keycloak.models.IdentityProviderMapperSyncMode.LEGACY;
import java.util.HashMap;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.admin.client.resource.IdentityProviderResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.broker.provider.ConfigConstants;
import org.keycloak.broker.provider.HardcodedRoleMapper;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import com.google.common.collect.ImmutableMap;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:external.martin.idel@bosch.io">Martin Idel</a>
* @author <a href="mailto:external.martin.idel@bosch.io">Martin Idel</a>,
* <a href="mailto:daniel.fesenmeyer@bosch.io">Daniel Fesenmeyer</a>
*/
public class HardcodedRoleMapperTest extends AbstractRoleMapperTest {
private RealmResource realm;
@Override
protected BrokerConfiguration getBrokerConfiguration() {
@ -34,64 +32,66 @@ public class HardcodedRoleMapperTest extends AbstractRoleMapperTest {
@Before
public void setupRealm() {
super.addClients();
realm = adminClient.realm(bc.consumerRealmName());
}
@Test
public void mapperGrantsRoleOnFirstLogin() {
UserRepresentation user = createMapperThenLoginAsUserTwiceWithHardcodedRoleMapper(IMPORT);
createMapperThenLoginAsUserTwiceWithHardcodedRoleMapper(IMPORT);
assertThatRoleHasBeenAssignedInConsumerRealmTo(user);
assertThatRoleHasBeenAssignedInConsumerRealm();
}
@Test
public void mapperDoesNotGrantRoleInModeImportIfMapperIsAddedLater() {
UserRepresentation user = loginAsUserThenCreateMapperAndLoginAgainWithHardcodedRoleMapper(IMPORT);
loginAsUserThenCreateMapperAndLoginAgainWithHardcodedRoleMapper(IMPORT);
assertThatRoleHasNotBeenAssignedInConsumerRealmTo(user);
assertThatRoleHasNotBeenAssignedInConsumerRealm();
}
@Test
public void updateBrokeredUserDoesNotGrantRoleInLegacyMode() {
UserRepresentation user = loginAsUserThenCreateMapperAndLoginAgainWithHardcodedRoleMapper(LEGACY);
loginAsUserThenCreateMapperAndLoginAgainWithHardcodedRoleMapper(LEGACY);
assertThatRoleHasNotBeenAssignedInConsumerRealmTo(user);
assertThatRoleHasNotBeenAssignedInConsumerRealm();
}
@Test
public void updateBrokeredUserGrantsRoleInForceMode() {
UserRepresentation user = loginAsUserThenCreateMapperAndLoginAgainWithHardcodedRoleMapper(FORCE);
loginAsUserThenCreateMapperAndLoginAgainWithHardcodedRoleMapper(FORCE);
assertThatRoleHasBeenAssignedInConsumerRealmTo(user);
assertThatRoleHasBeenAssignedInConsumerRealm();
}
@Test
public void updateBrokeredUserMatchDoesntDeleteRole() {
UserRepresentation user = createMapperThenLoginAsUserTwiceWithHardcodedRoleMapper(FORCE);
createMapperThenLoginAsUserTwiceWithHardcodedRoleMapper(FORCE);
assertThatRoleHasBeenAssignedInConsumerRealmTo(user);
assertThatRoleHasBeenAssignedInConsumerRealm();
}
private UserRepresentation createMapperThenLoginAsUserTwiceWithHardcodedRoleMapper(IdentityProviderMapperSyncMode syncMode) {
return loginAsUserTwiceWithMapper(syncMode, false, new HashMap<>());
private void createMapperThenLoginAsUserTwiceWithHardcodedRoleMapper(IdentityProviderMapperSyncMode syncMode) {
loginAsUserTwiceWithMapper(syncMode, false, Collections.emptyMap());
}
private UserRepresentation loginAsUserThenCreateMapperAndLoginAgainWithHardcodedRoleMapper(IdentityProviderMapperSyncMode syncMode) {
return loginAsUserTwiceWithMapper(syncMode, true, new HashMap<>());
private void loginAsUserThenCreateMapperAndLoginAgainWithHardcodedRoleMapper(IdentityProviderMapperSyncMode syncMode) {
loginAsUserTwiceWithMapper(syncMode, true, Collections.emptyMap());
}
@Override
protected void createMapperInIdp(IdentityProviderRepresentation idp, IdentityProviderMapperSyncMode syncMode) {
protected void createMapperInIdp(IdentityProviderMapperSyncMode syncMode, String roleValue) {
IdentityProviderMapperRepresentation advancedClaimToRoleMapper = new IdentityProviderMapperRepresentation();
advancedClaimToRoleMapper.setName("oidc-hardcoded-role-mapper");
advancedClaimToRoleMapper.setIdentityProviderMapper(HardcodedRoleMapper.PROVIDER_ID);
advancedClaimToRoleMapper.setConfig(ImmutableMap.<String, String>builder()
advancedClaimToRoleMapper.setConfig(ImmutableMap.<String, String> builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(ConfigConstants.ROLE, CLIENT_ROLE_MAPPER_REPRESENTATION)
.put(ConfigConstants.ROLE, roleValue)
.build());
IdentityProviderResource idpResource = realm.identityProviders().get(idp.getAlias());
advancedClaimToRoleMapper.setIdentityProviderAlias(bc.getIDPAlias());
idpResource.addMapper(advancedClaimToRoleMapper).close();
persistMapper(advancedClaimToRoleMapper);
}
@Override
protected Map<String, List<String>> createUserConfigForRole(String roleValue) {
return Collections.emptyMap();
}
}

View file

@ -1,23 +1,22 @@
package org.keycloak.testsuite.broker;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import static org.keycloak.testsuite.broker.KcSamlBrokerConfiguration.ATTRIBUTE_TO_MAP_FRIENDLY_NAME;
import org.junit.Test;
import org.keycloak.admin.client.resource.IdentityProviderResource;
import org.keycloak.broker.provider.ConfigConstants;
import org.keycloak.broker.saml.mappers.AdvancedAttributeToRoleMapper;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.util.List;
import static org.keycloak.testsuite.broker.KcSamlBrokerConfiguration.ATTRIBUTE_TO_MAP_FRIENDLY_NAME;
/**
* <a href="mailto:external.martin.idel@bosch.io">Martin Idel</a>
* @author <a href="mailto:external.martin.idel@bosch.io">Martin Idel</a>,
* <a href="mailto:daniel.fesenmeyer@bosch.io">Daniel Fesenmeyer</a>
*/
public class KcSamlAdvancedAttributeToRoleMapperTest extends AbstractAdvancedRoleMapperTest {
@ -39,34 +38,34 @@ public class KcSamlAdvancedAttributeToRoleMapperTest extends AbstractAdvancedRol
}
@Override
protected void createMapperInIdp(IdentityProviderRepresentation idp, String claimsOrAttributeRepresentation, boolean areClaimsOrAttributeValuesRegexes, IdentityProviderMapperSyncMode syncMode) {
protected void createMapperInIdp(String claimsOrAttributeRepresentation,
boolean areClaimsOrAttributeValuesRegexes, IdentityProviderMapperSyncMode syncMode, String roleValue) {
IdentityProviderMapperRepresentation advancedAttributeToRoleMapper = new IdentityProviderMapperRepresentation();
advancedAttributeToRoleMapper.setName("advanced-attribute-to-role-mapper");
advancedAttributeToRoleMapper.setIdentityProviderMapper(AdvancedAttributeToRoleMapper.PROVIDER_ID);
advancedAttributeToRoleMapper.setConfig(ImmutableMap.<String, String>builder()
advancedAttributeToRoleMapper.setConfig(ImmutableMap.<String, String> builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(AdvancedAttributeToRoleMapper.ATTRIBUTE_PROPERTY_NAME, claimsOrAttributeRepresentation)
.put(AdvancedAttributeToRoleMapper.ARE_ATTRIBUTE_VALUES_REGEX_PROPERTY_NAME, areClaimsOrAttributeValuesRegexes ? "true" : "false")
.put(ConfigConstants.ROLE, CLIENT_ROLE_MAPPER_REPRESENTATION)
.put(AdvancedAttributeToRoleMapper.ARE_ATTRIBUTE_VALUES_REGEX_PROPERTY_NAME,
Boolean.valueOf(areClaimsOrAttributeValuesRegexes).toString())
.put(ConfigConstants.ROLE, roleValue)
.build());
IdentityProviderResource idpResource = realm.identityProviders().get(idp.getAlias());
advancedAttributeToRoleMapper.setIdentityProviderAlias(bc.getIDPAlias());
idpResource.addMapper(advancedAttributeToRoleMapper).close();
persistMapper(advancedAttributeToRoleMapper);
}
@Test
public void attributeFriendlyNameGetsConsideredAndMatchedToRole() {
createAdvancedRoleMapper(ATTRIBUTES, false);
createUserInProviderRealm(ImmutableMap.<String, List<String>>builder()
.put(ATTRIBUTE_TO_MAP_FRIENDLY_NAME, ImmutableList.<String>builder().add("value 1").build())
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2, ImmutableList.<String>builder().add("value 2").build())
createUserInProviderRealm(ImmutableMap.<String, List<String>> builder()
.put(ATTRIBUTE_TO_MAP_FRIENDLY_NAME, ImmutableList.<String> builder().add("value 1").build())
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2,
ImmutableList.<String> builder().add("value 2").build())
.build());
logInAsUserInIDPForFirstTime();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
assertThatRoleHasBeenAssignedInConsumerRealmTo(user);
assertThatRoleHasBeenAssignedInConsumerRealm();
}
}

View file

@ -16,8 +16,6 @@
*/
package org.keycloak.testsuite.broker;
import com.google.common.collect.ImmutableMap;
import org.keycloak.admin.client.resource.IdentityProviderResource;
import org.keycloak.broker.provider.ConfigConstants;
import org.keycloak.broker.saml.mappers.AdvancedAttributeToRoleMapper;
import org.keycloak.broker.saml.mappers.AttributeToRoleMapper;
@ -25,7 +23,8 @@ import org.keycloak.broker.saml.mappers.UserAttributeMapper;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import com.google.common.collect.ImmutableMap;
/**
* Runs the same tests as {@link AttributeToRoleMapperTest} but using multiple SAML mappers that map different IDP attributes
@ -45,7 +44,8 @@ import org.keycloak.representations.idm.IdentityProviderRepresentation;
* mapper actually succeeds in applying the mapping, the other two do nothing as the test user doesn't have the necessary
* role/attribute(s). The test then verifies that the user still contains the mapped role after all mappers run.
*
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>,
* <a href="mailto:daniel.fesenmeyer@bosch.io">Daniel Fesenmeyer</a>
*/
public class KcSamlMultipleAttributeToRoleMappersTest extends AttributeToRoleMapperTest {
@ -57,49 +57,48 @@ public class KcSamlMultipleAttributeToRoleMappersTest extends AttributeToRoleMap
"]";
@Override
protected void createMapperInIdp(IdentityProviderRepresentation idp, IdentityProviderMapperSyncMode syncMode) {
protected void createMapperInIdp(IdentityProviderMapperSyncMode syncMode, String roleValue) {
// first mapper that maps a role the test user has - it should perform the mapping.
IdentityProviderMapperRepresentation firstSamlAttributeToRoleMapper = new IdentityProviderMapperRepresentation();
IdentityProviderMapperRepresentation firstSamlAttributeToRoleMapper =
new IdentityProviderMapperRepresentation();
firstSamlAttributeToRoleMapper.setName("first-role-mapper");
firstSamlAttributeToRoleMapper.setIdentityProviderMapper(AttributeToRoleMapper.PROVIDER_ID);
firstSamlAttributeToRoleMapper.setConfig(ImmutableMap.<String,String>builder()
firstSamlAttributeToRoleMapper.setConfig(ImmutableMap.<String, String> builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(UserAttributeMapper.ATTRIBUTE_NAME, "Role")
.put(ATTRIBUTE_VALUE, ROLE_USER)
.put(ConfigConstants.ROLE, CLIENT_ROLE_MAPPER_REPRESENTATION)
.put(ConfigConstants.ROLE, roleValue)
.build());
IdentityProviderResource idpResource = realm.identityProviders().get(idp.getAlias());
firstSamlAttributeToRoleMapper.setIdentityProviderAlias(bc.getIDPAlias());
idpResource.addMapper(firstSamlAttributeToRoleMapper).close();
persistMapper(firstSamlAttributeToRoleMapper);
// second mapper that maps a role the test user doesn't have - it would normally end up removing the mapped role but
// it should now check if a previous mapper has already granted the same mapped role.
IdentityProviderMapperRepresentation secondSamlAttributeToRoleMapper = new IdentityProviderMapperRepresentation();
// second mapper that maps a role the test user doesn't have - it would normally end up removing the mapped
// role, but it should now check if a previous mapper has already granted the same mapped role.
IdentityProviderMapperRepresentation secondSamlAttributeToRoleMapper =
new IdentityProviderMapperRepresentation();
secondSamlAttributeToRoleMapper.setName("second-role-mapper");
secondSamlAttributeToRoleMapper.setIdentityProviderMapper(AttributeToRoleMapper.PROVIDER_ID);
secondSamlAttributeToRoleMapper.setConfig(ImmutableMap.<String,String>builder()
secondSamlAttributeToRoleMapper.setConfig(ImmutableMap.<String, String> builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(UserAttributeMapper.ATTRIBUTE_NAME, "Role")
.put(ATTRIBUTE_VALUE, "missing-role")
.put(ConfigConstants.ROLE, CLIENT_ROLE_MAPPER_REPRESENTATION)
.put(ConfigConstants.ROLE, roleValue)
.build());
secondSamlAttributeToRoleMapper.setIdentityProviderAlias(bc.getIDPAlias());
idpResource.addMapper(secondSamlAttributeToRoleMapper).close();
persistMapper(secondSamlAttributeToRoleMapper);
// third mapper (advanced) that maps an attribute the test user doesn't have - it would normally end up removing the
// mapped role but it should now check if a previous mapper has already granted the same role.
// third mapper (advanced) that maps an attribute the test user doesn't have - it would normally end up removing
// the mapped role, but it should now check if a previous mapper has already granted the same role.
IdentityProviderMapperRepresentation thirdSamlAttributeToRoleMapper = new IdentityProviderMapperRepresentation();
thirdSamlAttributeToRoleMapper.setName("advanced-role-mapper");
thirdSamlAttributeToRoleMapper.setIdentityProviderMapper(AdvancedAttributeToRoleMapper.PROVIDER_ID);
thirdSamlAttributeToRoleMapper.setConfig(ImmutableMap.<String, String>builder()
thirdSamlAttributeToRoleMapper.setConfig(ImmutableMap.<String, String> builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(AdvancedAttributeToRoleMapper.ATTRIBUTE_PROPERTY_NAME, ATTRIBUTES_TO_MATCH)
.put(AdvancedAttributeToRoleMapper.ARE_ATTRIBUTE_VALUES_REGEX_PROPERTY_NAME, Boolean.FALSE.toString())
.put(ConfigConstants.ROLE, CLIENT_ROLE_MAPPER_REPRESENTATION)
.put(ConfigConstants.ROLE, roleValue)
.build());
thirdSamlAttributeToRoleMapper.setIdentityProviderAlias(bc.getIDPAlias());
idpResource.addMapper(thirdSamlAttributeToRoleMapper).close();
persistMapper(thirdSamlAttributeToRoleMapper);
}
}

View file

@ -1,5 +1,6 @@
package org.keycloak.testsuite.broker;
import org.keycloak.admin.client.CreatedResponseUtil;
import org.keycloak.admin.client.resource.IdentityProviderResource;
import org.keycloak.broker.oidc.mappers.AdvancedClaimToGroupMapper;
import org.keycloak.broker.provider.ConfigConstants;
@ -10,15 +11,22 @@ import org.keycloak.representations.idm.IdentityProviderRepresentation;
import com.google.common.collect.ImmutableMap;
import javax.ws.rs.core.Response;
/**
* @author <a href="mailto:artur.baltabayev@bosch.io">Artur Baltabayev</a>,
* <a href="mailto:daniel.fesenmeyer@bosch.io">Daniel Fesenmeyer</a>
*/
public class OidcAdvancedClaimToGroupMapperTest extends AbstractAdvancedGroupMapperTest {
@Override
protected BrokerConfiguration getBrokerConfiguration() {
return new KcOidcBrokerConfiguration();
}
@Override
protected void createMapperInIdp(IdentityProviderRepresentation idp, String claimsOrAttributeRepresentation,
boolean areClaimsOrAttributeValuesRegexes, IdentityProviderMapperSyncMode syncMode) {
protected String createMapperInIdp(IdentityProviderRepresentation idp, String claimsOrAttributeRepresentation,
boolean areClaimsOrAttributeValuesRegexes, IdentityProviderMapperSyncMode syncMode, String groupPath) {
IdentityProviderMapperRepresentation advancedClaimToGroupMapper = new IdentityProviderMapperRepresentation();
advancedClaimToGroupMapper.setName("advanced-claim-to-group-mapper");
advancedClaimToGroupMapper.setIdentityProviderMapper(AdvancedClaimToGroupMapper.PROVIDER_ID);
@ -26,12 +34,14 @@ public class OidcAdvancedClaimToGroupMapperTest extends AbstractAdvancedGroupMap
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(AdvancedClaimToGroupMapper.CLAIM_PROPERTY_NAME, claimsOrAttributeRepresentation)
.put(AdvancedClaimToGroupMapper.ARE_CLAIM_VALUES_REGEX_PROPERTY_NAME,
areClaimsOrAttributeValuesRegexes ? "true" : "false")
.put(ConfigConstants.GROUP, MAPPER_TEST_GROUP_PATH)
Boolean.valueOf(areClaimsOrAttributeValuesRegexes).toString())
.put(ConfigConstants.GROUP, groupPath)
.build());
IdentityProviderResource idpResource = realm.identityProviders().get(idp.getAlias());
advancedClaimToGroupMapper.setIdentityProviderAlias(bc.getIDPAlias());
idpResource.addMapper(advancedClaimToGroupMapper).close();
Response response = idpResource.addMapper(advancedClaimToGroupMapper);
return CreatedResponseUtil.getCreatedId(response);
}
}

View file

@ -1,17 +1,17 @@
package org.keycloak.testsuite.broker;
import com.google.common.collect.ImmutableMap;
import org.keycloak.admin.client.resource.IdentityProviderResource;
import org.keycloak.broker.oidc.mappers.AdvancedClaimToRoleMapper;
import org.keycloak.broker.provider.ConfigConstants;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import com.google.common.collect.ImmutableMap;
/**
* <a href="mailto:external.benjamin.weimer@bosch-si.com">Benjamin Weimer</a>,
* <a href="mailto:external.martin.idel@bosch.io">Martin Idel</a>
* <a href="mailto:external.martin.idel@bosch.io">Martin Idel</a>,
* <a href="mailto:daniel.fesenmeyer@bosch.io">Daniel Fesenmeyer</a>
*/
public class OidcAdvancedClaimToRoleMapperTest extends AbstractAdvancedRoleMapperTest {
@Override
@ -20,19 +20,19 @@ public class OidcAdvancedClaimToRoleMapperTest extends AbstractAdvancedRoleMappe
}
@Override
protected void createMapperInIdp(IdentityProviderRepresentation idp, String claimsOrAttributeRepresentation, boolean areClaimsOrAttributeValuesRegexes, IdentityProviderMapperSyncMode syncMode) {
protected void createMapperInIdp(String claimsOrAttributeRepresentation,
boolean areClaimsOrAttributeValuesRegexes, IdentityProviderMapperSyncMode syncMode, String roleValue) {
IdentityProviderMapperRepresentation advancedClaimToRoleMapper = new IdentityProviderMapperRepresentation();
advancedClaimToRoleMapper.setName("advanced-claim-to-role-mapper");
advancedClaimToRoleMapper.setIdentityProviderMapper(AdvancedClaimToRoleMapper.PROVIDER_ID);
advancedClaimToRoleMapper.setConfig(ImmutableMap.<String, String>builder()
advancedClaimToRoleMapper.setConfig(ImmutableMap.<String, String> builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(AdvancedClaimToRoleMapper.CLAIM_PROPERTY_NAME, claimsOrAttributeRepresentation)
.put(AdvancedClaimToRoleMapper.ARE_CLAIM_VALUES_REGEX_PROPERTY_NAME, areClaimsOrAttributeValuesRegexes ? "true" : "false")
.put(ConfigConstants.ROLE, CLIENT_ROLE_MAPPER_REPRESENTATION)
.put(AdvancedClaimToRoleMapper.ARE_CLAIM_VALUES_REGEX_PROPERTY_NAME,
Boolean.valueOf(areClaimsOrAttributeValuesRegexes).toString())
.put(ConfigConstants.ROLE, roleValue)
.build());
IdentityProviderResource idpResource = realm.identityProviders().get(idp.getAlias());
advancedClaimToRoleMapper.setIdentityProviderAlias(bc.getIDPAlias());
idpResource.addMapper(advancedClaimToRoleMapper).close();
persistMapper(advancedClaimToRoleMapper);
}
}

View file

@ -3,24 +3,23 @@ package org.keycloak.testsuite.broker;
import static org.keycloak.models.IdentityProviderMapperSyncMode.FORCE;
import static org.keycloak.models.IdentityProviderMapperSyncMode.LEGACY;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
import org.keycloak.admin.client.resource.IdentityProviderResource;
import org.keycloak.broker.oidc.mappers.ClaimToRoleMapper;
import org.keycloak.broker.provider.ConfigConstants;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:external.martin.idel@bosch.io">Martin Idel</a>
* @author <a href="mailto:external.martin.idel@bosch.io">Martin Idel</a>,
* <a href="mailto:daniel.fesenmeyer@bosch.io">Daniel Fesenmeyer</a>
*/
public class OidcClaimToRoleMapperTest extends AbstractRoleMapperTest {
@ -36,89 +35,79 @@ public class OidcClaimToRoleMapperTest extends AbstractRoleMapperTest {
@Test
public void allClaimValuesMatch() {
createClaimToRoleMapper(CLAIM_VALUE);
createUserInProviderRealm(ImmutableMap.<String, List<String>>builder()
.put(CLAIM, ImmutableList.<String>builder().add(CLAIM_VALUE).build())
.build());
createUserInProviderRealm(createUserConfig());
logInAsUserInIDPForFirstTime();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
assertThatRoleHasBeenAssignedInConsumerRealmTo(user);
assertThatRoleHasBeenAssignedInConsumerRealm();
}
@Test
public void claimValuesMismatch() {
createClaimToRoleMapper("other value");
createUserInProviderRealm(ImmutableMap.<String, List<String>>builder()
.put(CLAIM, ImmutableList.<String>builder().add(CLAIM_VALUE).build())
.build());
createUserInProviderRealm(createUserConfig());
logInAsUserInIDPForFirstTime();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
assertThatRoleHasNotBeenAssignedInConsumerRealmTo(user);
assertThatRoleHasNotBeenAssignedInConsumerRealm();
}
@Test
public void updateBrokeredUserMismatchDeletesRoleInForceMode() {
UserRepresentation user = loginWithClaimThenChangeClaimToValue("value mismatch", FORCE, false);
loginWithClaimThenChangeClaimToValue("value mismatch", FORCE, false);
assertThatRoleHasNotBeenAssignedInConsumerRealmTo(user);
assertThatRoleHasNotBeenAssignedInConsumerRealm();
}
@Test
public void updateBrokeredUserMismatchDeletesRoleInLegacyMode() {
UserRepresentation user = createMapperThenLoginWithStandardClaimThenChangeClaimToValue("value mismatch", LEGACY);
createMapperThenLoginWithStandardClaimThenChangeClaimToValue("value mismatch", LEGACY);
assertThatRoleHasNotBeenAssignedInConsumerRealmTo(user);
assertThatRoleHasNotBeenAssignedInConsumerRealm();
}
@Test
public void updateBrokeredUserNewMatchGrantsRoleAfterFirstLoginInForceMode() {
UserRepresentation user = loginWithStandardClaimThenAddMapperAndLoginAgain(FORCE);
loginWithStandardClaimThenAddMapperAndLoginAgain(FORCE);
assertThatRoleHasBeenAssignedInConsumerRealmTo(user);
assertThatRoleHasBeenAssignedInConsumerRealm();
}
@Test
public void updateBrokeredUserNewMatchDoesNotGrantRoleAfterFirstLoginInLegacyMode() {
UserRepresentation user = loginWithStandardClaimThenAddMapperAndLoginAgain(LEGACY);
loginWithStandardClaimThenAddMapperAndLoginAgain(LEGACY);
assertThatRoleHasNotBeenAssignedInConsumerRealmTo(user);
assertThatRoleHasNotBeenAssignedInConsumerRealm();
}
@Test
public void updateBrokeredUserDoesNotDeleteRoleIfClaimStillMatches() {
UserRepresentation user = createMapperThenLoginWithStandardClaimThenChangeClaimToValue(CLAIM_VALUE, FORCE);
createMapperThenLoginWithStandardClaimThenChangeClaimToValue(CLAIM_VALUE, FORCE);
assertThatRoleHasBeenAssignedInConsumerRealmTo(user);
assertThatRoleHasBeenAssignedInConsumerRealm();
}
private UserRepresentation loginWithStandardClaimThenAddMapperAndLoginAgain(IdentityProviderMapperSyncMode syncMode) {
return loginWithClaimThenChangeClaimToValue(OidcClaimToRoleMapperTest.CLAIM_VALUE, syncMode, true);
private void loginWithStandardClaimThenAddMapperAndLoginAgain(IdentityProviderMapperSyncMode syncMode) {
loginWithClaimThenChangeClaimToValue(OidcClaimToRoleMapperTest.CLAIM_VALUE, syncMode, true);
}
private UserRepresentation createMapperThenLoginWithStandardClaimThenChangeClaimToValue(String claimOnSecondLogin, IdentityProviderMapperSyncMode syncMode) {
return loginWithClaimThenChangeClaimToValue(claimOnSecondLogin, syncMode, false);
private void createMapperThenLoginWithStandardClaimThenChangeClaimToValue(String claimOnSecondLogin, IdentityProviderMapperSyncMode syncMode) {
loginWithClaimThenChangeClaimToValue(claimOnSecondLogin, syncMode, false);
}
@NotNull
private UserRepresentation loginWithClaimThenChangeClaimToValue(String claimOnSecondLogin, IdentityProviderMapperSyncMode syncMode, boolean createAfterFirstLogin) {
private void loginWithClaimThenChangeClaimToValue(String claimOnSecondLogin, IdentityProviderMapperSyncMode syncMode, boolean createAfterFirstLogin) {
this.claimOnSecondLogin = claimOnSecondLogin;
return loginAsUserTwiceWithMapper(syncMode, createAfterFirstLogin,
ImmutableMap.<String, List<String>>builder()
.put(CLAIM, ImmutableList.<String>builder().add(CLAIM_VALUE).build())
.build());
loginAsUserTwiceWithMapper(syncMode, createAfterFirstLogin, createUserConfig());
}
private void createClaimToRoleMapper(String claimValue) {
IdentityProviderRepresentation idp = setupIdentityProvider();
createClaimToRoleMapper(idp, claimValue, IdentityProviderMapperSyncMode.IMPORT);
setupIdentityProvider();
createClaimToRoleMapper(claimValue, IdentityProviderMapperSyncMode.IMPORT, CLIENT_ROLE_MAPPER_REPRESENTATION);
}
@Override
protected void createMapperInIdp(IdentityProviderRepresentation idp, IdentityProviderMapperSyncMode syncMode) {
createClaimToRoleMapper(idp, CLAIM_VALUE, syncMode);
protected void createMapperInIdp(IdentityProviderMapperSyncMode syncMode, String roleValue) {
createClaimToRoleMapper(CLAIM_VALUE, syncMode, roleValue);
}
@Override
@ -131,19 +120,29 @@ public class OidcClaimToRoleMapperTest extends AbstractRoleMapperTest {
adminClient.realm(bc.providerRealmName()).users().get(user.getId()).update(user);
}
protected void createClaimToRoleMapper(IdentityProviderRepresentation idp, String claimValue, IdentityProviderMapperSyncMode syncMode) {
protected void createClaimToRoleMapper(String claimValue, IdentityProviderMapperSyncMode syncMode,
String roleValue) {
IdentityProviderMapperRepresentation claimToRoleMapper = new IdentityProviderMapperRepresentation();
claimToRoleMapper.setName("claim-to-role-mapper");
claimToRoleMapper.setIdentityProviderMapper(ClaimToRoleMapper.PROVIDER_ID);
claimToRoleMapper.setConfig(ImmutableMap.<String, String>builder()
claimToRoleMapper.setConfig(ImmutableMap.<String, String> builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(ClaimToRoleMapper.CLAIM, OidcClaimToRoleMapperTest.CLAIM)
.put(ClaimToRoleMapper.CLAIM_VALUE, claimValue)
.put(ConfigConstants.ROLE, CLIENT_ROLE_MAPPER_REPRESENTATION)
.put(ConfigConstants.ROLE, roleValue)
.build());
IdentityProviderResource idpResource = realm.identityProviders().get(idp.getAlias());
claimToRoleMapper.setIdentityProviderAlias(bc.getIDPAlias());
idpResource.addMapper(claimToRoleMapper).close();
persistMapper(claimToRoleMapper);
}
@Override
protected Map<String, List<String>> createUserConfigForRole(String roleValue) {
return createUserConfig();
}
private static ImmutableMap<String, List<String>> createUserConfig() {
return ImmutableMap.<String, List<String>>builder()
.put(CLAIM, ImmutableList.<String>builder().add(CLAIM_VALUE).build())
.build();
}
}

View file

@ -16,8 +16,6 @@
*/
package org.keycloak.testsuite.broker;
import com.google.common.collect.ImmutableMap;
import org.keycloak.admin.client.resource.IdentityProviderResource;
import org.keycloak.broker.oidc.mappers.AdvancedClaimToRoleMapper;
import org.keycloak.broker.oidc.mappers.ClaimToRoleMapper;
import org.keycloak.broker.oidc.mappers.ExternalKeycloakRoleToRoleMapper;
@ -25,7 +23,8 @@ import org.keycloak.broker.provider.ConfigConstants;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import com.google.common.collect.ImmutableMap;
/**
* Runs the same tests as {@link OidcClaimToRoleMapperTest} but using multiple OIDC mappers that map different IDP claims
@ -45,7 +44,8 @@ import org.keycloak.representations.idm.IdentityProviderRepresentation;
* mapper actually succeeds in applying the mapping, the other two do nothing as the test user doesn't have the necessary
* role/attribute(s). The test then verifies that the user still contains the mapped role after all mappers run.
*
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>,
* <a href="mailto:daniel.fesenmeyer@bosch.io">Daniel Fesenmeyer</a>
*/
public class OidcMultipleClaimToRoleMappersTest extends OidcClaimToRoleMapperTest {
@ -57,48 +57,46 @@ public class OidcMultipleClaimToRoleMappersTest extends OidcClaimToRoleMapperTes
"]";
@Override
protected void createClaimToRoleMapper(IdentityProviderRepresentation idp, String claimValue, IdentityProviderMapperSyncMode syncMode) {
protected void createClaimToRoleMapper(String claimValue, IdentityProviderMapperSyncMode syncMode,
String roleValue) {
// first mapper that maps attributes the user has - it should perform the mapping to the expected role.
IdentityProviderMapperRepresentation firstOidcClaimToRoleMapper = new IdentityProviderMapperRepresentation();
firstOidcClaimToRoleMapper.setName("claim-to-role-mapper");
firstOidcClaimToRoleMapper.setIdentityProviderMapper(ClaimToRoleMapper.PROVIDER_ID);
firstOidcClaimToRoleMapper.setConfig(ImmutableMap.<String, String>builder()
firstOidcClaimToRoleMapper.setConfig(ImmutableMap.<String, String> builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(ClaimToRoleMapper.CLAIM, OidcClaimToRoleMapperTest.CLAIM)
.put(ClaimToRoleMapper.CLAIM_VALUE, claimValue)
.put(ConfigConstants.ROLE, CLIENT_ROLE_MAPPER_REPRESENTATION)
.put(ConfigConstants.ROLE, roleValue)
.build());
IdentityProviderResource idpResource = realm.identityProviders().get(idp.getAlias());
firstOidcClaimToRoleMapper.setIdentityProviderAlias(bc.getIDPAlias());
idpResource.addMapper(firstOidcClaimToRoleMapper).close();
persistMapper(firstOidcClaimToRoleMapper);
// second mapper that maps an external role claim the test user doesn't have - it would normally end up removing the
// mapped role but it should now check if a previous mapper has already granted the same role.
// second mapper that maps an external role claim the test user doesn't have - it would normally end up removing
// the mapped role, but it should now check if a previous mapper has already granted the same role.
IdentityProviderMapperRepresentation secondOidcClaimToRoleMapper = new IdentityProviderMapperRepresentation();
secondOidcClaimToRoleMapper.setName("external-keycloak-role-mapper");
secondOidcClaimToRoleMapper.setIdentityProviderMapper(ExternalKeycloakRoleToRoleMapper.PROVIDER_ID);
secondOidcClaimToRoleMapper.setConfig(ImmutableMap.<String,String>builder()
secondOidcClaimToRoleMapper.setConfig(ImmutableMap.<String, String> builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put("external.role", "missing-role")
.put("role", CLIENT_ROLE_MAPPER_REPRESENTATION)
.put(ConfigConstants.ROLE, roleValue)
.build());
secondOidcClaimToRoleMapper.setIdentityProviderAlias(bc.getIDPAlias());
idpResource.addMapper(secondOidcClaimToRoleMapper).close();
persistMapper(secondOidcClaimToRoleMapper);
// third mapper (advanced) that maps a claim the test user doesn't have - it would normally end up removing the
// mapped role but it should now check if a previous mapper has already granted the same role.
// mapped role, but it should now check if a previous mapper has already granted the same role.
IdentityProviderMapperRepresentation thirdOidcClaimToRoleMapper = new IdentityProviderMapperRepresentation();
thirdOidcClaimToRoleMapper.setName("advanced-claim-to-role-mapper");
thirdOidcClaimToRoleMapper.setIdentityProviderMapper(AdvancedClaimToRoleMapper.PROVIDER_ID);
thirdOidcClaimToRoleMapper.setConfig(ImmutableMap.<String, String>builder()
thirdOidcClaimToRoleMapper.setConfig(ImmutableMap.<String, String> builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(AdvancedClaimToRoleMapper.CLAIM_PROPERTY_NAME, CLAIMS_OR_ATTRIBUTES)
.put(AdvancedClaimToRoleMapper.ARE_CLAIM_VALUES_REGEX_PROPERTY_NAME, Boolean.TRUE.toString())
.put(ConfigConstants.ROLE, CLIENT_ROLE_MAPPER_REPRESENTATION)
.put(ConfigConstants.ROLE, roleValue)
.build());
thirdOidcClaimToRoleMapper.setIdentityProviderAlias(bc.getIDPAlias());
idpResource.addMapper(thirdOidcClaimToRoleMapper).close();
persistMapper(thirdOidcClaimToRoleMapper);
}
}

View file

@ -1,9 +1,6 @@
package org.keycloak.testsuite.broker;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.junit.Test;
import org.keycloak.admin.client.resource.IdentityProviderResource;
import org.keycloak.broker.oidc.mappers.ClaimToRoleMapper;
import org.keycloak.broker.provider.ConfigConstants;
import org.keycloak.models.IdentityProviderMapperModel;
@ -14,18 +11,28 @@ import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper;
import org.keycloak.protocol.oidc.mappers.UserAttributeMapper;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.idm.*;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import java.util.Arrays;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:dashaylan@gmail.com">Dashaylan Naidoo</a>,
* <a href="mailto:daniel.fesenmeyer@bosch.io">Daniel Fesenmeyer</a>
*/
public class OidcUserInfoClaimToRoleMapperTest extends AbstractRoleMapperTest {
protected static final String ATTRIBUTE_TO_MAP_USER_INFO = "user-attribute-info";
private static final String USER_INFO_CLAIM = ATTRIBUTE_TO_MAP_USER_INFO;
private static final String USER_INFO_CLAIM_VALUE = "value 1";
private String claimOnSecondLogin = "";
private static final String CLAIM_ON_SECOND_LOGIN = "";
@Override
@ -35,69 +42,63 @@ public class OidcUserInfoClaimToRoleMapperTest extends AbstractRoleMapperTest {
@Test
public void singleClaimValueInUserInfoMatches() {
createClaimToRoleMapper(USER_INFO_CLAIM_VALUE);
createUserInProviderRealm(ImmutableMap.<String, List<String>>builder()
.put(USER_INFO_CLAIM, ImmutableList.<String>builder().add(USER_INFO_CLAIM_VALUE).build())
.build());
createClaimToRoleMapper();
createUserInProviderRealm(createUserConfig());
logInAsUserInIDPForFirstTime();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
assertThatRoleHasBeenAssignedInConsumerRealmTo(user);
assertThatRoleHasBeenAssignedInConsumerRealm();
}
@Test
public void noRoleAddedIfUserInfoDisabledAndOnlyClaimIsInUserInfo() {
createClaimToRoleMapperWithUserInfoDisabledInIdP(USER_INFO_CLAIM_VALUE);
createUserInProviderRealm(ImmutableMap.<String, List<String>>builder()
.put(USER_INFO_CLAIM, ImmutableList.<String>builder().add(USER_INFO_CLAIM_VALUE).build())
.build());
createClaimToRoleMapperWithUserInfoDisabledInIdP();
createUserInProviderRealm(createUserConfig());
logInAsUserInIDPForFirstTime();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
assertThatRoleHasNotBeenAssignedInConsumerRealmTo(user);
assertThatRoleHasNotBeenAssignedInConsumerRealm();
}
private void createClaimToRoleMapper(String claimValue) {
IdentityProviderRepresentation idp = setupIdentityProvider();
createClaimToRoleMapper(idp, claimValue, IdentityProviderMapperSyncMode.IMPORT);
private void createClaimToRoleMapper() {
setupIdentityProvider();
createClaimToRoleMapper(OidcUserInfoClaimToRoleMapperTest.USER_INFO_CLAIM_VALUE,
IdentityProviderMapperSyncMode.IMPORT, CLIENT_ROLE_MAPPER_REPRESENTATION);
}
private void createClaimToRoleMapperWithUserInfoDisabledInIdP(String claimValue) {
IdentityProviderRepresentation idp = setupIdentityProviderDisableUserInfo();
createClaimToRoleMapper(idp, claimValue, IdentityProviderMapperSyncMode.IMPORT);
private void createClaimToRoleMapperWithUserInfoDisabledInIdP() {
setupIdentityProviderDisableUserInfo();
createClaimToRoleMapper(OidcUserInfoClaimToRoleMapperTest.USER_INFO_CLAIM_VALUE,
IdentityProviderMapperSyncMode.IMPORT, CLIENT_ROLE_MAPPER_REPRESENTATION);
}
@Override
protected void createMapperInIdp(IdentityProviderRepresentation idp, IdentityProviderMapperSyncMode syncMode) {
createClaimToRoleMapper(idp, USER_INFO_CLAIM_VALUE, syncMode);
protected void createMapperInIdp(IdentityProviderMapperSyncMode syncMode, String roleValue) {
createClaimToRoleMapper(USER_INFO_CLAIM_VALUE, syncMode, roleValue);
}
@Override
protected void updateUser() {
UserRepresentation user = findUser(bc.providerRealmName(), bc.getUserLogin(), bc.getUserEmail());
ImmutableMap<String, List<String>> mismatchingAttributes = ImmutableMap.<String, List<String>>builder()
.put(USER_INFO_CLAIM, ImmutableList.<String>builder().add(claimOnSecondLogin).build())
ImmutableMap<String, List<String>> mismatchingAttributes = ImmutableMap.<String, List<String>> builder()
.put(USER_INFO_CLAIM, ImmutableList.<String> builder().add(CLAIM_ON_SECOND_LOGIN).build())
.build();
user.setAttributes(mismatchingAttributes);
adminClient.realm(bc.providerRealmName()).users().get(user.getId()).update(user);
}
private void createClaimToRoleMapper(IdentityProviderRepresentation idp, String claimValue, IdentityProviderMapperSyncMode syncMode) {
private void createClaimToRoleMapper(String claimValue, IdentityProviderMapperSyncMode syncMode, String roleValue) {
IdentityProviderMapperRepresentation claimToRoleMapper = new IdentityProviderMapperRepresentation();
claimToRoleMapper.setName("userinfo-claim-to-role-mapper");
claimToRoleMapper.setIdentityProviderMapper(ClaimToRoleMapper.PROVIDER_ID);
claimToRoleMapper.setConfig(ImmutableMap.<String, String>builder()
claimToRoleMapper.setConfig(ImmutableMap.<String, String> builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(ClaimToRoleMapper.CLAIM, OidcUserInfoClaimToRoleMapperTest.USER_INFO_CLAIM)
.put(ClaimToRoleMapper.CLAIM_VALUE, claimValue)
.put(ConfigConstants.ROLE, CLIENT_ROLE_MAPPER_REPRESENTATION)
.put(ConfigConstants.ROLE, roleValue)
.build());
IdentityProviderResource idpResource = realm.identityProviders().get(idp.getAlias());
claimToRoleMapper.setIdentityProviderAlias(bc.getIDPAlias());
idpResource.addMapper(claimToRoleMapper).close();
persistMapper(claimToRoleMapper);
}
private class KcOidcBrokerConfigurationUserInfoOnlyMappers extends KcOidcBrokerConfiguration {
@ -120,8 +121,8 @@ public class OidcUserInfoClaimToRoleMapperTest extends AbstractRoleMapperTest {
userAttrMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "false");
userAttrMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_USERINFO, "true");
for (ClientRepresentation client: clientsRepList) {
client.setProtocolMappers(Arrays.asList(userAttrMapper));
for (ClientRepresentation client : clientsRepList) {
client.setProtocolMappers(Collections.singletonList(userAttrMapper));
}
return clientsRepList;
@ -135,4 +136,14 @@ public class OidcUserInfoClaimToRoleMapperTest extends AbstractRoleMapperTest {
}
}
@Override
protected Map<String, List<String>> createUserConfigForRole(String roleValue) {
return createUserConfig();
}
private static Map<String, List<String>> createUserConfig() {
return ImmutableMap.<String, List<String>> builder()
.put(USER_INFO_CLAIM, ImmutableList.<String> builder().add(USER_INFO_CLAIM_VALUE).build())
.build();
}
}

View file

@ -21,12 +21,16 @@ import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RealmProvider;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.provider.ProviderEventListener;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.MatcherAssert.assertThat;
@ -34,6 +38,7 @@ import static org.hamcrest.Matchers.aMapWithSize;
import static org.hamcrest.Matchers.anEmptyMap;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasEntry;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.notNullValue;
@RequireProvider(RealmProvider.class)
@ -108,12 +113,12 @@ public class RealmModelTest extends KeycloakModelTest {
@Test
public void testRealmPreRemoveDoesntRemoveEntitiesFromOtherRealms() {
realm1Id = inComittedTransaction((Function<KeycloakSession, String>) session -> {
realm1Id = inComittedTransaction(session -> {
RealmModel realm = session.realms().createRealm("realm1");
realm.setDefaultRole(session.roles().addRealmRole(realm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + realm.getName()));
return realm.getId();
});
realm2Id = inComittedTransaction((Function<KeycloakSession, String>) session -> {
realm2Id = inComittedTransaction(session -> {
RealmModel realm = session.realms().createRealm("realm2");
realm.setDefaultRole(session.roles().addRealmRole(realm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + realm.getName()));
return realm.getId();
@ -140,4 +145,42 @@ public class RealmModelTest extends KeycloakModelTest {
assertThat(resourceServer, notNullValue());
}
@Test
public void testMoveGroup() {
ProviderEventListener providerEventListener = null;
try {
List<GroupModel.GroupPathChangeEvent> groupPathChangeEvents = new ArrayList<>();
providerEventListener = event -> {
if (event instanceof GroupModel.GroupPathChangeEvent) {
groupPathChangeEvents.add((GroupModel.GroupPathChangeEvent) event);
}
};
getFactory().register(providerEventListener);
withRealm(realmId, (session, realm) -> {
GroupModel groupA = realm.createGroup("a");
GroupModel groupB = realm.createGroup("b");
final String previousPath = "/a";
assertThat(KeycloakModelUtils.buildGroupPath(groupA), equalTo(previousPath));
realm.moveGroup(groupA, groupB);
final String expectedNewPath = "/b/a";
assertThat(KeycloakModelUtils.buildGroupPath(groupA), equalTo(expectedNewPath));
assertThat(groupPathChangeEvents, hasSize(1));
GroupModel.GroupPathChangeEvent groupPathChangeEvent = groupPathChangeEvents.get(0);
assertThat(groupPathChangeEvent.getPreviousPath(), equalTo(previousPath));
assertThat(groupPathChangeEvent.getNewPath(), equalTo(expectedNewPath));
return null;
});
} finally {
if (providerEventListener != null) {
getFactory().unregister(providerEventListener);
}
}
}
}