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) { if (group.getParentId() != null) {
group.getParent().removeChild(group); group.getParent().removeChild(group);
} }
GroupModel previousParent = group.getParent();
group.setParent(toParent); group.setParent(toParent);
if (toParent != null) toParent.addChild(group); if (toParent != null) toParent.addChild(group);
else session.groups().addTopLevelGroup(realm, 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 // DuplicateModelException {@link PersistenceExceptionConverter} is not called if the
// ConstraintViolationException is not thrown in method called directly from EntityManager // ConstraintViolationException is not thrown in method called directly from EntityManager
em.flush(); 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 @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.QueryParameters;
import org.keycloak.models.map.storage.criteria.DefaultModelCriteria; import org.keycloak.models.map.storage.criteria.DefaultModelCriteria;
import org.keycloak.models.utils.KeycloakModelUtils;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@ -259,6 +260,8 @@ public class MapGroupProvider implements GroupProvider {
public void moveGroup(RealmModel realm, GroupModel group, GroupModel toParent) { public void moveGroup(RealmModel realm, GroupModel group, GroupModel toParent) {
LOG.tracef("moveGroup(%s, %s, %s)%s", realm, group, toParent, getShortStackTrace()); LOG.tracef("moveGroup(%s, %s, %s)%s", realm, group, toParent, getShortStackTrace());
GroupModel previousParent = group.getParent();
if (toParent != null && group.getId().equals(toParent.getId())) { if (toParent != null && group.getId().equals(toParent.getId())) {
return; return;
} }
@ -282,6 +285,33 @@ public class MapGroupProvider implements GroupProvider {
} }
group.setParent(toParent); group.setParent(toParent);
if (toParent != null) toParent.addChild(group); 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 @Override

View file

@ -17,6 +17,8 @@
package org.keycloak.broker.provider; 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.IdentityProviderMapperModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
@ -28,6 +30,11 @@ import org.keycloak.models.UserModel;
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public abstract class AbstractIdentityProviderMapper implements IdentityProviderMapper { public abstract class AbstractIdentityProviderMapper implements IdentityProviderMapper {
private static final Logger LOG = Logger.getLogger(AbstractIdentityProviderMapper.class);
private static volatile KeycloakSessionFactory keycloakSessionFactory;
@Override @Override
public void close() { public void close() {
@ -45,7 +52,24 @@ public abstract class AbstractIdentityProviderMapper implements IdentityProvider
@Override @Override
public void postInit(KeycloakSessionFactory factory) { 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 @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. * 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 final class KeycloakModelUtils {
public static final String AUTH_TYPE_CLIENT_SECRET = "client-secret"; 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 AUTH_TYPE_CLIENT_SECRET_JWT = "client-secret-jwt";
public static final String GROUP_PATH_SEPARATOR = "/";
private static final char CLIENT_ROLE_SEPARATOR = '.';
private KeycloakModelUtils() { 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. * group names containing a {@code /} character.
* *
* @param segments the path segments * @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 * @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) { 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) { if (nameSegments.length > 1 && segments.length >= nameSegments.length) {
for (int i = 0; i < nameSegments.length; i++) { for (int i = 0; i < nameSegments.length; i++) {
@ -582,13 +586,13 @@ public final class KeycloakModelUtils {
if (path == null) { if (path == null) {
return null; return null;
} }
if (path.startsWith("/")) { if (path.startsWith(GROUP_PATH_SEPARATOR)) {
path = path.substring(1); path = path.substring(1);
} }
if (path.endsWith("/")) { if (path.endsWith(GROUP_PATH_SEPARATOR)) {
path = path.substring(0, path.length() - 1); path = path.substring(0, path.length() - 1);
} }
String[] split = path.split("/"); String[] split = path.split(GROUP_PATH_SEPARATOR);
if (split.length == 0) return null; if (split.length == 0) return null;
return realm.getTopLevelGroupsStream().map(group -> { return realm.getTopLevelGroupsStream().map(group -> {
@ -610,6 +614,42 @@ public final class KeycloakModelUtils {
}).filter(Objects::nonNull).findFirst().orElse(null); }).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 client {@link ClientModel}
* @param container {@link ScopeContainerModel} * @param container {@link ScopeContainerModel}
@ -629,8 +669,12 @@ public final class KeycloakModelUtils {
// Used in various role mappers // Used in various role mappers
public static RoleModel getRoleFromString(RealmModel realm, String roleName) { public static RoleModel getRoleFromString(RealmModel realm, String roleName) {
if (roleName == null) {
return null;
}
// Check client roles for all possible splits by dot // Check client roles for all possible splits by dot
int scopeIndex = roleName.lastIndexOf('.'); int scopeIndex = roleName.lastIndexOf(CLIENT_ROLE_SEPARATOR);
while (scopeIndex >= 0) { while (scopeIndex >= 0) {
String appName = roleName.substring(0, scopeIndex); String appName = roleName.substring(0, scopeIndex);
ClientModel client = realm.getClientByClientId(appName); ClientModel client = realm.getClientByClientId(appName);
@ -639,7 +683,7 @@ public final class KeycloakModelUtils {
return client.getRole(role); return client.getRole(role);
} }
scopeIndex = roleName.lastIndexOf('.', scopeIndex - 1); scopeIndex = roleName.lastIndexOf(CLIENT_ROLE_SEPARATOR, scopeIndex - 1);
} }
// determine if roleName is a realm role // determine if roleName is a realm role
@ -648,7 +692,7 @@ public final class KeycloakModelUtils {
// Used for hardcoded role mappers // Used for hardcoded role mappers
public static String[] parseRole(String role) { public static String[] parseRole(String role) {
int scopeIndex = role.lastIndexOf('.'); int scopeIndex = role.lastIndexOf(CLIENT_ROLE_SEPARATOR);
if (scopeIndex > -1) { if (scopeIndex > -1) {
String appName = role.substring(0, scopeIndex); String appName = role.substring(0, scopeIndex);
role = role.substring(scopeIndex + 1); 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 * 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); 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) { public static String buildGroupPath(GroupModel group) {
StringBuilder sb = new StringBuilder(); return KeycloakModelUtils.buildGroupPath(group);
buildGroupPath(sb, group);
return sb.toString();
} }
public static GroupRepresentation groupToBriefRepresentation(GroupModel g) { public static GroupRepresentation groupToBriefRepresentation(GroupModel g) {
return toRepresentation(g, false); return toRepresentation(g, false);
} }

View file

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

View file

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

View file

@ -17,6 +17,7 @@
package org.keycloak.models; package org.keycloak.models;
import org.keycloak.provider.ProviderEvent;
import org.keycloak.storage.SearchableModelField; import org.keycloak.storage.SearchableModelField;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -41,6 +42,18 @@ public interface RoleModel {
public static final SearchableModelField<RoleModel> COMPOSITE_ROLE = new SearchableModelField<>("compositeRoles", Boolean.class); 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 getName();
String getDescription(); String getDescription();

View file

@ -17,9 +17,9 @@
package org.keycloak.broker.oidc.mappers; package org.keycloak.broker.oidc.mappers;
import org.jboss.logging.Logger;
import org.keycloak.broker.provider.BrokeredIdentityContext; import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.broker.provider.ConfigConstants; import org.keycloak.broker.provider.ConfigConstants;
import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.models.GroupModel; import org.keycloak.models.GroupModel;
import org.keycloak.models.IdentityProviderMapperModel; import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
@ -27,13 +27,24 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils; 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 { public abstract class AbstractClaimToGroupMapper extends AbstractClaimMapper {
private static final Logger LOG = Logger.getLogger(AbstractClaimToGroupMapper.class);
@Override @Override
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user,
IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
GroupModel group = this.getGroup(realm, mapperModel); GroupModel group = this.getGroup(realm, mapperModel);
if (group == null) {
return;
}
if (applies(mapperModel, context)) { if (applies(mapperModel, context)) {
user.joinGroup(group); user.joinGroup(group);
} }
@ -44,8 +55,11 @@ public abstract class AbstractClaimToGroupMapper extends AbstractClaimMapper {
IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
GroupModel group = this.getGroup(realm, mapperModel); 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 (!context.hasMapperAssignedGroup(groupId)) {
if (applies(mapperModel, context)) { if (applies(mapperModel, context)) {
context.addMapperAssignedGroup(groupId); context.addMapperAssignedGroup(groupId);
@ -67,22 +81,16 @@ public abstract class AbstractClaimToGroupMapper extends AbstractClaimMapper {
protected abstract boolean applies(final IdentityProviderMapperModel mapperModel, protected abstract boolean applies(final IdentityProviderMapperModel mapperModel,
final BrokeredIdentityContext context); 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) { 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) { 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; return group;
} }
} }

View file

@ -16,9 +16,9 @@
*/ */
package org.keycloak.broker.oidc.mappers; package org.keycloak.broker.oidc.mappers;
import org.jboss.logging.Logger;
import org.keycloak.broker.provider.BrokeredIdentityContext; import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.broker.provider.ConfigConstants; import org.keycloak.broker.provider.ConfigConstants;
import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.models.IdentityProviderMapperModel; import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; 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 * 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. * 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 { public abstract class AbstractClaimToRoleMapper extends AbstractClaimMapper {
private static final Logger LOG = Logger.getLogger(AbstractClaimToRoleMapper.class);
@Override @Override
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { 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)) { if (applies(mapperModel, context)) {
user.grantRole(role); user.grantRole(role);
} }
@ -44,7 +51,11 @@ public abstract class AbstractClaimToRoleMapper extends AbstractClaimMapper {
@Override @Override
public void updateBrokeredUserLegacy(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { 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)) { if (!applies(mapperModel, context)) {
user.deleteRoleMapping(role); user.deleteRoleMapping(role);
} }
@ -52,7 +63,11 @@ public abstract class AbstractClaimToRoleMapper extends AbstractClaimMapper {
@Override @Override
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { 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); 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. // 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)) { if (!context.hasMapperGrantedRole(roleName)) {
@ -77,22 +92,24 @@ public abstract class AbstractClaimToRoleMapper extends AbstractClaimMapper {
protected abstract boolean applies(final IdentityProviderMapperModel mapperModel, final BrokeredIdentityContext context); protected abstract boolean applies(final IdentityProviderMapperModel mapperModel, final BrokeredIdentityContext context);
/** /**
* Obtains the {@link RoleModel} corresponding the role configured in the specified {@link IdentityProviderMapperModel}. * Obtains the {@link RoleModel} corresponding the role configured in the specified
* 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 * {@link IdentityProviderMapperModel}.
* an {@link IdentityBrokerException} to convey that an invalid role was configured. * 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 realm a reference to the realm.
* @param mapperModel a reference to the {@link IdentityProviderMapperModel} containing the configured role. * @param mapperModel a reference to the {@link IdentityProviderMapperModel} containing the configured role.
* @return the {@link RoleModel} that corresponds to the mapper model role. * @return the {@link RoleModel} that corresponds to the mapper model role; {@code null}, when role was not found
* @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.
*/ */
private RoleModel getRole(final RealmModel realm, final IdentityProviderMapperModel mapperModel) { private RoleModel getRole(final RealmModel realm, final IdentityProviderMapperModel mapperModel) {
String roleName = mapperModel.getConfig().get(ConfigConstants.ROLE); String roleName = mapperModel.getConfig().get(ConfigConstants.ROLE);
RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName); RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName);
if (role == null) { 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; return role;
} }
} }

View file

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

View file

@ -17,6 +17,7 @@
package org.keycloak.broker.provider; package org.keycloak.broker.provider;
import org.jboss.logging.Logger;
import org.keycloak.models.IdentityProviderMapperModel; import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderSyncMode; import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
@ -37,8 +38,12 @@ import java.util.Set;
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class HardcodedRoleMapper extends AbstractIdentityProviderMapper { public class HardcodedRoleMapper extends AbstractIdentityProviderMapper {
protected static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>(); protected static final List<ProviderConfigProperty> configProperties = new ArrayList<>();
private static final Set<IdentityProviderSyncMode> IDENTITY_PROVIDER_SYNC_MODES = new HashSet<>(Arrays.asList(IdentityProviderSyncMode.values()));
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 { static {
ProviderConfigProperty property; ProviderConfigProperty property;
@ -91,10 +96,22 @@ public class HardcodedRoleMapper extends AbstractIdentityProviderMapper {
} }
private void grantUserRole(RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel) { 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); String roleName = mapperModel.getConfig().get(ConfigConstants.ROLE);
RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName); 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 @Override

View file

@ -16,10 +16,10 @@
*/ */
package org.keycloak.broker.saml.mappers; package org.keycloak.broker.saml.mappers;
import org.jboss.logging.Logger;
import org.keycloak.broker.provider.AbstractIdentityProviderMapper; import org.keycloak.broker.provider.AbstractIdentityProviderMapper;
import org.keycloak.broker.provider.BrokeredIdentityContext; import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.broker.provider.ConfigConstants; import org.keycloak.broker.provider.ConfigConstants;
import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.models.IdentityProviderMapperModel; import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; 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 * 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. * 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 { public abstract class AbstractAttributeToRoleMapper extends AbstractIdentityProviderMapper {
private static final Logger LOG = Logger.getLogger(AbstractAttributeToRoleMapper.class);
@Override @Override
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
RoleModel role = this.getRole(realm, mapperModel); RoleModel role = this.getRole(realm, mapperModel);
if (role == null) {
return;
}
if (this.applies(mapperModel, context)) { if (this.applies(mapperModel, context)) {
user.grantRole(role); user.grantRole(role);
} }
@ -46,6 +53,10 @@ public abstract class AbstractAttributeToRoleMapper extends AbstractIdentityProv
@Override @Override
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
RoleModel role = this.getRole(realm, mapperModel); RoleModel role = this.getRole(realm, mapperModel);
if (role == null) {
return;
}
String roleName = mapperModel.getConfig().get(ConfigConstants.ROLE); 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. // 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)) { if (!context.hasMapperGrantedRole(roleName)) {
@ -69,21 +80,22 @@ public abstract class AbstractAttributeToRoleMapper extends AbstractIdentityProv
protected abstract boolean applies(final IdentityProviderMapperModel mapperModel, final BrokeredIdentityContext context); protected abstract boolean applies(final IdentityProviderMapperModel mapperModel, final BrokeredIdentityContext context);
/** /**
* Obtains the {@link RoleModel} corresponding the role configured in the specified {@link IdentityProviderMapperModel}. * Obtains the {@link RoleModel} corresponding the role configured in the specified
* 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 * {@link IdentityProviderMapperModel}.
* an {@link IdentityBrokerException} to convey that an invalid role was configured. * 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 realm a reference to the realm.
* @param mapperModel a reference to the {@link IdentityProviderMapperModel} containing the configured role. * @param mapperModel a reference to the {@link IdentityProviderMapperModel} containing the configured role.
* @return the {@link RoleModel} that corresponds to the mapper model role. * @return the {@link RoleModel} that corresponds to the mapper model role or {@code null}, if the role could not be
* @throws IdentityBrokerException if the role name doesn't correspond to one of the realm's client roles or to one * found
* of the realm's roles.
*/ */
private RoleModel getRole(final RealmModel realm, final IdentityProviderMapperModel mapperModel) { private RoleModel getRole(final RealmModel realm, final IdentityProviderMapperModel mapperModel) {
String roleName = mapperModel.getConfig().get(ConfigConstants.ROLE); String roleName = mapperModel.getConfig().get(ConfigConstants.ROLE);
RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName); RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName);
if (role == null) { 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; 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); 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); RepresentationToModel.updateClientProtocolMappers(rep, client);
if (rep.getDefaultRoles() != null) { if (rep.getDefaultRoles() != null) {

View file

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

View file

@ -26,6 +26,7 @@ import org.keycloak.models.Constants;
import org.keycloak.models.GroupModel; import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.representations.idm.GroupRepresentation; import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.ManagementPermissionReference; 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(); adminEvent.operation(OperationType.UPDATE).resourcePath(session.getContext().getUri()).representation(rep).success();
return Response.noContent().build(); return Response.noContent().build();
@ -167,7 +168,7 @@ public class GroupResource {
adminEvent.operation(OperationType.UPDATE); adminEvent.operation(OperationType.UPDATE);
} else { } else {
child = realm.createGroup(groupName, group); child = realm.createGroup(groupName, group);
updateGroup(rep, child); updateGroup(rep, child, realm, session);
URI uri = session.getContext().getUri().getBaseUriBuilder() URI uri = session.getContext().getUri().getBaseUriBuilder()
.path(session.getContext().getUri().getMatchedURIs().get(2)) .path(session.getContext().getUri().getMatchedURIs().get(2))
.path(child.getId()).build(); .path(child.getId()).build();
@ -182,8 +183,42 @@ public class GroupResource {
return builder.type(MediaType.APPLICATION_JSON_TYPE).entity(childRep).build(); return builder.type(MediaType.APPLICATION_JSON_TYPE).entity(childRep).build();
} }
public static void updateGroup(GroupRepresentation rep, GroupModel model) { public static void updateGroup(GroupRepresentation rep, GroupModel model, RealmModel realm, KeycloakSession session) {
if (rep.getName() != null) model.setName(rep.getName()); 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) { if (rep.getAttributes() != null) {
Set<String> attrsToRemove = new HashSet<>(model.getAttributes().keySet()); 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()); adminEvent.operation(OperationType.UPDATE).resourcePath(session.getContext().getUri());
} else { } else {
child = realm.createGroup(groupName); child = realm.createGroup(groupName);
GroupResource.updateGroup(rep, child); GroupResource.updateGroup(rep, child, realm, session);
URI uri = session.getContext().getUri().getAbsolutePathBuilder() URI uri = session.getContext().getUri().getAbsolutePathBuilder()
.path(child.getId()).build(); .path(child.getId()).build();
builder.status(201).location(uri); 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) { public void updateRole(final @PathParam("role-id") String id, final RoleRepresentation rep) {
RoleModel role = getRoleModel(id); RoleModel role = getRoleModel(id);
auth.roles().requireManage(role); auth.roles().requireManage(role);
updateRole(rep, role); updateRole(rep, role, realm, session);
if (role.isClientRole()) { if (role.isClientRole()) {
adminEvent.resource(ResourceType.CLIENT_ROLE); adminEvent.resource(ResourceType.CLIENT_ROLE);

View file

@ -269,7 +269,7 @@ public class RoleContainerResource extends RoleResource {
throw new NotFoundException("Could not find role"); throw new NotFoundException("Could not find role");
} }
try { try {
updateRole(rep, role); updateRole(rep, role, realm, session);
if (role.isClientRole()) { if (role.isClientRole()) {
adminEvent.resource(ResourceType.CLIENT_ROLE); 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.OperationType;
import org.keycloak.events.admin.ResourceType; import org.keycloak.events.admin.ResourceType;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel; import org.keycloak.models.RoleModel;
import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.models.utils.ModelToRepresentation;
@ -57,8 +58,45 @@ public abstract class RoleResource {
} }
} }
protected void updateRole(RoleRepresentation rep, RoleModel role) { protected void updateRole(RoleRepresentation rep, RoleModel role, RealmModel realm,
role.setName(rep.getName()); 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()); role.setDescription(rep.getDescription());
if (rep.getAttributes() != null) { 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.FORCE;
import static org.keycloak.models.IdentityProviderMapperSyncMode.IMPORT; import static org.keycloak.models.IdentityProviderMapperSyncMode.IMPORT;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.keycloak.models.IdentityProviderMapperSyncMode; import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
@ -14,7 +12,12 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import java.util.List; 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 { public abstract class AbstractAdvancedGroupMapperTest extends AbstractGroupMapperTest {
private static final String CLAIMS_OR_ATTRIBUTES = "[\n" + private static final String CLAIMS_OR_ATTRIBUTES = "[\n" +
@ -41,24 +44,12 @@ public abstract class AbstractAdvancedGroupMapperTest extends AbstractGroupMappe
private String newValueForAttribute2 = ""; 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 @Test
public void allValuesMatch() { public void allValuesMatch() {
createAdvancedGroupMapper(CLAIMS_OR_ATTRIBUTES, false); createAdvancedGroupMapper(CLAIMS_OR_ATTRIBUTES, false, MAPPER_TEST_GROUP_PATH);
createUserInProviderRealm(ImmutableMap.<String, List<String>>builder() createUserInProviderRealm(createMatchingAttributes());
.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());
logInAsUserInIDPForFirstTime(); logInAsUserInIDPForFirstTimeAndAssertSuccess();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail()); UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
assertThatUserHasBeenAssignedToGroup(user); assertThatUserHasBeenAssignedToGroup(user);
@ -66,13 +57,13 @@ public abstract class AbstractAdvancedGroupMapperTest extends AbstractGroupMappe
@Test @Test
public void valuesMismatch() { public void valuesMismatch() {
createAdvancedGroupMapper(CLAIMS_OR_ATTRIBUTES, false); createAdvancedGroupMapper(CLAIMS_OR_ATTRIBUTES, false, MAPPER_TEST_GROUP_PATH);
createUserInProviderRealm(ImmutableMap.<String, List<String>>builder() 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, ImmutableList.<String>builder().add("value 1").build())
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2, ImmutableList.<String>builder().add("value mismatch").build()) .put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2, ImmutableList.<String>builder().add("value mismatch").build())
.build()); .build());
logInAsUserInIDPForFirstTime(); logInAsUserInIDPForFirstTimeAndAssertSuccess();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail()); UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
assertThatUserHasNotBeenAssignedToGroup(user); assertThatUserHasNotBeenAssignedToGroup(user);
@ -80,13 +71,13 @@ public abstract class AbstractAdvancedGroupMapperTest extends AbstractGroupMappe
@Test @Test
public void valuesMatchIfNoClaimsSpecified() { public void valuesMatchIfNoClaimsSpecified() {
createAdvancedGroupMapper("[]", false); createAdvancedGroupMapper("[]", false, MAPPER_TEST_GROUP_PATH);
createUserInProviderRealm(ImmutableMap.<String, List<String>>builder() 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, ImmutableList.<String>builder().add("some value").build())
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2, ImmutableList.<String>builder().add("some value").build()) .put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2, ImmutableList.<String>builder().add("some value").build())
.build()); .build());
logInAsUserInIDPForFirstTime(); logInAsUserInIDPForFirstTimeAndAssertSuccess();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail()); UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
assertThatUserHasBeenAssignedToGroup(user); assertThatUserHasBeenAssignedToGroup(user);
@ -94,13 +85,10 @@ public abstract class AbstractAdvancedGroupMapperTest extends AbstractGroupMappe
@Test @Test
public void allValuesMatchRegex() { public void allValuesMatchRegex() {
createAdvancedGroupMapper(CLAIMS_OR_ATTRIBUTES_REGEX, true); createAdvancedGroupMapper(CLAIMS_OR_ATTRIBUTES_REGEX, true, MAPPER_TEST_GROUP_PATH);
createUserInProviderRealm(ImmutableMap.<String, List<String>>builder() createUserInProviderRealm(createMatchingAttributes());
.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());
logInAsUserInIDPForFirstTime(); logInAsUserInIDPForFirstTimeAndAssertSuccess();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail()); UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
assertThatUserHasBeenAssignedToGroup(user); assertThatUserHasBeenAssignedToGroup(user);
@ -108,13 +96,13 @@ public abstract class AbstractAdvancedGroupMapperTest extends AbstractGroupMappe
@Test @Test
public void valuesMismatchRegex() { 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() createUserInProviderRealm(ImmutableMap.<String, List<String>>builder()
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("mismatch").build()) .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()) .put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2, ImmutableList.<String>builder().add("value 2").build())
.build()); .build());
logInAsUserInIDPForFirstTime(); logInAsUserInIDPForFirstTimeAndAssertSuccess();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail()); UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
assertThatUserHasNotBeenAssignedToGroup(user); assertThatUserHasNotBeenAssignedToGroup(user);
@ -123,7 +111,7 @@ public abstract class AbstractAdvancedGroupMapperTest extends AbstractGroupMappe
@Test @Test
public void updateBrokeredUserMismatchLeavesGroup() { public void updateBrokeredUserMismatchLeavesGroup() {
newValueForAttribute2 = "value mismatch"; newValueForAttribute2 = "value mismatch";
UserRepresentation user = createMapperAndLoginAsUserTwiceWithMapper(FORCE, false); UserRepresentation user = createMapperAndLoginAsUserTwiceWithMapper(FORCE, false, MAPPER_TEST_GROUP_PATH);
assertThatUserHasNotBeenAssignedToGroup(user); assertThatUserHasNotBeenAssignedToGroup(user);
} }
@ -131,7 +119,7 @@ public abstract class AbstractAdvancedGroupMapperTest extends AbstractGroupMappe
@Test @Test
public void updateBrokeredUserMismatchDoesNotLeaveGroupInImportMode() { public void updateBrokeredUserMismatchDoesNotLeaveGroupInImportMode() {
newValueForAttribute2 = "value mismatch"; newValueForAttribute2 = "value mismatch";
UserRepresentation user = createMapperAndLoginAsUserTwiceWithMapper(IMPORT, false); UserRepresentation user = createMapperAndLoginAsUserTwiceWithMapper(IMPORT, false, MAPPER_TEST_GROUP_PATH);
assertThatUserHasBeenAssignedToGroup(user); assertThatUserHasBeenAssignedToGroup(user);
} }
@ -139,24 +127,31 @@ public abstract class AbstractAdvancedGroupMapperTest extends AbstractGroupMappe
@Test @Test
public void updateBrokeredUserMatchDoesntLeaveGroup() { public void updateBrokeredUserMatchDoesntLeaveGroup() {
newValueForAttribute2 = "value 2"; newValueForAttribute2 = "value 2";
UserRepresentation user = createMapperAndLoginAsUserTwiceWithMapper(FORCE, false); UserRepresentation user = createMapperAndLoginAsUserTwiceWithMapper(FORCE, false, MAPPER_TEST_GROUP_PATH);
assertThatUserHasBeenAssignedToGroup(user); assertThatUserHasBeenAssignedToGroup(user);
} }
@Test
public void tryToUpdateBrokeredUserWithMissingGroupDoesNotBreakLogin() {
newValueForAttribute2 = "value 2";
UserRepresentation user =
createMapperAndLoginAsUserTwiceWithMapper(FORCE, true, MAPPER_TEST_NOT_EXISTING_GROUP_PATH);
assertThatUserDoesNotHaveGroups(user);
}
@Test @Test
public void updateBrokeredUserIsAssignedToGroupInForceModeWhenCreatingTheMapperAfterFirstLogin() { public void updateBrokeredUserIsAssignedToGroupInForceModeWhenCreatingTheMapperAfterFirstLogin() {
newValueForAttribute2 = "value 2"; newValueForAttribute2 = "value 2";
UserRepresentation user = createMapperAndLoginAsUserTwiceWithMapper(FORCE, true); UserRepresentation user = createMapperAndLoginAsUserTwiceWithMapper(FORCE, true, MAPPER_TEST_GROUP_PATH);
assertThatUserHasBeenAssignedToGroup(user); assertThatUserHasBeenAssignedToGroup(user);
} }
public UserRepresentation createMapperAndLoginAsUserTwiceWithMapper(IdentityProviderMapperSyncMode syncMode, boolean createAfterFirstLogin) { public UserRepresentation createMapperAndLoginAsUserTwiceWithMapper(IdentityProviderMapperSyncMode syncMode,
return loginAsUserTwiceWithMapper(syncMode, createAfterFirstLogin, ImmutableMap.<String, List<String>>builder() boolean createAfterFirstLogin, String groupPath) {
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("value 1").build()) return loginAsUserTwiceWithMapper(syncMode, createAfterFirstLogin, createMatchingAttributes(), groupPath);
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2, ImmutableList.<String>builder().add("value 2").build())
.build());
} }
@Override @Override
@ -171,16 +166,44 @@ public abstract class AbstractAdvancedGroupMapperTest extends AbstractGroupMappe
adminClient.realm(bc.providerRealmName()).users().get(user.getId()).update(user); adminClient.realm(bc.providerRealmName()).users().get(user.getId()).update(user);
} }
@Override @Override
protected void createMapperInIdp(IdentityProviderRepresentation idp, IdentityProviderMapperSyncMode syncMode) { protected String createMapperInIdp(IdentityProviderRepresentation idp, IdentityProviderMapperSyncMode syncMode,
createMapperInIdp(idp, CLAIMS_OR_ATTRIBUTES, false, 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(); IdentityProviderRepresentation idp = setupIdentityProvider();
createMapperInIdp(idp, claimsOrAttributeRepresentation, areClaimsOrAttributeValuesRegexes, IMPORT); return createMapperInIdp(idp, claimsOrAttributeRepresentation, areClaimsOrAttributeValuesRegexes, IMPORT,
groupPath);
} }
abstract protected void createMapperInIdp( abstract protected String createMapperInIdp(
IdentityProviderRepresentation idp, String claimsOrAttributeRepresentation, boolean areClaimsOrAttributeValuesRegexes, IdentityProviderMapperSyncMode syncMode); 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; 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.FORCE;
import static org.keycloak.models.IdentityProviderMapperSyncMode.IMPORT; 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, * @author hmlnarik,
* <a href="mailto:external.benjamin.weimer@bosch-si.com">Benjamin Weimer</a>, * <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 { public abstract class AbstractAdvancedRoleMapperTest extends AbstractRoleMapperTest {
@ -46,134 +48,145 @@ public abstract class AbstractAdvancedRoleMapperTest extends AbstractRoleMapperT
@Test @Test
public void allValuesMatch() { public void allValuesMatch() {
createAdvancedRoleMapper(CLAIMS_OR_ATTRIBUTES, false); createAdvancedRoleMapper(CLAIMS_OR_ATTRIBUTES, false);
createUserInProviderRealm(ImmutableMap.<String, List<String>>builder() createUserInProviderRealm(createMatchingUserConfig());
.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());
logInAsUserInIDPForFirstTime(); logInAsUserInIDPForFirstTime();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail()); assertThatRoleHasBeenAssignedInConsumerRealm();
assertThatRoleHasBeenAssignedInConsumerRealmTo(user);
} }
@Test @Test
public void valuesMismatch() { public void valuesMismatch() {
createAdvancedRoleMapper(CLAIMS_OR_ATTRIBUTES, false); createAdvancedRoleMapper(CLAIMS_OR_ATTRIBUTES, false);
createUserInProviderRealm(ImmutableMap.<String, List<String>>builder() 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,
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2, ImmutableList.<String>builder().add("value mismatch").build()) ImmutableList.<String> builder().add("value 1").build())
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2,
ImmutableList.<String> builder().add("value mismatch").build())
.build()); .build());
logInAsUserInIDPForFirstTime(); logInAsUserInIDPForFirstTime();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail()); assertThatRoleHasNotBeenAssignedInConsumerRealm();
assertThatRoleHasNotBeenAssignedInConsumerRealmTo(user);
} }
@Test @Test
public void valuesMatchIfNoClaimsSpecified() { public void valuesMatchIfNoClaimsSpecified() {
createAdvancedRoleMapper("[]", false); createAdvancedRoleMapper("[]", false);
createUserInProviderRealm(ImmutableMap.<String, List<String>>builder() 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,
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2, ImmutableList.<String>builder().add("some value").build()) ImmutableList.<String> builder().add("some value").build())
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2,
ImmutableList.<String> builder().add("some value").build())
.build()); .build());
logInAsUserInIDPForFirstTime(); logInAsUserInIDPForFirstTime();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail()); assertThatRoleHasBeenAssignedInConsumerRealm();
assertThatRoleHasBeenAssignedInConsumerRealmTo(user);
} }
@Test @Test
public void allValuesMatchRegex() { public void allValuesMatchRegex() {
createAdvancedRoleMapper(CLAIMS_OR_ATTRIBUTES_REGEX, true); createAdvancedRoleMapper(CLAIMS_OR_ATTRIBUTES_REGEX, true);
createUserInProviderRealm(ImmutableMap.<String, List<String>>builder() createUserInProviderRealm(createMatchingUserConfig());
.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());
logInAsUserInIDPForFirstTime(); logInAsUserInIDPForFirstTime();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail()); assertThatRoleHasBeenAssignedInConsumerRealm();
assertThatRoleHasBeenAssignedInConsumerRealmTo(user);
} }
@Test @Test
public void valuesMismatchRegex() { public void valuesMismatchRegex() {
createAdvancedRoleMapper(CLAIMS_OR_ATTRIBUTES_REGEX, true); createAdvancedRoleMapper(CLAIMS_OR_ATTRIBUTES_REGEX, true);
createUserInProviderRealm(ImmutableMap.<String, List<String>>builder() createUserInProviderRealm(ImmutableMap.<String, List<String>> builder()
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("mismatch").build()) .put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME,
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2, ImmutableList.<String>builder().add("value 2").build()) ImmutableList.<String> builder().add("mismatch").build())
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2,
ImmutableList.<String> builder().add("value 2").build())
.build()); .build());
logInAsUserInIDPForFirstTime(); logInAsUserInIDPForFirstTime();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail()); assertThatRoleHasNotBeenAssignedInConsumerRealm();
assertThatRoleHasNotBeenAssignedInConsumerRealmTo(user);
} }
@Test @Test
public void updateBrokeredUserMismatchDeletesRole() { public void updateBrokeredUserMismatchDeletesRole() {
newValueForAttribute2 = "value mismatch"; newValueForAttribute2 = "value mismatch";
UserRepresentation user = createMapperAndLoginAsUserTwiceWithMapper(FORCE, false); createMapperAndLoginAsUserTwiceWithMapper(FORCE, false);
assertThatRoleHasNotBeenAssignedInConsumerRealmTo(user); assertThatRoleHasNotBeenAssignedInConsumerRealm();
} }
@Test @Test
public void updateBrokeredUserMismatchDoesNotDeleteRoleInImportMode() { public void updateBrokeredUserMismatchDoesNotDeleteRoleInImportMode() {
newValueForAttribute2 = "value mismatch"; newValueForAttribute2 = "value mismatch";
UserRepresentation user = createMapperAndLoginAsUserTwiceWithMapper(IMPORT, false); createMapperAndLoginAsUserTwiceWithMapper(IMPORT, false);
assertThatRoleHasBeenAssignedInConsumerRealmTo(user); assertThatRoleHasBeenAssignedInConsumerRealm();
} }
@Test @Test
public void updateBrokeredUserMatchDoesntDeleteRole() { public void updateBrokeredUserMatchDoesntDeleteRole() {
newValueForAttribute2 = "value 2"; newValueForAttribute2 = "value 2";
UserRepresentation user = createMapperAndLoginAsUserTwiceWithMapper(FORCE, false); createMapperAndLoginAsUserTwiceWithMapper(FORCE, false);
assertThatRoleHasBeenAssignedInConsumerRealmTo(user); assertThatRoleHasBeenAssignedInConsumerRealm();
} }
@Test @Test
public void updateBrokeredUserAssignsRoleInForceModeWhenCreatingTheMapperAfterFirstLogin() { public void updateBrokeredUserAssignsRoleInForceModeWhenCreatingTheMapperAfterFirstLogin() {
newValueForAttribute2 = "value 2"; newValueForAttribute2 = "value 2";
UserRepresentation user = createMapperAndLoginAsUserTwiceWithMapper(FORCE, true); createMapperAndLoginAsUserTwiceWithMapper(FORCE, true);
assertThatRoleHasBeenAssignedInConsumerRealmTo(user); assertThatRoleHasBeenAssignedInConsumerRealm();
} }
public UserRepresentation createMapperAndLoginAsUserTwiceWithMapper(IdentityProviderMapperSyncMode syncMode, boolean createAfterFirstLogin) { public void createMapperAndLoginAsUserTwiceWithMapper(IdentityProviderMapperSyncMode syncMode,
return loginAsUserTwiceWithMapper(syncMode, createAfterFirstLogin, ImmutableMap.<String, List<String>>builder() boolean createAfterFirstLogin) {
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("value 1").build()) loginAsUserTwiceWithMapper(syncMode, createAfterFirstLogin, createMatchingUserConfig());
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2, ImmutableList.<String>builder().add("value 2").build())
.build());
} }
@Override @Override
protected void updateUser() { protected void updateUser() {
UserRepresentation user = findUser(bc.providerRealmName(), bc.getUserLogin(), bc.getUserEmail()); UserRepresentation user = findUser(bc.providerRealmName(), bc.getUserLogin(), bc.getUserEmail());
ImmutableMap<String, List<String>> matchingAttributes = ImmutableMap.<String, List<String>>builder() 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,
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2, ImmutableList.<String>builder().add(newValueForAttribute2).build()) ImmutableList.<String> builder().add("value 1").build())
.put("some.other.attribute", ImmutableList.<String>builder().add("some value").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(); .build();
user.setAttributes(matchingAttributes); user.setAttributes(matchingAttributes);
adminClient.realm(bc.providerRealmName()).users().get(user.getId()).update(user); adminClient.realm(bc.providerRealmName()).users().get(user.getId()).update(user);
} }
@Override @Override
protected void createMapperInIdp(IdentityProviderRepresentation idp, IdentityProviderMapperSyncMode syncMode) { protected void createMapperInIdp(IdentityProviderMapperSyncMode syncMode, String roleValue) {
createMapperInIdp(idp, CLAIMS_OR_ATTRIBUTES, false, syncMode); createMapperInIdp(CLAIMS_OR_ATTRIBUTES, false, syncMode, roleValue);
} }
protected void createAdvancedRoleMapper(String claimsOrAttributeRepresentation, boolean areClaimsOrAttributeValuesRegexes) { @Override
IdentityProviderRepresentation idp = setupIdentityProvider(); protected Map<String, List<String>> createUserConfigForRole(String roleValue) {
createMapperInIdp(idp, claimsOrAttributeRepresentation, areClaimsOrAttributeValuesRegexes, IMPORT); return createMatchingUserConfig();
} }
abstract protected void createMapperInIdp( private static Map<String, List<String>> createMatchingUserConfig() {
IdentityProviderRepresentation idp, String claimsOrAttributeRepresentation, boolean areClaimsOrAttributeValuesRegexes, IdentityProviderMapperSyncMode syncMode); 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(); updateAccountInformation();
} }
protected void logInAsUserInIDPForFirstTimeAndAssertSuccess() {
logInAsUserInIDPForFirstTime();
assertLoggedInAccountManagement();
}
protected void updateAccountInformation() { protected void updateAccountInformation() {
waitForPage(driver, "update account information", false); waitForPage(driver, "update account information", false);

View file

@ -1,38 +1,134 @@
package org.keycloak.testsuite.broker; package org.keycloak.testsuite.broker;
import static org.junit.Assert.assertFalse; import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertTrue; 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 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.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.IdentityProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; 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 abstract class AbstractGroupMapperTest extends AbstractIdentityProviderMapperTest {
public static final String MAPPER_TEST_GROUP_NAME = "mapper-test"; 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( public static final String MAPPER_TEST_NOT_EXISTING_GROUP_PATH = buildGroupPath("mapper-test-not-existing");
IdentityProviderRepresentation idp, IdentityProviderMapperSyncMode syncMode);
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() { 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( protected UserRepresentation loginAsUserTwiceWithMapper(
IdentityProviderMapperSyncMode syncMode, boolean createAfterFirstLogin, IdentityProviderMapperSyncMode syncMode, boolean createAfterFirstLogin,
Map<String, List<String>> userConfig) { Map<String, List<String>> userConfig, String groupPath) {
final IdentityProviderRepresentation idp = setupIdentityProvider(); final IdentityProviderRepresentation idp = setupIdentityProvider();
if (!createAfterFirstLogin) { if (!createAfterFirstLogin) {
createMapperInIdp(idp, syncMode); createMapperInIdp(idp, syncMode, groupPath);
} }
createUserInProviderRealm(userConfig); createUserInProviderRealm(userConfig);
logInAsUserInIDPForFirstTime(); logInAsUserInIDPForFirstTimeAndAssertSuccess();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail()); UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
if (!createAfterFirstLogin) { if (!createAfterFirstLogin) {
@ -42,34 +138,46 @@ public abstract class AbstractGroupMapperTest extends AbstractIdentityProviderMa
} }
if (createAfterFirstLogin) { if (createAfterFirstLogin) {
createMapperInIdp(idp, syncMode); createMapperInIdp(idp, syncMode, groupPath);
} }
logoutFromRealm(getConsumerRoot(), bc.consumerRealmName()); logoutFromRealm(getConsumerRoot(), bc.consumerRealmName());
updateUser(); updateUser();
logInAsUserInIDP(); logInAsUserInIDP();
assertLoggedInAccountManagement();
user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail()); user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
return user; return user;
} }
protected void assertThatUserHasBeenAssignedToGroup(UserRepresentation 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 -> { protected void assertThatUserHasBeenAssignedToGroup(UserRepresentation user, String groupPath) {
groupNames.add(group.getName()); assertThat(getUserGroupPaths(user), contains(groupPath));
});
assertTrue(groupNames.contains(MAPPER_TEST_GROUP_NAME));
} }
protected void assertThatUserHasNotBeenAssignedToGroup(UserRepresentation user) { 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 -> { protected void assertThatUserDoesNotHaveGroups(UserRepresentation user) {
groupNames.add(group.getName()); 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; 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.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.hamcrest.Matchers.not;
import static org.junit.Assert.assertThat;
import static org.keycloak.testsuite.broker.BrokerTestTools.getConsumerRoot; 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.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
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;
/** /**
* @author hmlnarik, * @author hmlnarik,
* <a href="mailto:external.benjamin.weimer@bosch-si.com">Benjamin Weimer</a>, * <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 AbstractRoleMapperTest extends AbstractIdentityProviderMapperTest { public abstract class AbstractRoleMapperTest extends AbstractIdentityProviderMapperTest {
private static final String CLIENT = "realm-management"; protected static final String CLIENT_ID = "mapper-test-client";
private static final String CLIENT_ROLE = "view-realm"; protected static final String CLIENT_ROLE = "test-role";
public static final String ROLE_USER = "user"; protected static final String CLIENT_ROLE_MAPPER_REPRESENTATION = createClientRoleString(CLIENT_ID, CLIENT_ROLE);
public static final String CLIENT_ROLE_MAPPER_REPRESENTATION = CLIENT + "." + 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 void updateUser() {
} }
protected UserRepresentation loginAsUserTwiceWithMapper( @Before
IdentityProviderMapperSyncMode syncMode, boolean createAfterFirstLogin, Map<String, List<String>> userConfig) { public void init() {
final IdentityProviderRepresentation idp = setupIdentityProvider(); mapperIdsWithName = new ConcurrentHashMap<>();
if (!createAfterFirstLogin) {
createMapperInIdp(idp, syncMode); 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(); logInAsUserInIDPForFirstTime();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
if (!createAfterFirstLogin) { if (!createAfterFirstLogin) {
assertThatRoleHasBeenAssignedInConsumerRealmTo(user); assertThatRoleHasBeenAssignedInConsumerRealm();
} else { } else {
assertThatRoleHasNotBeenAssignedInConsumerRealmTo(user); assertThatRoleHasNotBeenAssignedInConsumerRealm();
} }
if (createAfterFirstLogin) { if (createAfterFirstLogin) {
createMapperInIdp(idp, syncMode); createMapperInIdp(syncMode, CLIENT_ROLE_MAPPER_REPRESENTATION);
} }
logoutFromRealm(getConsumerRoot(), bc.consumerRealmName()); logoutFromRealm(getConsumerRoot(), bc.consumerRealmName());
updateUser(); updateUser();
logInAsUserInIDP(); 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() { 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); adminClient.realm(bc.providerRealmName()).roles().create(userRole);
RoleRepresentation role = adminClient.realm(bc.providerRealmName()).roles().get(ROLE_USER).toRepresentation(); RoleRepresentation role = adminClient.realm(bc.providerRealmName()).roles().get(ROLE_USER).toRepresentation();
UserResource userResource = adminClient.realm(bc.providerRealmName()).users().get(userId); UserResource userResource = adminClient.realm(bc.providerRealmName()).users().get(userId);
userResource.roles().realmLevel().add(Collections.singletonList(role)); userResource.roles().realmLevel().add(Collections.singletonList(role));
} }
protected void assertThatRoleHasBeenAssignedInConsumerRealmTo(UserRepresentation user) { private void assertLoginSucceedsWithoutRoleAssignment() {
assertThat(user.getClientRoles().get(CLIENT), contains(CLIENT_ROLE)); logInAsUserInIDPForFirstTimeAndAssertSuccess();
assertThatNoRolesHaveBeenAssignedInConsumerRealm();
} }
protected void assertThatRoleHasNotBeenAssignedInConsumerRealmTo(UserRepresentation user) { protected void assertThatRoleHasBeenAssignedInConsumerRealm() {
assertThat(user.getClientRoles().get(CLIENT), not(contains(CLIENT_ROLE))); 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.FORCE;
import static org.keycloak.models.IdentityProviderMapperSyncMode.LEGACY; import static org.keycloak.models.IdentityProviderMapperSyncMode.LEGACY;
import java.util.List;
import org.junit.Test; 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.AttributeToRoleMapper;
import org.keycloak.broker.saml.mappers.UserAttributeMapper; import org.keycloak.broker.saml.mappers.UserAttributeMapper;
import org.keycloak.models.IdentityProviderMapperModel; import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderMapperSyncMode; import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation; 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.ImmutableList;
import com.google.common.collect.ImmutableMap; 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>
*/ */
public class AttributeToRoleMapperTest extends AbstractRoleMapperTest { public class AttributeToRoleMapperTest extends AbstractRoleMapperTest {
private static final String ROLE_ATTR_NAME = "Role";
@Override @Override
protected BrokerConfiguration getBrokerConfiguration() { protected BrokerConfiguration getBrokerConfiguration() {
return new KcSamlBrokerConfiguration(); return new KcSamlBrokerConfiguration();
@ -31,53 +31,53 @@ public class AttributeToRoleMapperTest extends AbstractRoleMapperTest {
@Test @Test
public void mapperGrantsRoleOnFirstLogin() { public void mapperGrantsRoleOnFirstLogin() {
UserRepresentation user = createMapperThenLoginAsUserTwiceWithAttributeToRoleMapper(FORCE); createMapperThenLoginAsUserTwiceWithAttributeToRoleMapper();
assertThatRoleHasBeenAssignedInConsumerRealmTo(user); assertThatRoleHasBeenAssignedInConsumerRealm();
} }
@Test @Test
public void updateBrokeredUserGrantsRoleInLegacyMode() { public void updateBrokeredUserGrantsRoleInLegacyMode() {
UserRepresentation user = loginAsUserThenCreateMapperAndLoginAgainWithAttributeToRoleMapper(LEGACY); loginAsUserThenCreateMapperAndLoginAgainWithAttributeToRoleMapper(LEGACY);
assertThatRoleHasBeenAssignedInConsumerRealmTo(user); assertThatRoleHasBeenAssignedInConsumerRealm();
} }
@Test @Test
public void updateBrokeredUserGrantsRoleInForceMode() { public void updateBrokeredUserGrantsRoleInForceMode() {
UserRepresentation user = loginAsUserThenCreateMapperAndLoginAgainWithAttributeToRoleMapper(FORCE); loginAsUserThenCreateMapperAndLoginAgainWithAttributeToRoleMapper(FORCE);
assertThatRoleHasBeenAssignedInConsumerRealmTo(user); assertThatRoleHasBeenAssignedInConsumerRealm();
} }
private UserRepresentation createMapperThenLoginAsUserTwiceWithAttributeToRoleMapper(IdentityProviderMapperSyncMode syncMode) { private void createMapperThenLoginAsUserTwiceWithAttributeToRoleMapper() {
return loginAsUserTwiceWithMapper(syncMode, false, loginAsUserTwiceWithMapper(IdentityProviderMapperSyncMode.FORCE, false,
ImmutableMap.<String, List<String>>builder() createUserConfigForRole(CLIENT_ROLE_MAPPER_REPRESENTATION));
.put("Role", ImmutableList.<String>builder().add(CLIENT_ROLE_MAPPER_REPRESENTATION).build())
.build());
} }
private UserRepresentation loginAsUserThenCreateMapperAndLoginAgainWithAttributeToRoleMapper(IdentityProviderMapperSyncMode syncMode) { private void loginAsUserThenCreateMapperAndLoginAgainWithAttributeToRoleMapper(
return loginAsUserTwiceWithMapper(syncMode, true, IdentityProviderMapperSyncMode syncMode) {
ImmutableMap.<String, List<String>>builder() loginAsUserTwiceWithMapper(syncMode, true, createUserConfigForRole(CLIENT_ROLE_MAPPER_REPRESENTATION));
.put("Role", ImmutableList.<String>builder().add(CLIENT_ROLE_MAPPER_REPRESENTATION).build())
.build());
} }
@Override @Override
protected void createMapperInIdp(IdentityProviderRepresentation idp, IdentityProviderMapperSyncMode syncMode) { protected void createMapperInIdp(IdentityProviderMapperSyncMode syncMode, String roleValue) {
IdentityProviderMapperRepresentation samlAttributeToRoleMapper = new IdentityProviderMapperRepresentation(); IdentityProviderMapperRepresentation samlAttributeToRoleMapper = new IdentityProviderMapperRepresentation();
samlAttributeToRoleMapper.setName("user-role-mapper"); samlAttributeToRoleMapper.setName("user-role-mapper");
samlAttributeToRoleMapper.setIdentityProviderMapper(AttributeToRoleMapper.PROVIDER_ID); samlAttributeToRoleMapper.setIdentityProviderMapper(AttributeToRoleMapper.PROVIDER_ID);
samlAttributeToRoleMapper.setConfig(ImmutableMap.<String,String>builder() samlAttributeToRoleMapper.setConfig(ImmutableMap.<String, String> builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString()) .put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(UserAttributeMapper.ATTRIBUTE_NAME, "Role") .put(UserAttributeMapper.ATTRIBUTE_NAME, ROLE_ATTR_NAME)
.put(ATTRIBUTE_VALUE, ROLE_USER) .put(ATTRIBUTE_VALUE, ROLE_USER)
.put("role", CLIENT_ROLE_MAPPER_REPRESENTATION) .put(ConfigConstants.ROLE, roleValue)
.build()); .build());
IdentityProviderResource idpResource = realm.identityProviders().get(idp.getAlias()); persistMapper(samlAttributeToRoleMapper);
samlAttributeToRoleMapper.setIdentityProviderAlias(bc.getIDPAlias()); }
idpResource.addMapper(samlAttributeToRoleMapper).close();
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; 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(); waitForPageToLoad();
WebDriverWait wait = new WebDriverWait(driver, 5); 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); wait.until(condition);
} }

View file

@ -1,30 +1,30 @@
package org.keycloak.testsuite.broker; package org.keycloak.testsuite.broker;
import java.util.Collections; import static org.keycloak.models.IdentityProviderMapperSyncMode.FORCE;
import java.util.List; import static org.keycloak.models.IdentityProviderMapperSyncMode.IMPORT;
import static org.keycloak.models.IdentityProviderMapperSyncMode.LEGACY;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; 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.admin.client.resource.UserResource;
import org.keycloak.broker.oidc.mappers.ExternalKeycloakRoleToRoleMapper; import org.keycloak.broker.oidc.mappers.ExternalKeycloakRoleToRoleMapper;
import org.keycloak.broker.provider.ConfigConstants;
import org.keycloak.models.IdentityProviderMapperModel; import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderMapperSyncMode; import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation; import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import com.google.common.collect.ImmutableMap; 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 { public class ExternalKeycloakRoleToRoleMapperTest extends AbstractRoleMapperTest {
private RealmResource realm;
private boolean deleteRoleFromUser = true; private boolean deleteRoleFromUser = true;
@Override @Override
@ -35,67 +35,66 @@ public class ExternalKeycloakRoleToRoleMapperTest extends AbstractRoleMapperTest
@Before @Before
public void setupRealm() { public void setupRealm() {
super.addClients(); super.addClients();
realm = adminClient.realm(bc.consumerRealmName());
} }
@Test @Test
public void mapperGrantsRoleOnFirstLogin() { public void mapperGrantsRoleOnFirstLogin() {
UserRepresentation user = createMapperThenLoginAsUserTwiceWithExternalKeycloakRoleToRoleMapper(IMPORT); createMapperThenLoginAsUserTwiceWithExternalKeycloakRoleToRoleMapper(IMPORT);
assertThatRoleHasBeenAssignedInConsumerRealmTo(user); assertThatRoleHasBeenAssignedInConsumerRealm();
} }
@Test @Test
public void updateBrokeredUserDoesNotGrantRoleInLegacyMode() { public void updateBrokeredUserDoesNotGrantRoleInLegacyMode() {
UserRepresentation user = loginAsUserThenCreateMapperAndLoginAgainWithExternalKeycloakRoleToRoleMapper(LEGACY); loginAsUserThenCreateMapperAndLoginAgainWithExternalKeycloakRoleToRoleMapper(LEGACY);
assertThatRoleHasNotBeenAssignedInConsumerRealmTo(user); assertThatRoleHasNotBeenAssignedInConsumerRealm();
} }
@Test @Test
public void updateBrokeredUserGrantsRoleInForceMode() { public void updateBrokeredUserGrantsRoleInForceMode() {
UserRepresentation user = loginAsUserThenCreateMapperAndLoginAgainWithExternalKeycloakRoleToRoleMapper(FORCE); loginAsUserThenCreateMapperAndLoginAgainWithExternalKeycloakRoleToRoleMapper(FORCE);
assertThatRoleHasBeenAssignedInConsumerRealmTo(user); assertThatRoleHasBeenAssignedInConsumerRealm();
} }
@Test @Test
public void updateBrokeredUserMatchDeletesRoleInForceMode() { public void updateBrokeredUserMatchDeletesRoleInForceMode() {
UserRepresentation user = createMapperThenLoginAsUserTwiceWithExternalKeycloakRoleToRoleMapper(FORCE); createMapperThenLoginAsUserTwiceWithExternalKeycloakRoleToRoleMapper(FORCE);
assertThatRoleHasNotBeenAssignedInConsumerRealmTo(user); assertThatRoleHasNotBeenAssignedInConsumerRealm();
} }
@Test @Test
public void updateBrokeredUserMatchDoesNotDeleteRoleInLegacyMode() { public void updateBrokeredUserMatchDoesNotDeleteRoleInLegacyMode() {
UserRepresentation user = createMapperThenLoginAsUserTwiceWithExternalKeycloakRoleToRoleMapper(LEGACY); createMapperThenLoginAsUserTwiceWithExternalKeycloakRoleToRoleMapper(LEGACY);
assertThatRoleHasBeenAssignedInConsumerRealmTo(user); assertThatRoleHasBeenAssignedInConsumerRealm();
} }
private UserRepresentation createMapperThenLoginAsUserTwiceWithExternalKeycloakRoleToRoleMapper(IdentityProviderMapperSyncMode syncMode) { private void createMapperThenLoginAsUserTwiceWithExternalKeycloakRoleToRoleMapper(
return loginAsUserTwiceWithMapper(syncMode, false, ImmutableMap.<String, List<String>>builder().build()); IdentityProviderMapperSyncMode syncMode) {
loginAsUserTwiceWithMapper(syncMode, false, Collections.emptyMap());
} }
private UserRepresentation loginAsUserThenCreateMapperAndLoginAgainWithExternalKeycloakRoleToRoleMapper(IdentityProviderMapperSyncMode syncMode) { private void loginAsUserThenCreateMapperAndLoginAgainWithExternalKeycloakRoleToRoleMapper(
IdentityProviderMapperSyncMode syncMode) {
deleteRoleFromUser = false; deleteRoleFromUser = false;
return loginAsUserTwiceWithMapper(syncMode, true, ImmutableMap.<String, List<String>>builder().build()); loginAsUserTwiceWithMapper(syncMode, true, Collections.emptyMap());
} }
@Override @Override
protected void createMapperInIdp(IdentityProviderRepresentation idp, IdentityProviderMapperSyncMode syncMode) { protected void createMapperInIdp(IdentityProviderMapperSyncMode syncMode, String roleValue) {
IdentityProviderMapperRepresentation externalRoleToRoleMapper = new IdentityProviderMapperRepresentation(); IdentityProviderMapperRepresentation externalRoleToRoleMapper = new IdentityProviderMapperRepresentation();
externalRoleToRoleMapper.setName("external-keycloak-role-mapper"); externalRoleToRoleMapper.setName("external-keycloak-role-mapper");
externalRoleToRoleMapper.setIdentityProviderMapper(ExternalKeycloakRoleToRoleMapper.PROVIDER_ID); externalRoleToRoleMapper.setIdentityProviderMapper(ExternalKeycloakRoleToRoleMapper.PROVIDER_ID);
externalRoleToRoleMapper.setConfig(ImmutableMap.<String,String>builder() externalRoleToRoleMapper.setConfig(ImmutableMap.<String, String> builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString()) .put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put("external.role", ROLE_USER) .put("external.role", ROLE_USER)
.put("role", CLIENT_ROLE_MAPPER_REPRESENTATION) .put(ConfigConstants.ROLE, roleValue)
.build()); .build());
IdentityProviderResource idpResource = realm.identityProviders().get(idp.getAlias()); persistMapper(externalRoleToRoleMapper);
externalRoleToRoleMapper.setIdentityProviderAlias(bc.getIDPAlias());
idpResource.addMapper(externalRoleToRoleMapper).close();
} }
@Override @Override
@ -106,4 +105,9 @@ public class ExternalKeycloakRoleToRoleMapperTest extends AbstractRoleMapperTest
userResource.roles().realmLevel().remove(Collections.singletonList(role)); 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.IMPORT;
import static org.keycloak.models.IdentityProviderMapperSyncMode.LEGACY; import static org.keycloak.models.IdentityProviderMapperSyncMode.LEGACY;
import java.util.HashMap;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; 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.ConfigConstants;
import org.keycloak.broker.provider.HardcodedRoleMapper; import org.keycloak.broker.provider.HardcodedRoleMapper;
import org.keycloak.models.IdentityProviderMapperModel; import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderMapperSyncMode; import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation; 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 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 { public class HardcodedRoleMapperTest extends AbstractRoleMapperTest {
private RealmResource realm;
@Override @Override
protected BrokerConfiguration getBrokerConfiguration() { protected BrokerConfiguration getBrokerConfiguration() {
@ -34,64 +32,66 @@ public class HardcodedRoleMapperTest extends AbstractRoleMapperTest {
@Before @Before
public void setupRealm() { public void setupRealm() {
super.addClients(); super.addClients();
realm = adminClient.realm(bc.consumerRealmName());
} }
@Test @Test
public void mapperGrantsRoleOnFirstLogin() { public void mapperGrantsRoleOnFirstLogin() {
UserRepresentation user = createMapperThenLoginAsUserTwiceWithHardcodedRoleMapper(IMPORT); createMapperThenLoginAsUserTwiceWithHardcodedRoleMapper(IMPORT);
assertThatRoleHasBeenAssignedInConsumerRealmTo(user); assertThatRoleHasBeenAssignedInConsumerRealm();
} }
@Test @Test
public void mapperDoesNotGrantRoleInModeImportIfMapperIsAddedLater() { public void mapperDoesNotGrantRoleInModeImportIfMapperIsAddedLater() {
UserRepresentation user = loginAsUserThenCreateMapperAndLoginAgainWithHardcodedRoleMapper(IMPORT); loginAsUserThenCreateMapperAndLoginAgainWithHardcodedRoleMapper(IMPORT);
assertThatRoleHasNotBeenAssignedInConsumerRealmTo(user); assertThatRoleHasNotBeenAssignedInConsumerRealm();
} }
@Test @Test
public void updateBrokeredUserDoesNotGrantRoleInLegacyMode() { public void updateBrokeredUserDoesNotGrantRoleInLegacyMode() {
UserRepresentation user = loginAsUserThenCreateMapperAndLoginAgainWithHardcodedRoleMapper(LEGACY); loginAsUserThenCreateMapperAndLoginAgainWithHardcodedRoleMapper(LEGACY);
assertThatRoleHasNotBeenAssignedInConsumerRealmTo(user); assertThatRoleHasNotBeenAssignedInConsumerRealm();
} }
@Test @Test
public void updateBrokeredUserGrantsRoleInForceMode() { public void updateBrokeredUserGrantsRoleInForceMode() {
UserRepresentation user = loginAsUserThenCreateMapperAndLoginAgainWithHardcodedRoleMapper(FORCE); loginAsUserThenCreateMapperAndLoginAgainWithHardcodedRoleMapper(FORCE);
assertThatRoleHasBeenAssignedInConsumerRealmTo(user); assertThatRoleHasBeenAssignedInConsumerRealm();
} }
@Test @Test
public void updateBrokeredUserMatchDoesntDeleteRole() { public void updateBrokeredUserMatchDoesntDeleteRole() {
UserRepresentation user = createMapperThenLoginAsUserTwiceWithHardcodedRoleMapper(FORCE); createMapperThenLoginAsUserTwiceWithHardcodedRoleMapper(FORCE);
assertThatRoleHasBeenAssignedInConsumerRealmTo(user); assertThatRoleHasBeenAssignedInConsumerRealm();
} }
private UserRepresentation createMapperThenLoginAsUserTwiceWithHardcodedRoleMapper(IdentityProviderMapperSyncMode syncMode) { private void createMapperThenLoginAsUserTwiceWithHardcodedRoleMapper(IdentityProviderMapperSyncMode syncMode) {
return loginAsUserTwiceWithMapper(syncMode, false, new HashMap<>()); loginAsUserTwiceWithMapper(syncMode, false, Collections.emptyMap());
} }
private UserRepresentation loginAsUserThenCreateMapperAndLoginAgainWithHardcodedRoleMapper(IdentityProviderMapperSyncMode syncMode) { private void loginAsUserThenCreateMapperAndLoginAgainWithHardcodedRoleMapper(IdentityProviderMapperSyncMode syncMode) {
return loginAsUserTwiceWithMapper(syncMode, true, new HashMap<>()); loginAsUserTwiceWithMapper(syncMode, true, Collections.emptyMap());
} }
@Override @Override
protected void createMapperInIdp(IdentityProviderRepresentation idp, IdentityProviderMapperSyncMode syncMode) { protected void createMapperInIdp(IdentityProviderMapperSyncMode syncMode, String roleValue) {
IdentityProviderMapperRepresentation advancedClaimToRoleMapper = new IdentityProviderMapperRepresentation(); IdentityProviderMapperRepresentation advancedClaimToRoleMapper = new IdentityProviderMapperRepresentation();
advancedClaimToRoleMapper.setName("oidc-hardcoded-role-mapper"); advancedClaimToRoleMapper.setName("oidc-hardcoded-role-mapper");
advancedClaimToRoleMapper.setIdentityProviderMapper(HardcodedRoleMapper.PROVIDER_ID); advancedClaimToRoleMapper.setIdentityProviderMapper(HardcodedRoleMapper.PROVIDER_ID);
advancedClaimToRoleMapper.setConfig(ImmutableMap.<String, String>builder() advancedClaimToRoleMapper.setConfig(ImmutableMap.<String, String> builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString()) .put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(ConfigConstants.ROLE, CLIENT_ROLE_MAPPER_REPRESENTATION) .put(ConfigConstants.ROLE, roleValue)
.build()); .build());
IdentityProviderResource idpResource = realm.identityProviders().get(idp.getAlias()); persistMapper(advancedClaimToRoleMapper);
advancedClaimToRoleMapper.setIdentityProviderAlias(bc.getIDPAlias()); }
idpResource.addMapper(advancedClaimToRoleMapper).close();
@Override
protected Map<String, List<String>> createUserConfigForRole(String roleValue) {
return Collections.emptyMap();
} }
} }

View file

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

View file

@ -16,8 +16,6 @@
*/ */
package org.keycloak.testsuite.broker; 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.provider.ConfigConstants;
import org.keycloak.broker.saml.mappers.AdvancedAttributeToRoleMapper; import org.keycloak.broker.saml.mappers.AdvancedAttributeToRoleMapper;
import org.keycloak.broker.saml.mappers.AttributeToRoleMapper; 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.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderMapperSyncMode; import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation; 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 * 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 * 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. * 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 { public class KcSamlMultipleAttributeToRoleMappersTest extends AttributeToRoleMapperTest {
@ -57,49 +57,48 @@ public class KcSamlMultipleAttributeToRoleMappersTest extends AttributeToRoleMap
"]"; "]";
@Override @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. // 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.setName("first-role-mapper");
firstSamlAttributeToRoleMapper.setIdentityProviderMapper(AttributeToRoleMapper.PROVIDER_ID); firstSamlAttributeToRoleMapper.setIdentityProviderMapper(AttributeToRoleMapper.PROVIDER_ID);
firstSamlAttributeToRoleMapper.setConfig(ImmutableMap.<String,String>builder() firstSamlAttributeToRoleMapper.setConfig(ImmutableMap.<String, String> builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString()) .put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(UserAttributeMapper.ATTRIBUTE_NAME, "Role") .put(UserAttributeMapper.ATTRIBUTE_NAME, "Role")
.put(ATTRIBUTE_VALUE, ROLE_USER) .put(ATTRIBUTE_VALUE, ROLE_USER)
.put(ConfigConstants.ROLE, CLIENT_ROLE_MAPPER_REPRESENTATION) .put(ConfigConstants.ROLE, roleValue)
.build()); .build());
IdentityProviderResource idpResource = realm.identityProviders().get(idp.getAlias()); persistMapper(firstSamlAttributeToRoleMapper);
firstSamlAttributeToRoleMapper.setIdentityProviderAlias(bc.getIDPAlias());
idpResource.addMapper(firstSamlAttributeToRoleMapper).close();
// second mapper that maps a role the test user doesn't have - it would normally end up removing the mapped role but // second mapper that maps a role the test user doesn't have - it would normally end up removing the mapped
// it should now check if a previous mapper has already granted the same mapped role. // role, but it should now check if a previous mapper has already granted the same mapped role.
IdentityProviderMapperRepresentation secondSamlAttributeToRoleMapper = new IdentityProviderMapperRepresentation(); IdentityProviderMapperRepresentation secondSamlAttributeToRoleMapper =
new IdentityProviderMapperRepresentation();
secondSamlAttributeToRoleMapper.setName("second-role-mapper"); secondSamlAttributeToRoleMapper.setName("second-role-mapper");
secondSamlAttributeToRoleMapper.setIdentityProviderMapper(AttributeToRoleMapper.PROVIDER_ID); secondSamlAttributeToRoleMapper.setIdentityProviderMapper(AttributeToRoleMapper.PROVIDER_ID);
secondSamlAttributeToRoleMapper.setConfig(ImmutableMap.<String,String>builder() secondSamlAttributeToRoleMapper.setConfig(ImmutableMap.<String, String> builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString()) .put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(UserAttributeMapper.ATTRIBUTE_NAME, "Role") .put(UserAttributeMapper.ATTRIBUTE_NAME, "Role")
.put(ATTRIBUTE_VALUE, "missing-role") .put(ATTRIBUTE_VALUE, "missing-role")
.put(ConfigConstants.ROLE, CLIENT_ROLE_MAPPER_REPRESENTATION) .put(ConfigConstants.ROLE, roleValue)
.build()); .build());
secondSamlAttributeToRoleMapper.setIdentityProviderAlias(bc.getIDPAlias()); persistMapper(secondSamlAttributeToRoleMapper);
idpResource.addMapper(secondSamlAttributeToRoleMapper).close();
// third mapper (advanced) that maps an attribute the test user doesn't have - it would normally end up removing the // third mapper (advanced) that maps an attribute the test user doesn't have - it would normally end up removing
// mapped role but it should now check if a previous mapper has already granted the same role. // the mapped role, but it should now check if a previous mapper has already granted the same role.
IdentityProviderMapperRepresentation thirdSamlAttributeToRoleMapper = new IdentityProviderMapperRepresentation(); IdentityProviderMapperRepresentation thirdSamlAttributeToRoleMapper = new IdentityProviderMapperRepresentation();
thirdSamlAttributeToRoleMapper.setName("advanced-role-mapper"); thirdSamlAttributeToRoleMapper.setName("advanced-role-mapper");
thirdSamlAttributeToRoleMapper.setIdentityProviderMapper(AdvancedAttributeToRoleMapper.PROVIDER_ID); thirdSamlAttributeToRoleMapper.setIdentityProviderMapper(AdvancedAttributeToRoleMapper.PROVIDER_ID);
thirdSamlAttributeToRoleMapper.setConfig(ImmutableMap.<String, String>builder() thirdSamlAttributeToRoleMapper.setConfig(ImmutableMap.<String, String> builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString()) .put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(AdvancedAttributeToRoleMapper.ATTRIBUTE_PROPERTY_NAME, ATTRIBUTES_TO_MATCH) .put(AdvancedAttributeToRoleMapper.ATTRIBUTE_PROPERTY_NAME, ATTRIBUTES_TO_MATCH)
.put(AdvancedAttributeToRoleMapper.ARE_ATTRIBUTE_VALUES_REGEX_PROPERTY_NAME, Boolean.FALSE.toString()) .put(AdvancedAttributeToRoleMapper.ARE_ATTRIBUTE_VALUES_REGEX_PROPERTY_NAME, Boolean.FALSE.toString())
.put(ConfigConstants.ROLE, CLIENT_ROLE_MAPPER_REPRESENTATION) .put(ConfigConstants.ROLE, roleValue)
.build()); .build());
thirdSamlAttributeToRoleMapper.setIdentityProviderAlias(bc.getIDPAlias());
idpResource.addMapper(thirdSamlAttributeToRoleMapper).close(); persistMapper(thirdSamlAttributeToRoleMapper);
} }
} }

View file

@ -1,5 +1,6 @@
package org.keycloak.testsuite.broker; package org.keycloak.testsuite.broker;
import org.keycloak.admin.client.CreatedResponseUtil;
import org.keycloak.admin.client.resource.IdentityProviderResource; import org.keycloak.admin.client.resource.IdentityProviderResource;
import org.keycloak.broker.oidc.mappers.AdvancedClaimToGroupMapper; import org.keycloak.broker.oidc.mappers.AdvancedClaimToGroupMapper;
import org.keycloak.broker.provider.ConfigConstants; import org.keycloak.broker.provider.ConfigConstants;
@ -10,15 +11,22 @@ import org.keycloak.representations.idm.IdentityProviderRepresentation;
import com.google.common.collect.ImmutableMap; 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 { public class OidcAdvancedClaimToGroupMapperTest extends AbstractAdvancedGroupMapperTest {
@Override @Override
protected BrokerConfiguration getBrokerConfiguration() { protected BrokerConfiguration getBrokerConfiguration() {
return new KcOidcBrokerConfiguration(); return new KcOidcBrokerConfiguration();
} }
@Override @Override
protected void createMapperInIdp(IdentityProviderRepresentation idp, String claimsOrAttributeRepresentation, protected String createMapperInIdp(IdentityProviderRepresentation idp, String claimsOrAttributeRepresentation,
boolean areClaimsOrAttributeValuesRegexes, IdentityProviderMapperSyncMode syncMode) { boolean areClaimsOrAttributeValuesRegexes, IdentityProviderMapperSyncMode syncMode, String groupPath) {
IdentityProviderMapperRepresentation advancedClaimToGroupMapper = new IdentityProviderMapperRepresentation(); IdentityProviderMapperRepresentation advancedClaimToGroupMapper = new IdentityProviderMapperRepresentation();
advancedClaimToGroupMapper.setName("advanced-claim-to-group-mapper"); advancedClaimToGroupMapper.setName("advanced-claim-to-group-mapper");
advancedClaimToGroupMapper.setIdentityProviderMapper(AdvancedClaimToGroupMapper.PROVIDER_ID); advancedClaimToGroupMapper.setIdentityProviderMapper(AdvancedClaimToGroupMapper.PROVIDER_ID);
@ -26,12 +34,14 @@ public class OidcAdvancedClaimToGroupMapperTest extends AbstractAdvancedGroupMap
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString()) .put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(AdvancedClaimToGroupMapper.CLAIM_PROPERTY_NAME, claimsOrAttributeRepresentation) .put(AdvancedClaimToGroupMapper.CLAIM_PROPERTY_NAME, claimsOrAttributeRepresentation)
.put(AdvancedClaimToGroupMapper.ARE_CLAIM_VALUES_REGEX_PROPERTY_NAME, .put(AdvancedClaimToGroupMapper.ARE_CLAIM_VALUES_REGEX_PROPERTY_NAME,
areClaimsOrAttributeValuesRegexes ? "true" : "false") Boolean.valueOf(areClaimsOrAttributeValuesRegexes).toString())
.put(ConfigConstants.GROUP, MAPPER_TEST_GROUP_PATH) .put(ConfigConstants.GROUP, groupPath)
.build()); .build());
IdentityProviderResource idpResource = realm.identityProviders().get(idp.getAlias()); IdentityProviderResource idpResource = realm.identityProviders().get(idp.getAlias());
advancedClaimToGroupMapper.setIdentityProviderAlias(bc.getIDPAlias()); 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; 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.AdvancedClaimToRoleMapper;
import org.keycloak.broker.provider.ConfigConstants; import org.keycloak.broker.provider.ConfigConstants;
import org.keycloak.models.IdentityProviderMapperModel; import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderMapperSyncMode; import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation; 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.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 { public class OidcAdvancedClaimToRoleMapperTest extends AbstractAdvancedRoleMapperTest {
@Override @Override
@ -20,19 +20,19 @@ public class OidcAdvancedClaimToRoleMapperTest extends AbstractAdvancedRoleMappe
} }
@Override @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(); IdentityProviderMapperRepresentation advancedClaimToRoleMapper = new IdentityProviderMapperRepresentation();
advancedClaimToRoleMapper.setName("advanced-claim-to-role-mapper"); advancedClaimToRoleMapper.setName("advanced-claim-to-role-mapper");
advancedClaimToRoleMapper.setIdentityProviderMapper(AdvancedClaimToRoleMapper.PROVIDER_ID); advancedClaimToRoleMapper.setIdentityProviderMapper(AdvancedClaimToRoleMapper.PROVIDER_ID);
advancedClaimToRoleMapper.setConfig(ImmutableMap.<String, String>builder() advancedClaimToRoleMapper.setConfig(ImmutableMap.<String, String> builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString()) .put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(AdvancedClaimToRoleMapper.CLAIM_PROPERTY_NAME, claimsOrAttributeRepresentation) .put(AdvancedClaimToRoleMapper.CLAIM_PROPERTY_NAME, claimsOrAttributeRepresentation)
.put(AdvancedClaimToRoleMapper.ARE_CLAIM_VALUES_REGEX_PROPERTY_NAME, areClaimsOrAttributeValuesRegexes ? "true" : "false") .put(AdvancedClaimToRoleMapper.ARE_CLAIM_VALUES_REGEX_PROPERTY_NAME,
.put(ConfigConstants.ROLE, CLIENT_ROLE_MAPPER_REPRESENTATION) Boolean.valueOf(areClaimsOrAttributeValuesRegexes).toString())
.put(ConfigConstants.ROLE, roleValue)
.build()); .build());
IdentityProviderResource idpResource = realm.identityProviders().get(idp.getAlias()); persistMapper(advancedClaimToRoleMapper);
advancedClaimToRoleMapper.setIdentityProviderAlias(bc.getIDPAlias());
idpResource.addMapper(advancedClaimToRoleMapper).close();
} }
} }

View file

@ -3,24 +3,23 @@ package org.keycloak.testsuite.broker;
import static org.keycloak.models.IdentityProviderMapperSyncMode.FORCE; import static org.keycloak.models.IdentityProviderMapperSyncMode.FORCE;
import static org.keycloak.models.IdentityProviderMapperSyncMode.LEGACY; import static org.keycloak.models.IdentityProviderMapperSyncMode.LEGACY;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.junit.Test; import org.junit.Test;
import org.keycloak.admin.client.resource.IdentityProviderResource;
import org.keycloak.broker.oidc.mappers.ClaimToRoleMapper; import org.keycloak.broker.oidc.mappers.ClaimToRoleMapper;
import org.keycloak.broker.provider.ConfigConstants; import org.keycloak.broker.provider.ConfigConstants;
import org.keycloak.models.IdentityProviderMapperModel; import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderMapperSyncMode; import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation; import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; 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 { public class OidcClaimToRoleMapperTest extends AbstractRoleMapperTest {
@ -36,89 +35,79 @@ public class OidcClaimToRoleMapperTest extends AbstractRoleMapperTest {
@Test @Test
public void allClaimValuesMatch() { public void allClaimValuesMatch() {
createClaimToRoleMapper(CLAIM_VALUE); createClaimToRoleMapper(CLAIM_VALUE);
createUserInProviderRealm(ImmutableMap.<String, List<String>>builder() createUserInProviderRealm(createUserConfig());
.put(CLAIM, ImmutableList.<String>builder().add(CLAIM_VALUE).build())
.build());
logInAsUserInIDPForFirstTime(); logInAsUserInIDPForFirstTime();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail()); assertThatRoleHasBeenAssignedInConsumerRealm();
assertThatRoleHasBeenAssignedInConsumerRealmTo(user);
} }
@Test @Test
public void claimValuesMismatch() { public void claimValuesMismatch() {
createClaimToRoleMapper("other value"); createClaimToRoleMapper("other value");
createUserInProviderRealm(ImmutableMap.<String, List<String>>builder() createUserInProviderRealm(createUserConfig());
.put(CLAIM, ImmutableList.<String>builder().add(CLAIM_VALUE).build())
.build());
logInAsUserInIDPForFirstTime(); logInAsUserInIDPForFirstTime();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail()); assertThatRoleHasNotBeenAssignedInConsumerRealm();
assertThatRoleHasNotBeenAssignedInConsumerRealmTo(user);
} }
@Test @Test
public void updateBrokeredUserMismatchDeletesRoleInForceMode() { public void updateBrokeredUserMismatchDeletesRoleInForceMode() {
UserRepresentation user = loginWithClaimThenChangeClaimToValue("value mismatch", FORCE, false); loginWithClaimThenChangeClaimToValue("value mismatch", FORCE, false);
assertThatRoleHasNotBeenAssignedInConsumerRealmTo(user); assertThatRoleHasNotBeenAssignedInConsumerRealm();
} }
@Test @Test
public void updateBrokeredUserMismatchDeletesRoleInLegacyMode() { public void updateBrokeredUserMismatchDeletesRoleInLegacyMode() {
UserRepresentation user = createMapperThenLoginWithStandardClaimThenChangeClaimToValue("value mismatch", LEGACY); createMapperThenLoginWithStandardClaimThenChangeClaimToValue("value mismatch", LEGACY);
assertThatRoleHasNotBeenAssignedInConsumerRealmTo(user); assertThatRoleHasNotBeenAssignedInConsumerRealm();
} }
@Test @Test
public void updateBrokeredUserNewMatchGrantsRoleAfterFirstLoginInForceMode() { public void updateBrokeredUserNewMatchGrantsRoleAfterFirstLoginInForceMode() {
UserRepresentation user = loginWithStandardClaimThenAddMapperAndLoginAgain(FORCE); loginWithStandardClaimThenAddMapperAndLoginAgain(FORCE);
assertThatRoleHasBeenAssignedInConsumerRealmTo(user); assertThatRoleHasBeenAssignedInConsumerRealm();
} }
@Test @Test
public void updateBrokeredUserNewMatchDoesNotGrantRoleAfterFirstLoginInLegacyMode() { public void updateBrokeredUserNewMatchDoesNotGrantRoleAfterFirstLoginInLegacyMode() {
UserRepresentation user = loginWithStandardClaimThenAddMapperAndLoginAgain(LEGACY); loginWithStandardClaimThenAddMapperAndLoginAgain(LEGACY);
assertThatRoleHasNotBeenAssignedInConsumerRealmTo(user); assertThatRoleHasNotBeenAssignedInConsumerRealm();
} }
@Test @Test
public void updateBrokeredUserDoesNotDeleteRoleIfClaimStillMatches() { public void updateBrokeredUserDoesNotDeleteRoleIfClaimStillMatches() {
UserRepresentation user = createMapperThenLoginWithStandardClaimThenChangeClaimToValue(CLAIM_VALUE, FORCE); createMapperThenLoginWithStandardClaimThenChangeClaimToValue(CLAIM_VALUE, FORCE);
assertThatRoleHasBeenAssignedInConsumerRealmTo(user); assertThatRoleHasBeenAssignedInConsumerRealm();
} }
private UserRepresentation loginWithStandardClaimThenAddMapperAndLoginAgain(IdentityProviderMapperSyncMode syncMode) { private void loginWithStandardClaimThenAddMapperAndLoginAgain(IdentityProviderMapperSyncMode syncMode) {
return loginWithClaimThenChangeClaimToValue(OidcClaimToRoleMapperTest.CLAIM_VALUE, syncMode, true); loginWithClaimThenChangeClaimToValue(OidcClaimToRoleMapperTest.CLAIM_VALUE, syncMode, true);
} }
private UserRepresentation createMapperThenLoginWithStandardClaimThenChangeClaimToValue(String claimOnSecondLogin, IdentityProviderMapperSyncMode syncMode) { private void createMapperThenLoginWithStandardClaimThenChangeClaimToValue(String claimOnSecondLogin, IdentityProviderMapperSyncMode syncMode) {
return loginWithClaimThenChangeClaimToValue(claimOnSecondLogin, syncMode, false); loginWithClaimThenChangeClaimToValue(claimOnSecondLogin, syncMode, false);
} }
@NotNull private void loginWithClaimThenChangeClaimToValue(String claimOnSecondLogin, IdentityProviderMapperSyncMode syncMode, boolean createAfterFirstLogin) {
private UserRepresentation loginWithClaimThenChangeClaimToValue(String claimOnSecondLogin, IdentityProviderMapperSyncMode syncMode, boolean createAfterFirstLogin) {
this.claimOnSecondLogin = claimOnSecondLogin; this.claimOnSecondLogin = claimOnSecondLogin;
return loginAsUserTwiceWithMapper(syncMode, createAfterFirstLogin, loginAsUserTwiceWithMapper(syncMode, createAfterFirstLogin, createUserConfig());
ImmutableMap.<String, List<String>>builder()
.put(CLAIM, ImmutableList.<String>builder().add(CLAIM_VALUE).build())
.build());
} }
private void createClaimToRoleMapper(String claimValue) { private void createClaimToRoleMapper(String claimValue) {
IdentityProviderRepresentation idp = setupIdentityProvider(); setupIdentityProvider();
createClaimToRoleMapper(idp, claimValue, IdentityProviderMapperSyncMode.IMPORT); createClaimToRoleMapper(claimValue, IdentityProviderMapperSyncMode.IMPORT, CLIENT_ROLE_MAPPER_REPRESENTATION);
} }
@Override @Override
protected void createMapperInIdp(IdentityProviderRepresentation idp, IdentityProviderMapperSyncMode syncMode) { protected void createMapperInIdp(IdentityProviderMapperSyncMode syncMode, String roleValue) {
createClaimToRoleMapper(idp, CLAIM_VALUE, syncMode); createClaimToRoleMapper(CLAIM_VALUE, syncMode, roleValue);
} }
@Override @Override
@ -131,19 +120,29 @@ public class OidcClaimToRoleMapperTest extends AbstractRoleMapperTest {
adminClient.realm(bc.providerRealmName()).users().get(user.getId()).update(user); 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(); IdentityProviderMapperRepresentation claimToRoleMapper = new IdentityProviderMapperRepresentation();
claimToRoleMapper.setName("claim-to-role-mapper"); claimToRoleMapper.setName("claim-to-role-mapper");
claimToRoleMapper.setIdentityProviderMapper(ClaimToRoleMapper.PROVIDER_ID); claimToRoleMapper.setIdentityProviderMapper(ClaimToRoleMapper.PROVIDER_ID);
claimToRoleMapper.setConfig(ImmutableMap.<String, String>builder() claimToRoleMapper.setConfig(ImmutableMap.<String, String> builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString()) .put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(ClaimToRoleMapper.CLAIM, OidcClaimToRoleMapperTest.CLAIM) .put(ClaimToRoleMapper.CLAIM, OidcClaimToRoleMapperTest.CLAIM)
.put(ClaimToRoleMapper.CLAIM_VALUE, claimValue) .put(ClaimToRoleMapper.CLAIM_VALUE, claimValue)
.put(ConfigConstants.ROLE, CLIENT_ROLE_MAPPER_REPRESENTATION) .put(ConfigConstants.ROLE, roleValue)
.build()); .build());
IdentityProviderResource idpResource = realm.identityProviders().get(idp.getAlias()); persistMapper(claimToRoleMapper);
claimToRoleMapper.setIdentityProviderAlias(bc.getIDPAlias()); }
idpResource.addMapper(claimToRoleMapper).close();
@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; 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.AdvancedClaimToRoleMapper;
import org.keycloak.broker.oidc.mappers.ClaimToRoleMapper; import org.keycloak.broker.oidc.mappers.ClaimToRoleMapper;
import org.keycloak.broker.oidc.mappers.ExternalKeycloakRoleToRoleMapper; 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.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderMapperSyncMode; import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation; 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 * 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 * 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. * 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 { public class OidcMultipleClaimToRoleMappersTest extends OidcClaimToRoleMapperTest {
@ -57,48 +57,46 @@ public class OidcMultipleClaimToRoleMappersTest extends OidcClaimToRoleMapperTes
"]"; "]";
@Override @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. // first mapper that maps attributes the user has - it should perform the mapping to the expected role.
IdentityProviderMapperRepresentation firstOidcClaimToRoleMapper = new IdentityProviderMapperRepresentation(); IdentityProviderMapperRepresentation firstOidcClaimToRoleMapper = new IdentityProviderMapperRepresentation();
firstOidcClaimToRoleMapper.setName("claim-to-role-mapper"); firstOidcClaimToRoleMapper.setName("claim-to-role-mapper");
firstOidcClaimToRoleMapper.setIdentityProviderMapper(ClaimToRoleMapper.PROVIDER_ID); firstOidcClaimToRoleMapper.setIdentityProviderMapper(ClaimToRoleMapper.PROVIDER_ID);
firstOidcClaimToRoleMapper.setConfig(ImmutableMap.<String, String>builder() firstOidcClaimToRoleMapper.setConfig(ImmutableMap.<String, String> builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString()) .put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(ClaimToRoleMapper.CLAIM, OidcClaimToRoleMapperTest.CLAIM) .put(ClaimToRoleMapper.CLAIM, OidcClaimToRoleMapperTest.CLAIM)
.put(ClaimToRoleMapper.CLAIM_VALUE, claimValue) .put(ClaimToRoleMapper.CLAIM_VALUE, claimValue)
.put(ConfigConstants.ROLE, CLIENT_ROLE_MAPPER_REPRESENTATION) .put(ConfigConstants.ROLE, roleValue)
.build()); .build());
IdentityProviderResource idpResource = realm.identityProviders().get(idp.getAlias()); persistMapper(firstOidcClaimToRoleMapper);
firstOidcClaimToRoleMapper.setIdentityProviderAlias(bc.getIDPAlias());
idpResource.addMapper(firstOidcClaimToRoleMapper).close();
// second mapper that maps an external role claim the test user doesn't have - it would normally end up removing the // second mapper that maps an external role claim the test user doesn't have - it would normally end up removing
// mapped role but it should now check if a previous mapper has already granted the same role. // the mapped role, but it should now check if a previous mapper has already granted the same role.
IdentityProviderMapperRepresentation secondOidcClaimToRoleMapper = new IdentityProviderMapperRepresentation(); IdentityProviderMapperRepresentation secondOidcClaimToRoleMapper = new IdentityProviderMapperRepresentation();
secondOidcClaimToRoleMapper.setName("external-keycloak-role-mapper"); secondOidcClaimToRoleMapper.setName("external-keycloak-role-mapper");
secondOidcClaimToRoleMapper.setIdentityProviderMapper(ExternalKeycloakRoleToRoleMapper.PROVIDER_ID); secondOidcClaimToRoleMapper.setIdentityProviderMapper(ExternalKeycloakRoleToRoleMapper.PROVIDER_ID);
secondOidcClaimToRoleMapper.setConfig(ImmutableMap.<String,String>builder() secondOidcClaimToRoleMapper.setConfig(ImmutableMap.<String, String> builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString()) .put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put("external.role", "missing-role") .put("external.role", "missing-role")
.put("role", CLIENT_ROLE_MAPPER_REPRESENTATION) .put(ConfigConstants.ROLE, roleValue)
.build()); .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 // 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(); IdentityProviderMapperRepresentation thirdOidcClaimToRoleMapper = new IdentityProviderMapperRepresentation();
thirdOidcClaimToRoleMapper.setName("advanced-claim-to-role-mapper"); thirdOidcClaimToRoleMapper.setName("advanced-claim-to-role-mapper");
thirdOidcClaimToRoleMapper.setIdentityProviderMapper(AdvancedClaimToRoleMapper.PROVIDER_ID); thirdOidcClaimToRoleMapper.setIdentityProviderMapper(AdvancedClaimToRoleMapper.PROVIDER_ID);
thirdOidcClaimToRoleMapper.setConfig(ImmutableMap.<String, String>builder() thirdOidcClaimToRoleMapper.setConfig(ImmutableMap.<String, String> builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString()) .put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(AdvancedClaimToRoleMapper.CLAIM_PROPERTY_NAME, CLAIMS_OR_ATTRIBUTES) .put(AdvancedClaimToRoleMapper.CLAIM_PROPERTY_NAME, CLAIMS_OR_ATTRIBUTES)
.put(AdvancedClaimToRoleMapper.ARE_CLAIM_VALUES_REGEX_PROPERTY_NAME, Boolean.TRUE.toString()) .put(AdvancedClaimToRoleMapper.ARE_CLAIM_VALUES_REGEX_PROPERTY_NAME, Boolean.TRUE.toString())
.put(ConfigConstants.ROLE, CLIENT_ROLE_MAPPER_REPRESENTATION) .put(ConfigConstants.ROLE, roleValue)
.build()); .build());
thirdOidcClaimToRoleMapper.setIdentityProviderAlias(bc.getIDPAlias()); persistMapper(thirdOidcClaimToRoleMapper);
idpResource.addMapper(thirdOidcClaimToRoleMapper).close();
} }
} }

View file

@ -1,9 +1,6 @@
package org.keycloak.testsuite.broker; package org.keycloak.testsuite.broker;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.junit.Test; import org.junit.Test;
import org.keycloak.admin.client.resource.IdentityProviderResource;
import org.keycloak.broker.oidc.mappers.ClaimToRoleMapper; import org.keycloak.broker.oidc.mappers.ClaimToRoleMapper;
import org.keycloak.broker.provider.ConfigConstants; import org.keycloak.broker.provider.ConfigConstants;
import org.keycloak.models.IdentityProviderMapperModel; 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.OIDCAttributeMapperHelper;
import org.keycloak.protocol.oidc.mappers.UserAttributeMapper; import org.keycloak.protocol.oidc.mappers.UserAttributeMapper;
import org.keycloak.provider.ProviderConfigProperty; 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.List;
import java.util.Map; 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 { public class OidcUserInfoClaimToRoleMapperTest extends AbstractRoleMapperTest {
protected static final String ATTRIBUTE_TO_MAP_USER_INFO = "user-attribute-info"; 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 = ATTRIBUTE_TO_MAP_USER_INFO;
private static final String USER_INFO_CLAIM_VALUE = "value 1"; private static final String USER_INFO_CLAIM_VALUE = "value 1";
private String claimOnSecondLogin = ""; private static final String CLAIM_ON_SECOND_LOGIN = "";
@Override @Override
@ -35,69 +42,63 @@ public class OidcUserInfoClaimToRoleMapperTest extends AbstractRoleMapperTest {
@Test @Test
public void singleClaimValueInUserInfoMatches() { public void singleClaimValueInUserInfoMatches() {
createClaimToRoleMapper(USER_INFO_CLAIM_VALUE); createClaimToRoleMapper();
createUserInProviderRealm(ImmutableMap.<String, List<String>>builder() createUserInProviderRealm(createUserConfig());
.put(USER_INFO_CLAIM, ImmutableList.<String>builder().add(USER_INFO_CLAIM_VALUE).build())
.build());
logInAsUserInIDPForFirstTime(); logInAsUserInIDPForFirstTime();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail()); assertThatRoleHasBeenAssignedInConsumerRealm();
assertThatRoleHasBeenAssignedInConsumerRealmTo(user);
} }
@Test @Test
public void noRoleAddedIfUserInfoDisabledAndOnlyClaimIsInUserInfo() { public void noRoleAddedIfUserInfoDisabledAndOnlyClaimIsInUserInfo() {
createClaimToRoleMapperWithUserInfoDisabledInIdP(USER_INFO_CLAIM_VALUE); createClaimToRoleMapperWithUserInfoDisabledInIdP();
createUserInProviderRealm(ImmutableMap.<String, List<String>>builder() createUserInProviderRealm(createUserConfig());
.put(USER_INFO_CLAIM, ImmutableList.<String>builder().add(USER_INFO_CLAIM_VALUE).build())
.build());
logInAsUserInIDPForFirstTime(); logInAsUserInIDPForFirstTime();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail()); assertThatRoleHasNotBeenAssignedInConsumerRealm();
assertThatRoleHasNotBeenAssignedInConsumerRealmTo(user);
} }
private void createClaimToRoleMapper(String claimValue) { private void createClaimToRoleMapper() {
IdentityProviderRepresentation idp = setupIdentityProvider(); setupIdentityProvider();
createClaimToRoleMapper(idp, claimValue, IdentityProviderMapperSyncMode.IMPORT); createClaimToRoleMapper(OidcUserInfoClaimToRoleMapperTest.USER_INFO_CLAIM_VALUE,
IdentityProviderMapperSyncMode.IMPORT, CLIENT_ROLE_MAPPER_REPRESENTATION);
} }
private void createClaimToRoleMapperWithUserInfoDisabledInIdP(String claimValue) { private void createClaimToRoleMapperWithUserInfoDisabledInIdP() {
IdentityProviderRepresentation idp = setupIdentityProviderDisableUserInfo(); setupIdentityProviderDisableUserInfo();
createClaimToRoleMapper(idp, claimValue, IdentityProviderMapperSyncMode.IMPORT); createClaimToRoleMapper(OidcUserInfoClaimToRoleMapperTest.USER_INFO_CLAIM_VALUE,
IdentityProviderMapperSyncMode.IMPORT, CLIENT_ROLE_MAPPER_REPRESENTATION);
} }
@Override @Override
protected void createMapperInIdp(IdentityProviderRepresentation idp, IdentityProviderMapperSyncMode syncMode) { protected void createMapperInIdp(IdentityProviderMapperSyncMode syncMode, String roleValue) {
createClaimToRoleMapper(idp, USER_INFO_CLAIM_VALUE, syncMode); createClaimToRoleMapper(USER_INFO_CLAIM_VALUE, syncMode, roleValue);
} }
@Override @Override
protected void updateUser() { protected void updateUser() {
UserRepresentation user = findUser(bc.providerRealmName(), bc.getUserLogin(), bc.getUserEmail()); UserRepresentation user = findUser(bc.providerRealmName(), bc.getUserLogin(), bc.getUserEmail());
ImmutableMap<String, List<String>> mismatchingAttributes = ImmutableMap.<String, List<String>>builder() ImmutableMap<String, List<String>> mismatchingAttributes = ImmutableMap.<String, List<String>> builder()
.put(USER_INFO_CLAIM, ImmutableList.<String>builder().add(claimOnSecondLogin).build()) .put(USER_INFO_CLAIM, ImmutableList.<String> builder().add(CLAIM_ON_SECOND_LOGIN).build())
.build(); .build();
user.setAttributes(mismatchingAttributes); user.setAttributes(mismatchingAttributes);
adminClient.realm(bc.providerRealmName()).users().get(user.getId()).update(user); 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(); IdentityProviderMapperRepresentation claimToRoleMapper = new IdentityProviderMapperRepresentation();
claimToRoleMapper.setName("userinfo-claim-to-role-mapper"); claimToRoleMapper.setName("userinfo-claim-to-role-mapper");
claimToRoleMapper.setIdentityProviderMapper(ClaimToRoleMapper.PROVIDER_ID); claimToRoleMapper.setIdentityProviderMapper(ClaimToRoleMapper.PROVIDER_ID);
claimToRoleMapper.setConfig(ImmutableMap.<String, String>builder() claimToRoleMapper.setConfig(ImmutableMap.<String, String> builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString()) .put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(ClaimToRoleMapper.CLAIM, OidcUserInfoClaimToRoleMapperTest.USER_INFO_CLAIM) .put(ClaimToRoleMapper.CLAIM, OidcUserInfoClaimToRoleMapperTest.USER_INFO_CLAIM)
.put(ClaimToRoleMapper.CLAIM_VALUE, claimValue) .put(ClaimToRoleMapper.CLAIM_VALUE, claimValue)
.put(ConfigConstants.ROLE, CLIENT_ROLE_MAPPER_REPRESENTATION) .put(ConfigConstants.ROLE, roleValue)
.build()); .build());
IdentityProviderResource idpResource = realm.identityProviders().get(idp.getAlias()); persistMapper(claimToRoleMapper);
claimToRoleMapper.setIdentityProviderAlias(bc.getIDPAlias());
idpResource.addMapper(claimToRoleMapper).close();
} }
private class KcOidcBrokerConfigurationUserInfoOnlyMappers extends KcOidcBrokerConfiguration { 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_ID_TOKEN, "false");
userAttrMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_USERINFO, "true"); userAttrMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_USERINFO, "true");
for (ClientRepresentation client: clientsRepList) { for (ClientRepresentation client : clientsRepList) {
client.setProtocolMappers(Arrays.asList(userAttrMapper)); client.setProtocolMappers(Collections.singletonList(userAttrMapper));
} }
return clientsRepList; 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.authorization.model.ResourceServer;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants; import org.keycloak.models.Constants;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RealmProvider; 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.Consumer;
import java.util.function.Function;
import static org.hamcrest.CoreMatchers.allOf; import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.MatcherAssert.assertThat; 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.anEmptyMap;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.hasEntry;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.notNullValue;
@RequireProvider(RealmProvider.class) @RequireProvider(RealmProvider.class)
@ -108,12 +113,12 @@ public class RealmModelTest extends KeycloakModelTest {
@Test @Test
public void testRealmPreRemoveDoesntRemoveEntitiesFromOtherRealms() { public void testRealmPreRemoveDoesntRemoveEntitiesFromOtherRealms() {
realm1Id = inComittedTransaction((Function<KeycloakSession, String>) session -> { realm1Id = inComittedTransaction(session -> {
RealmModel realm = session.realms().createRealm("realm1"); RealmModel realm = session.realms().createRealm("realm1");
realm.setDefaultRole(session.roles().addRealmRole(realm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + realm.getName())); realm.setDefaultRole(session.roles().addRealmRole(realm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + realm.getName()));
return realm.getId(); return realm.getId();
}); });
realm2Id = inComittedTransaction((Function<KeycloakSession, String>) session -> { realm2Id = inComittedTransaction(session -> {
RealmModel realm = session.realms().createRealm("realm2"); RealmModel realm = session.realms().createRealm("realm2");
realm.setDefaultRole(session.roles().addRealmRole(realm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + realm.getName())); realm.setDefaultRole(session.roles().addRealmRole(realm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + realm.getName()));
return realm.getId(); return realm.getId();
@ -140,4 +145,42 @@ public class RealmModelTest extends KeycloakModelTest {
assertThat(resourceServer, notNullValue()); 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);
}
}
}
} }