KEYCLOAK-11862 Add Sync mode option

- Store in config map in database and model
- Expose the field in the OIDC-IDP
- Write logic for import, force and legacy mode
- Show how mappers can be updated keeping correct legacy mode
- Show how mappers that work correctly don't have to be modified
- Log an error if sync mode is not supported

Fix updateBrokeredUser method for all mappers

- Allow updating of username (UsernameTemplateMapper)
- Delete UserAttributeStatementMapper: mapper isn't even registered
  Was actually rejected but never cleaned up: https://github.com/keycloak/keycloak/pull/4513
  The mapper won't work as specified and it's not easy to tests here
- Fixup json mapper
- Fix ExternalKeycloakRoleToRoleMapper:
  Bug: delete cannot work - just delete it. Don't fix it in legacy mode

Rework mapper tests

- Fix old tests for Identity Broker:
  Old tests did not work at all:
  They tested that if you take a realm and assign the role,
  this role is then assigned to the user in that realm,
  which has nothing to do with identity brokering
  Simplify logic in OidcClaimToRoleMapperTests
- Add SyncMode tests to most mappers
  Added tests for UsernameTemplateMapper
  Added tests to all RoleMappers
  Add test for json attribute mapper (Github as example)
- Extract common test setup(s)
- Extend admin console tests for sync mode

Signed-off-by: Martin Idel <external.Martin.Idel@bosch.io>
This commit is contained in:
Martin Idel 2019-12-11 13:11:53 +01:00 committed by Stian Thorgersen
parent 8f5e58234e
commit 7e8018c7ca
83 changed files with 2279 additions and 573 deletions

View file

@ -57,4 +57,14 @@ public abstract class AbstractIdentityProviderMapper implements IdentityProvider
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
}
@Override
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
}
@Override
public void updateBrokeredUserLegacy(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
updateBrokeredUser(session, realm, user, mapperModel, context);
}
}

View file

@ -161,6 +161,11 @@ public class BrokeredIdentityContext {
getContextData().put(Constants.USER_ATTRIBUTES_PREFIX + attributeName, list);
}
// Remove an attribute attribute, which would otherwise be available on "Update profile" page and in authenticators
public void removeUserAttribute(String attributeName) {
getContextData().remove(Constants.USER_ATTRIBUTES_PREFIX + attributeName);
}
public void setUserAttribute(String attributeName, List<String> attributeValues) {
getContextData().put(Constants.USER_ATTRIBUTES_PREFIX + attributeName, attributeValues);
}

View file

@ -18,6 +18,7 @@
package org.keycloak.broker.provider;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
@ -25,17 +26,26 @@ import org.keycloak.provider.ConfiguredProvider;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface IdentityProviderMapper extends Provider, ProviderFactory<IdentityProviderMapper>,ConfiguredProvider {
String ANY_PROVIDER = "*";
Set<IdentityProviderSyncMode> DEFAULT_IDENTITY_PROVIDER_MAPPER_SYNC_MODES = new HashSet<>(Arrays.asList(IdentityProviderSyncMode.LEGACY, IdentityProviderSyncMode.IMPORT));
String[] getCompatibleProviders();
String getDisplayCategory();
String getDisplayType();
default boolean supportsSyncMode(IdentityProviderSyncMode syncMode) {
return DEFAULT_IDENTITY_PROVIDER_MAPPER_SYNC_MODES.contains(syncMode);
}
/**
* Called to determine what keycloak username and email to use to process the login request from the external IDP.
* It's called before "FirstBrokerLogin" flow, so can be used to map attributes to BrokeredIdentityContext ( BrokeredIdentityContext.setUserAttribute ),
@ -60,6 +70,18 @@ public interface IdentityProviderMapper extends Provider, ProviderFactory<Identi
*/
void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context);
/**
* Called when this user has logged in before and has already been imported. Legacy behaviour. When updating the mapper to correctly update brokered users
* in all sync modes, move the old behavior into this method.
*
* @param session
* @param realm
* @param user
* @param mapperModel
* @param context
*/
void updateBrokeredUserLegacy(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context);
/**
* Called when this user has logged in before and has already been imported.
*
@ -70,6 +92,4 @@ public interface IdentityProviderMapper extends Provider, ProviderFactory<Identi
* @param context
*/
void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context);
}

View file

@ -0,0 +1,32 @@
package org.keycloak.broker.provider;
import org.jboss.logging.Logger;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
public final class IdentityProviderMapperSyncModeDelegate {
protected static final Logger logger = Logger.getLogger(IdentityProviderMapperSyncModeDelegate.class);
public static void delegateUpdateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context, IdentityProviderMapper mapper) {
IdentityProviderSyncMode effectiveSyncMode = combineIdpAndMapperSyncMode(context.getIdpConfig().getSyncMode(), mapperModel.getSyncMode());
if (!mapper.supportsSyncMode(effectiveSyncMode)) {
logger.warnf("The mapper %s does not explicitly support sync mode %s. Please ensure that the SPI supports the sync mode correctly and update it to reflect this.", mapper.getDisplayType(), effectiveSyncMode);
}
if (effectiveSyncMode == IdentityProviderSyncMode.LEGACY) {
mapper.updateBrokeredUserLegacy(session, realm, user, mapperModel, context);
} else if (effectiveSyncMode == IdentityProviderSyncMode.FORCE) {
mapper.updateBrokeredUser(session, realm, user, mapperModel, context);
}
}
public static IdentityProviderSyncMode combineIdpAndMapperSyncMode(IdentityProviderSyncMode syncMode, IdentityProviderMapperSyncMode mapperSyncMode) {
return IdentityProviderMapperSyncMode.INHERIT.equals(mapperSyncMode) ? syncMode : IdentityProviderSyncMode.valueOf(mapperSyncMode.toString());
}
}

View file

@ -33,6 +33,7 @@ import java.util.stream.Collectors;
* @version $Revision: 1 $
*/
public class IdentityProviderMapperModel implements Serializable {
public static final String SYNC_MODE = "syncMode";
private static final TypeReference<List<StringPair>> MAP_TYPE_REPRESENTATION = new TypeReference<List<StringPair>>() {
};
@ -76,6 +77,14 @@ public class IdentityProviderMapperModel implements Serializable {
this.identityProviderMapper = identityProviderMapper;
}
public IdentityProviderMapperSyncMode getSyncMode() {
return IdentityProviderMapperSyncMode.valueOf(getConfig().getOrDefault(SYNC_MODE, "LEGACY"));
}
public void setSyncMode(IdentityProviderMapperSyncMode syncMode) {
getConfig().put(SYNC_MODE, syncMode.toString());
}
public Map<String, String> getConfig() {
return config;
}

View file

@ -0,0 +1,5 @@
package org.keycloak.models;
public enum IdentityProviderMapperSyncMode {
INHERIT, LEGACY, IMPORT, FORCE
}

View file

@ -30,6 +30,8 @@ public class IdentityProviderModel implements Serializable {
public static final String ALLOWED_CLOCK_SKEW = "allowedClockSkew";
public static final String SYNC_MODE = "syncMode";
private String internalId;
/**
@ -64,6 +66,8 @@ public class IdentityProviderModel implements Serializable {
private String displayName;
private IdentityProviderSyncMode syncMode;
/**
* <p>A map containing the configuration and properties for a specific identity provider instance and implementation. The items
* in the map are understood by the identity provider implementation.</p>
@ -205,6 +209,13 @@ public class IdentityProviderModel implements Serializable {
* @param realm the realm
*/
public void validate(RealmModel realm) {
}
public IdentityProviderSyncMode getSyncMode() {
return IdentityProviderSyncMode.valueOf(getConfig().getOrDefault(SYNC_MODE, "LEGACY"));
}
public void setSyncMode(IdentityProviderSyncMode syncMode) {
getConfig().put(SYNC_MODE, syncMode.toString());
}
}

View file

@ -0,0 +1,5 @@
package org.keycloak.models;
public enum IdentityProviderSyncMode {
LEGACY, IMPORT, FORCE
}

View file

@ -23,6 +23,7 @@ import org.keycloak.broker.oidc.OIDCIdentityProvider;
import org.keycloak.broker.provider.AbstractIdentityProviderMapper;
import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
@ -30,7 +31,10 @@ import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper;
import org.keycloak.provider.ProviderConfigProperty;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Abstract class for Social Provider mappers which allow mapping of JSON user profile field into Keycloak user
@ -41,6 +45,7 @@ import java.util.List;
*/
public abstract class AbstractJsonUserAttributeMapper extends AbstractIdentityProviderMapper {
private static final Set<IdentityProviderSyncMode> IDENTITY_PROVIDER_SYNC_MODES = new HashSet<>(Arrays.asList(IdentityProviderSyncMode.values()));
protected static final Logger logger = Logger.getLogger(AbstractJsonUserAttributeMapper.class);
@ -97,6 +102,11 @@ public abstract class AbstractJsonUserAttributeMapper extends AbstractIdentityPr
LOGGER_DUMP_USER_PROFILE.debug("User Profile JSON Data for provider "+provider+": " + profile);
}
@Override
public boolean supportsSyncMode(IdentityProviderSyncMode syncMode) {
return IDENTITY_PROVIDER_SYNC_MODES.contains(syncMode);
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return configProperties;
@ -119,12 +129,10 @@ public abstract class AbstractJsonUserAttributeMapper extends AbstractIdentityPr
@Override
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
String attribute = mapperModel.getConfig().get(CONF_USER_ATTRIBUTE);
if (attribute == null || attribute.trim().isEmpty()) {
logger.warnf("Attribute is not configured for mapper %s", mapperModel.getName());
String attribute = getAttribute(mapperModel);
if (attribute == null) {
return;
}
attribute = attribute.trim();
Object value = getJsonValue(mapperModel, context);
if (value != null) {
@ -137,10 +145,37 @@ public abstract class AbstractJsonUserAttributeMapper extends AbstractIdentityPr
}
@Override
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
public void updateBrokeredUserLegacy(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
// we do not update user profile from social provider
}
@Override
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
String attribute = getAttribute(mapperModel);
if (attribute == null) {
return;
}
Object value = getJsonValue(mapperModel, context);
if (value == null) {
user.removeAttribute(attribute);
} else if (value instanceof List) {
user.setAttribute(attribute, (List<String>) value);
} else {
user.setSingleAttribute(attribute, value.toString());
}
}
private String getAttribute(IdentityProviderMapperModel mapperModel) {
String attribute = mapperModel.getConfig().get(CONF_USER_ATTRIBUTE);
if (attribute == null || attribute.trim().isEmpty()) {
logger.warnf("Attribute is not configured for mapper %s", mapperModel.getName());
return null;
}
attribute = attribute.trim();
return attribute;
}
protected static Object getJsonValue(IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
String jsonField = mapperModel.getConfig().get(CONF_JSON_FIELD);
@ -223,7 +258,7 @@ public abstract class AbstractJsonUserAttributeMapper extends AbstractIdentityPr
if (values.isEmpty()) {
return null;
}
return values ;
return values ;
} else if (currentNode.isNull()) {
logger.debug("JsonNode is null node for name " + currentFieldName);

View file

@ -23,6 +23,7 @@ import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.broker.provider.ConfigConstants;
import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
@ -31,8 +32,11 @@ import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.provider.ProviderConfigProperty;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke, Benjamin Weimer</a>
@ -44,6 +48,7 @@ public class AdvancedClaimToRoleMapper extends AbstractClaimMapper {
public static final String ARE_CLAIM_VALUES_REGEX_PROPERTY_NAME = "are.claim.values.regex";
public static final String[] COMPATIBLE_PROVIDERS = {KeycloakOIDCIdentityProviderFactory.PROVIDER_ID, OIDCIdentityProviderFactory.PROVIDER_ID};
private static final Set<IdentityProviderSyncMode> IDENTITY_PROVIDER_SYNC_MODES = new HashSet<>(Arrays.asList(IdentityProviderSyncMode.values()));
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
@ -70,6 +75,10 @@ public class AdvancedClaimToRoleMapper extends AbstractClaimMapper {
public static final String PROVIDER_ID = "oidc-advanced-role-idp-mapper";
@Override
public boolean supportsSyncMode(IdentityProviderSyncMode syncMode) {
return IDENTITY_PROVIDER_SYNC_MODES.contains(syncMode);
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
@ -107,7 +116,7 @@ public class AdvancedClaimToRoleMapper extends AbstractClaimMapper {
}
@Override
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
public void updateBrokeredUserLegacy(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
String roleName = mapperModel.getConfig().get(ConfigConstants.ROLE);
if (!hasAllClaimValues(mapperModel, context)) {
RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName);
@ -117,6 +126,21 @@ public class AdvancedClaimToRoleMapper extends AbstractClaimMapper {
}
@Override
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
String roleName = mapperModel.getConfig().get(ConfigConstants.ROLE);
RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName);
if (role == null) {
throw new IdentityBrokerException("Unable to find role: " + roleName);
}
if (!hasAllClaimValues(mapperModel, context)) {
user.deleteRoleMapping(role);
} else {
user.grantRole(role);
}
}
@Override
public String getHelpText() {
return "If all claims exists, grant the user the specified realm or application role.";

View file

@ -23,6 +23,7 @@ import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.broker.provider.ConfigConstants;
import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
@ -31,7 +32,10 @@ import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.provider.ProviderConfigProperty;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -42,6 +46,7 @@ public class ClaimToRoleMapper extends AbstractClaimMapper {
public static final String[] COMPATIBLE_PROVIDERS = {KeycloakOIDCIdentityProviderFactory.PROVIDER_ID, OIDCIdentityProviderFactory.PROVIDER_ID};
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
private static final Set<IdentityProviderSyncMode> IDENTITY_PROVIDER_SYNC_MODES = new HashSet<>(Arrays.asList(IdentityProviderSyncMode.values()));
static {
ProviderConfigProperty property;
@ -68,6 +73,10 @@ public class ClaimToRoleMapper extends AbstractClaimMapper {
public static final String PROVIDER_ID = "oidc-role-idp-mapper";
@Override
public boolean supportsSyncMode(IdentityProviderSyncMode syncMode) {
return IDENTITY_PROVIDER_SYNC_MODES.contains(syncMode);
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
@ -105,7 +114,7 @@ public class ClaimToRoleMapper extends AbstractClaimMapper {
}
@Override
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
public void updateBrokeredUserLegacy(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
String roleName = mapperModel.getConfig().get(ConfigConstants.ROLE);
if (!hasClaimValue(mapperModel, context)) {
RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName);
@ -115,6 +124,20 @@ public class ClaimToRoleMapper extends AbstractClaimMapper {
}
@Override
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
String roleName = mapperModel.getConfig().get(ConfigConstants.ROLE);
RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName);
if (role == null) {
throw new IdentityBrokerException("Unable to find role: " + roleName);
}
if (!hasClaimValue(mapperModel, context)) {
user.deleteRoleMapping(role);
} else {
user.grantRole(role);
}
}
@Override
public String getHelpText() {
return "If a claim exists, grant the user the specified realm or application role.";

View file

@ -23,6 +23,7 @@ import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.broker.provider.ConfigConstants;
import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
@ -32,7 +33,10 @@ import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.JsonWebToken;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -44,6 +48,7 @@ public class ExternalKeycloakRoleToRoleMapper extends AbstractClaimMapper {
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
private static final String EXTERNAL_ROLE = "external.role";
private static final Set<IdentityProviderSyncMode> IDENTITY_PROVIDER_SYNC_MODES = new HashSet<>(Arrays.asList(IdentityProviderSyncMode.values()));
static {
ProviderConfigProperty property;
@ -64,6 +69,10 @@ public class ExternalKeycloakRoleToRoleMapper extends AbstractClaimMapper {
public static final String PROVIDER_ID = "keycloak-oidc-role-to-role-idp-mapper";
@Override
public boolean supportsSyncMode(IdentityProviderSyncMode syncMode) {
return IDENTITY_PROVIDER_SYNC_MODES.contains(syncMode);
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
@ -92,16 +101,13 @@ public class ExternalKeycloakRoleToRoleMapper extends AbstractClaimMapper {
@Override
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
RoleModel role = hasRole(realm, mapperModel, context);
if (role != null) {
user.grantRole(role);
if (hasRole(realm, mapperModel, context)) {
user.grantRole(searchRole(realm, mapperModel));
}
}
private RoleModel hasRole(RealmModel realm,IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
private boolean hasRole(RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
JsonWebToken token = (JsonWebToken)context.getContextData().get(KeycloakOIDCIdentityProvider.VALIDATED_ACCESS_TOKEN);
//if (token == null) return;
String roleName = mapperModel.getConfig().get(ConfigConstants.ROLE);
String[] parseRole = KeycloakModelUtils.parseRole(mapperModel.getConfig().get(EXTERNAL_ROLE));
String externalRoleName = parseRole[1];
String claimName = null;
@ -111,19 +117,28 @@ public class ExternalKeycloakRoleToRoleMapper extends AbstractClaimMapper {
claimName = "resource_access." + parseRole[0] + ".roles";
}
Object claim = getClaimValue(token, claimName);
if (valueEquals(externalRoleName, claim)) {
RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName);
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
return role;
}
return null;
return valueEquals(externalRoleName, claim);
}
private RoleModel searchRole(RealmModel realm, IdentityProviderMapperModel mapperModel) {
String roleName = mapperModel.getConfig().get(ConfigConstants.ROLE);
RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName);
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
return role;
}
@Override
public void updateBrokeredUserLegacy(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
// The legacy mapper actually did nothing although it pretended to do something
}
@Override
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
RoleModel role = hasRole(realm, mapperModel, context);
if (role == null) {
user.deleteRoleMapping(role);
if (hasRole(realm, mapperModel, context)) {
user.grantRole(searchRole(realm, mapperModel));
} else {
user.deleteRoleMapping(searchRole(realm, mapperModel));
}
}

View file

@ -22,6 +22,7 @@ import org.keycloak.broker.oidc.OIDCIdentityProviderFactory;
import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.common.util.CollectionUtil;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
@ -29,9 +30,12 @@ import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.saml.common.util.StringUtil;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@ -49,6 +53,7 @@ public class UserAttributeMapper extends AbstractClaimMapper {
public static final String EMAIL = "email";
public static final String FIRST_NAME = "firstName";
public static final String LAST_NAME = "lastName";
private static final Set<IdentityProviderSyncMode> IDENTITY_PROVIDER_SYNC_MODES = new HashSet<>(Arrays.asList(IdentityProviderSyncMode.values()));
static {
ProviderConfigProperty property;
@ -69,6 +74,11 @@ public class UserAttributeMapper extends AbstractClaimMapper {
public static final String PROVIDER_ID = "oidc-user-attribute-idp-mapper";
@Override
public boolean supportsSyncMode(IdentityProviderSyncMode syncMode) {
return IDENTITY_PROVIDER_SYNC_MODES.contains(syncMode);
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return configProperties;

View file

@ -21,6 +21,7 @@ import org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory;
import org.keycloak.broker.oidc.OIDCIdentityProviderFactory;
import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
@ -41,7 +42,10 @@ import org.keycloak.social.stackoverflow.StackoverflowIdentityProviderFactory;
import org.keycloak.social.twitter.TwitterIdentityProviderFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -70,6 +74,7 @@ public class UsernameTemplateMapper extends AbstractClaimMapper {
};
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
private static final Set<IdentityProviderSyncMode> IDENTITY_PROVIDER_SYNC_MODES = new HashSet<>(Arrays.asList(IdentityProviderSyncMode.values()));
public static final String TEMPLATE = "template";
@ -86,6 +91,11 @@ public class UsernameTemplateMapper extends AbstractClaimMapper {
public static final String PROVIDER_ID = "oidc-username-idp-mapper";
@Override
public boolean supportsSyncMode(IdentityProviderSyncMode syncMode) {
return IDENTITY_PROVIDER_SYNC_MODES.contains(syncMode);
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return configProperties;
@ -111,14 +121,27 @@ public class UsernameTemplateMapper extends AbstractClaimMapper {
return "Username Template Importer";
}
@Override
public void updateBrokeredUserLegacy(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
}
@Override
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
// preprocessFederatedIdentity gets called anyways, so we only need to set the username if necessary.
// However, we don't want to set the username when the email is used as username
if (!realm.isRegistrationEmailAsUsername()) {
user.setUsername(context.getModelUsername());
}
}
static Pattern substitution = Pattern.compile("\\$\\{([^}]+)\\}");
@Override
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
setUserNameFromTemplate(mapperModel, context);
}
private void setUserNameFromTemplate(IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
String template = mapperModel.getConfig().get(TEMPLATE);
Matcher m = substitution.matcher(template);
StringBuffer sb = new StringBuffer();
@ -141,7 +164,6 @@ public class UsernameTemplateMapper extends AbstractClaimMapper {
m.appendTail(sb);
String username = sb.toString();
context.setModelUsername(username);
}
@Override

View file

@ -18,13 +18,17 @@
package org.keycloak.broker.provider;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.provider.ProviderConfigProperty;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -34,6 +38,7 @@ public class HardcodedAttributeMapper extends AbstractIdentityProviderMapper {
public static final String ATTRIBUTE = "attribute";
public static final String ATTRIBUTE_VALUE = "attribute.value";
protected static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
private static final Set<IdentityProviderSyncMode> IDENTITY_PROVIDER_SYNC_MODES = new HashSet<>(Arrays.asList(IdentityProviderSyncMode.values()));
static {
ProviderConfigProperty property;
@ -51,7 +56,10 @@ public class HardcodedAttributeMapper extends AbstractIdentityProviderMapper {
configProperties.add(property);
}
@Override
public boolean supportsSyncMode(IdentityProviderSyncMode syncMode) {
return IDENTITY_PROVIDER_SYNC_MODES.contains(syncMode);
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {

View file

@ -18,6 +18,7 @@
package org.keycloak.broker.provider;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
@ -26,14 +27,18 @@ import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.provider.ProviderConfigProperty;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class HardcodedRoleMapper extends AbstractIdentityProviderMapper {
protected static final List<ProviderConfigProperty> configProperties = new ArrayList<>();
protected static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
private static final Set<IdentityProviderSyncMode> IDENTITY_PROVIDER_SYNC_MODES = new HashSet<>(Arrays.asList(IdentityProviderSyncMode.values()));
static {
ProviderConfigProperty property;
@ -65,6 +70,11 @@ public class HardcodedRoleMapper extends AbstractIdentityProviderMapper {
public static final String PROVIDER_ID = "oidc-hardcoded-role-idp-mapper";
@Override
public boolean supportsSyncMode(IdentityProviderSyncMode syncMode) {
return IDENTITY_PROVIDER_SYNC_MODES.contains(syncMode);
}
@Override
public String getId() {
return PROVIDER_ID;
@ -77,6 +87,10 @@ public class HardcodedRoleMapper extends AbstractIdentityProviderMapper {
@Override
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
grantUserRole(realm, user, mapperModel);
}
private void grantUserRole(RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel) {
String roleName = mapperModel.getConfig().get(ConfigConstants.ROLE);
RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName);
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
@ -85,7 +99,11 @@ public class HardcodedRoleMapper extends AbstractIdentityProviderMapper {
@Override
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
grantUserRole(realm, user, mapperModel);
}
@Override
public void updateBrokeredUserLegacy(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
}
@Override

View file

@ -18,13 +18,17 @@
package org.keycloak.broker.provider;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.provider.ProviderConfigProperty;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -34,6 +38,7 @@ public class HardcodedUserSessionAttributeMapper extends AbstractIdentityProvide
public static final String ATTRIBUTE = "attribute";
public static final String ATTRIBUTE_VALUE = "attribute.value";
protected static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
private static final Set<IdentityProviderSyncMode> IDENTITY_PROVIDER_SYNC_MODES = new HashSet<>(Arrays.asList(IdentityProviderSyncMode.values()));
static {
ProviderConfigProperty property;
@ -51,7 +56,10 @@ public class HardcodedUserSessionAttributeMapper extends AbstractIdentityProvide
configProperties.add(property);
}
@Override
public boolean supportsSyncMode(IdentityProviderSyncMode syncMode) {
return IDENTITY_PROVIDER_SYNC_MODES.contains(syncMode);
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {

View file

@ -27,6 +27,7 @@ import org.keycloak.dom.saml.v2.assertion.AssertionType;
import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
import org.keycloak.dom.saml.v2.assertion.AttributeType;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
@ -35,8 +36,11 @@ import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.provider.ProviderConfigProperty;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -51,6 +55,7 @@ public class AttributeToRoleMapper extends AbstractIdentityProviderMapper {
public static final String ATTRIBUTE_NAME = "attribute.name";
public static final String ATTRIBUTE_FRIENDLY_NAME = "attribute.friendly.name";
public static final String ATTRIBUTE_VALUE = "attribute.value";
private static final Set<IdentityProviderSyncMode> IDENTITY_PROVIDER_SYNC_MODES = new HashSet<>(Arrays.asList(IdentityProviderSyncMode.values()));
static {
ProviderConfigProperty property;
@ -82,6 +87,11 @@ public class AttributeToRoleMapper extends AbstractIdentityProviderMapper {
public static final String PROVIDER_ID = "saml-role-idp-mapper";
@Override
public boolean supportsSyncMode(IdentityProviderSyncMode syncMode) {
return IDENTITY_PROVIDER_SYNC_MODES.contains(syncMode);
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return configProperties;

View file

@ -25,11 +25,20 @@ import org.keycloak.common.util.CollectionUtil;
import org.keycloak.dom.saml.v2.assertion.AssertionType;
import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
import org.keycloak.dom.saml.v2.assertion.AttributeType;
import org.keycloak.models.*;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.saml.common.util.StringUtil;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@ -50,6 +59,7 @@ public class UserAttributeMapper extends AbstractIdentityProviderMapper {
private static final String EMAIL = "email";
private static final String FIRST_NAME = "firstName";
private static final String LAST_NAME = "lastName";
private static final Set<IdentityProviderSyncMode> IDENTITY_PROVIDER_SYNC_MODES = new HashSet<>(Arrays.asList(IdentityProviderSyncMode.values()));
static {
ProviderConfigProperty property;
@ -75,6 +85,11 @@ public class UserAttributeMapper extends AbstractIdentityProviderMapper {
public static final String PROVIDER_ID = "saml-user-attribute-idp-mapper";
@Override
public boolean supportsSyncMode(IdentityProviderSyncMode syncMode) {
return IDENTITY_PROVIDER_SYNC_MODES.contains(syncMode);
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return configProperties;
@ -183,7 +198,7 @@ public class UserAttributeMapper extends AbstractIdentityProviderMapper {
// attribute sent by brokered idp has different values as before, update it
user.setAttribute(attribute, attributeValuesInContext);
}
// attribute allready set
// attribute already set
}
}

View file

@ -1,220 +0,0 @@
/*
* Copyright (c) eHealth
*/
package org.keycloak.broker.saml.mappers;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.keycloak.broker.provider.AbstractIdentityProviderMapper;
import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.broker.saml.SAMLEndpoint;
import org.keycloak.broker.saml.SAMLIdentityProviderFactory;
import org.keycloak.common.util.CollectionUtil;
import org.keycloak.dom.saml.v2.assertion.AssertionType;
import org.keycloak.dom.saml.v2.assertion.AttributeStatementType.ASTChoiceType;
import org.keycloak.dom.saml.v2.assertion.AttributeType;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.provider.ProviderConfigProperty;
/**
* @author <a href="mailto:frelibert@yahoo.com">Frederik Libert</a>
*
*/
public class UserAttributeStatementMapper extends AbstractIdentityProviderMapper {
private static final String USER_ATTR_LOCALE = "locale";
private static final String[] COMPATIBLE_PROVIDERS = {SAMLIdentityProviderFactory.PROVIDER_ID};
private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = new ArrayList<>();
public static final String ATTRIBUTE_NAME_PATTERN = "attribute.name.pattern";
public static final String USER_ATTRIBUTE_FIRST_NAME = "user.attribute.firstName";
public static final String USER_ATTRIBUTE_LAST_NAME = "user.attribute.lastName";
public static final String USER_ATTRIBUTE_EMAIL = "user.attribute.email";
public static final String USER_ATTRIBUTE_LANGUAGE = "user.attribute.language";
private static final String USE_FRIENDLY_NAMES = "use.friendly.names";
static {
ProviderConfigProperty property;
property = new ProviderConfigProperty();
property.setName(ATTRIBUTE_NAME_PATTERN);
property.setLabel("Attribute Name Pattern");
property.setHelpText("Pattern of attribute names in assertion that must be mapped. Leave blank to map all attributes.");
property.setType(ProviderConfigProperty.STRING_TYPE);
CONFIG_PROPERTIES.add(property);
property = new ProviderConfigProperty();
property.setName(USER_ATTRIBUTE_FIRST_NAME);
property.setLabel("User Attribute FirstName");
property.setHelpText("Define which saml Attribute must be mapped to the User property firstName.");
property.setType(ProviderConfigProperty.STRING_TYPE);
CONFIG_PROPERTIES.add(property);
property = new ProviderConfigProperty();
property.setName(USER_ATTRIBUTE_LAST_NAME);
property.setLabel("User Attribute LastName");
property.setHelpText("Define which saml Attribute must be mapped to the User property lastName.");
property.setType(ProviderConfigProperty.STRING_TYPE);
CONFIG_PROPERTIES.add(property);
property = new ProviderConfigProperty();
property.setName(USER_ATTRIBUTE_EMAIL);
property.setLabel("User Attribute Email");
property.setHelpText("Define which saml Attribute must be mapped to the User property email.");
property.setType(ProviderConfigProperty.STRING_TYPE);
CONFIG_PROPERTIES.add(property);
property = new ProviderConfigProperty();
property.setName(USER_ATTRIBUTE_LANGUAGE);
property.setLabel("User Attribute Language");
property.setHelpText("Define which saml Attribute must be mapped to the User attribute locale.");
property.setType(ProviderConfigProperty.STRING_TYPE);
CONFIG_PROPERTIES.add(property);
property = new ProviderConfigProperty();
property.setName(USE_FRIENDLY_NAMES);
property.setLabel("Use Attribute Friendly Name");
property.setHelpText("Define which name to give to each mapped user attribute: name or friendlyName.");
property.setType(ProviderConfigProperty.BOOLEAN_TYPE);
CONFIG_PROPERTIES.add(property);
}
public static final String PROVIDER_ID = "saml-user-attributestatement-idp-mapper";
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return CONFIG_PROPERTIES;
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public String[] getCompatibleProviders() {
return COMPATIBLE_PROVIDERS.clone();
}
@Override
public String getDisplayCategory() {
return "AttributeStatement Importer";
}
@Override
public String getDisplayType() {
return "AttributeStatement Importer";
}
@Override
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
String firstNameAttribute = mapperModel.getConfig().get(USER_ATTRIBUTE_FIRST_NAME);
String lastNameAttribute = mapperModel.getConfig().get(USER_ATTRIBUTE_LAST_NAME);
String emailAttribute = mapperModel.getConfig().get(USER_ATTRIBUTE_EMAIL);
String langAttribute = mapperModel.getConfig().get(USER_ATTRIBUTE_LANGUAGE);
Boolean useFriendlyNames = Boolean.valueOf(mapperModel.getConfig().get(USE_FRIENDLY_NAMES));
List<AttributeType> attributesInContext = findAttributesInContext(context, getAttributePattern(mapperModel));
for (AttributeType a : attributesInContext) {
String attribute = useFriendlyNames ? a.getFriendlyName() : a.getName();
List<String> attributeValuesInContext = a.getAttributeValue().stream().filter(Objects::nonNull).map(Object::toString).collect(Collectors.toList());
if (!attributeValuesInContext.isEmpty()) {
// set as attribute anyway
context.setUserAttribute(attribute, attributeValuesInContext);
// set as special field ?
if (Objects.equals(attribute, emailAttribute)) {
setIfNotEmpty(context::setEmail, attributeValuesInContext);
} else if (Objects.equals(attribute, firstNameAttribute)) {
setIfNotEmpty(context::setFirstName, attributeValuesInContext);
} else if (Objects.equals(attribute, lastNameAttribute)) {
setIfNotEmpty(context::setLastName, attributeValuesInContext);
} else if (Objects.equals(attribute, langAttribute)) {
context.setUserAttribute(USER_ATTR_LOCALE, attributeValuesInContext);
}
}
}
}
@Override
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
String firstNameAttribute = mapperModel.getConfig().get(USER_ATTRIBUTE_FIRST_NAME);
String lastNameAttribute = mapperModel.getConfig().get(USER_ATTRIBUTE_LAST_NAME);
String emailAttribute = mapperModel.getConfig().get(USER_ATTRIBUTE_EMAIL);
String langAttribute = mapperModel.getConfig().get(USER_ATTRIBUTE_LANGUAGE);
Boolean useFriendlyNames = Boolean.valueOf(mapperModel.getConfig().get(USE_FRIENDLY_NAMES));
List<AttributeType> attributesInContext = findAttributesInContext(context, getAttributePattern(mapperModel));
Set<String> assertedUserAttributes = new HashSet<String>();
for (AttributeType a : attributesInContext) {
String attribute = useFriendlyNames ? a.getFriendlyName() : a.getName();
List<String> attributeValuesInContext = a.getAttributeValue().stream().filter(Objects::nonNull).map(Object::toString).collect(Collectors.toList());
List<String> currentAttributeValues = user.getAttributes().get(attribute);
if (attributeValuesInContext == null) {
// attribute no longer sent by brokered idp, remove it
user.removeAttribute(attribute);
} else if (currentAttributeValues == null) {
// new attribute sent by brokered idp, add it
user.setAttribute(attribute, attributeValuesInContext);
} else if (!CollectionUtil.collectionEquals(attributeValuesInContext, currentAttributeValues)) {
// attribute sent by brokered idp has different values as before, update it
user.setAttribute(attribute, attributeValuesInContext);
}
if (Objects.equals(attribute, emailAttribute)) {
setIfNotEmpty(context::setEmail, attributeValuesInContext);
} else if (Objects.equals(attribute, firstNameAttribute)) {
setIfNotEmpty(context::setFirstName, attributeValuesInContext);
} else if (Objects.equals(attribute, lastNameAttribute)) {
setIfNotEmpty(context::setLastName, attributeValuesInContext);
} else if (Objects.equals(attribute, langAttribute)) {
if(attributeValuesInContext == null) {
user.removeAttribute(USER_ATTR_LOCALE);
} else {
user.setAttribute(USER_ATTR_LOCALE, attributeValuesInContext);
}
assertedUserAttributes.add(USER_ATTR_LOCALE);
}
// Mark attribute as handled
assertedUserAttributes.add(attribute);
}
// Remove user attributes that were not referenced in assertion.
user.getAttributes().keySet().stream().filter(a -> !assertedUserAttributes.contains(a)).forEach(a -> user.removeAttribute(a));
}
@Override
public String getHelpText() {
return "Import all saml attributes found in attributestatements in assertion into user properties or attributes.";
}
private Optional<Pattern> getAttributePattern(IdentityProviderMapperModel mapperModel) {
String attributePatternConfig = mapperModel.getConfig().get(ATTRIBUTE_NAME_PATTERN);
return Optional.ofNullable(attributePatternConfig != null ? Pattern.compile(attributePatternConfig) : null);
}
private List<AttributeType> findAttributesInContext(BrokeredIdentityContext context, Optional<Pattern> attributePattern) {
AssertionType assertion = (AssertionType) context.getContextData().get(SAMLEndpoint.SAML_ASSERTION);
return assertion.getAttributeStatements().stream()//
.flatMap(statement -> statement.getAttributes().stream())//
.filter(item -> !attributePattern.isPresent() || attributePattern.get().matcher(item.getAttribute().getName()).matches())//
.map(ASTChoiceType::getAttribute)//
.collect(Collectors.toList());
}
private void setIfNotEmpty(Consumer<String> consumer, List<String> values) {
if (values != null && !values.isEmpty()) {
consumer.accept(values.get(0));
}
}
}

View file

@ -27,6 +27,7 @@ import org.keycloak.dom.saml.v2.assertion.AttributeType;
import org.keycloak.dom.saml.v2.assertion.NameIDType;
import org.keycloak.dom.saml.v2.assertion.SubjectType;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
@ -34,7 +35,10 @@ import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.provider.ProviderConfigProperty;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -50,6 +54,8 @@ public class UsernameTemplateMapper extends AbstractIdentityProviderMapper {
public static final String TEMPLATE = "template";
private static final Set<IdentityProviderSyncMode> IDENTITY_PROVIDER_SYNC_MODES = new HashSet<>(Arrays.asList(IdentityProviderSyncMode.values()));
static {
ProviderConfigProperty property;
property = new ProviderConfigProperty();
@ -63,6 +69,11 @@ public class UsernameTemplateMapper extends AbstractIdentityProviderMapper {
public static final String PROVIDER_ID = "saml-username-idp-mapper";
@Override
public boolean supportsSyncMode(IdentityProviderSyncMode syncMode) {
return IDENTITY_PROVIDER_SYNC_MODES.contains(syncMode);
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return configProperties;
@ -89,13 +100,26 @@ public class UsernameTemplateMapper extends AbstractIdentityProviderMapper {
}
@Override
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
public void updateBrokeredUserLegacy(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
}
@Override
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
// preprocessFederatedIdentity gets called anyways, so we only need to set the username if necessary.
// However, we don't want to set the username when the email is used as username
if (!realm.isRegistrationEmailAsUsername()) {
user.setUsername(context.getModelUsername());
}
}
static Pattern substitution = Pattern.compile("\\$\\{([^}]+)\\}");
@Override
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
setUserNameFromTemplate(mapperModel, context);
}
private void setUserNameFromTemplate(IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
AssertionType assertion = (AssertionType)context.getContextData().get(SAMLEndpoint.SAML_ASSERTION);
String template = mapperModel.getConfig().get(TEMPLATE);
Matcher m = substitution.matcher(template);
@ -134,7 +158,6 @@ public class UsernameTemplateMapper extends AbstractIdentityProviderMapper {
}
m.appendTail(sb);
context.setModelUsername(sb.toString());
}
@Override

View file

@ -33,6 +33,7 @@ import org.keycloak.broker.provider.ExchangeTokenToIdentityProviderToken;
import org.keycloak.broker.provider.IdentityProvider;
import org.keycloak.broker.provider.IdentityProviderFactory;
import org.keycloak.broker.provider.IdentityProviderMapper;
import org.keycloak.broker.provider.IdentityProviderMapperSyncModeDelegate;
import org.keycloak.common.ClientConnection;
import org.keycloak.common.Profile;
import org.keycloak.common.constants.ServiceAccountConstants;
@ -1077,7 +1078,7 @@ public class TokenEndpoint {
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
for (IdentityProviderMapperModel mapper : mappers) {
IdentityProviderMapper target = (IdentityProviderMapper)sessionFactory.getProviderFactory(IdentityProviderMapper.class, mapper.getIdentityProviderMapper());
target.updateBrokeredUser(session, realm, user, mapper, context);
IdentityProviderMapperSyncModeDelegate.delegateUpdateBrokeredUser(session, realm, user, mapper, context, target);
}
}
}

View file

@ -32,15 +32,14 @@ import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.broker.provider.IdentityProvider;
import org.keycloak.broker.provider.IdentityProviderFactory;
import org.keycloak.broker.provider.IdentityProviderMapper;
import org.keycloak.broker.provider.IdentityProviderMapperSyncModeDelegate;
import org.keycloak.broker.provider.util.IdentityBrokerState;
import org.keycloak.broker.saml.SAMLEndpoint;
import org.keycloak.broker.saml.SAMLIdentityProvider;
import org.keycloak.broker.social.SocialIdentityProvider;
import org.keycloak.common.ClientConnection;
import org.keycloak.common.util.Base64Url;
import org.keycloak.common.util.ObjectUtil;
import org.keycloak.common.util.Time;
import org.keycloak.dom.saml.v2.SAML2Object;
import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
@ -1001,7 +1000,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
for (IdentityProviderMapperModel mapper : mappers) {
IdentityProviderMapper target = (IdentityProviderMapper)sessionFactory.getProviderFactory(IdentityProviderMapper.class, mapper.getIdentityProviderMapper());
target.updateBrokeredUser(session, realmModel, federatedUser, mapper, context);
IdentityProviderMapperSyncModeDelegate.delegateUpdateBrokeredUser(session, realmModel, federatedUser, mapper, context, target);
}
}

View file

@ -25,6 +25,7 @@ import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper;
*/
public class GitHubUserAttributeMapper extends AbstractJsonUserAttributeMapper {
public static final String PROVIDER_ID = "github-user-attribute-mapper";
private static final String[] cp = new String[] { GitHubIdentityProviderFactory.PROVIDER_ID };
@Override
@ -34,7 +35,7 @@ public class GitHubUserAttributeMapper extends AbstractJsonUserAttributeMapper {
@Override
public String getId() {
return "github-user-attribute-mapper";
return PROVIDER_ID;
}
}

View file

@ -32,6 +32,10 @@ import org.keycloak.dom.saml.v2.metadata.KeyTypes;
import org.keycloak.dom.saml.v2.metadata.SPSSODescriptorType;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.models.utils.StripSecretsUtils;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.representations.idm.AdminEventRepresentation;
@ -140,6 +144,7 @@ public class IdentityProviderTest extends AbstractAdminTest {
public void testCreate() {
IdentityProviderRepresentation newIdentityProvider = createRep("new-identity-provider", "oidc");
newIdentityProvider.getConfig().put(IdentityProviderModel.SYNC_MODE, "IMPORT");
newIdentityProvider.getConfig().put("clientId", "clientId");
newIdentityProvider.getConfig().put("clientSecret", "some secret value");
@ -156,6 +161,7 @@ public class IdentityProviderTest extends AbstractAdminTest {
assertNotNull(representation.getInternalId());
assertEquals("new-identity-provider", representation.getAlias());
assertEquals("oidc", representation.getProviderId());
assertEquals("IMPORT", representation.getConfig().get(IdentityProviderMapperModel.SYNC_MODE));
assertEquals("clientId", representation.getConfig().get("clientId"));
assertEquals(ComponentRepresentation.SECRET_VALUE, representation.getConfig().get("clientSecret"));
assertTrue(representation.isEnabled());
@ -243,6 +249,7 @@ public class IdentityProviderTest extends AbstractAdminTest {
public void testCreateWithBasicAuth() {
IdentityProviderRepresentation newIdentityProvider = createRep("new-identity-provider", "oidc");
newIdentityProvider.getConfig().put(IdentityProviderModel.SYNC_MODE, "IMPORT");
newIdentityProvider.getConfig().put("clientId", "clientId");
newIdentityProvider.getConfig().put("clientSecret", "some secret value");
newIdentityProvider.getConfig().put("clientAuthMethod",OIDCLoginProtocol.CLIENT_SECRET_BASIC);
@ -260,6 +267,7 @@ public class IdentityProviderTest extends AbstractAdminTest {
assertNotNull(representation.getInternalId());
assertEquals("new-identity-provider", representation.getAlias());
assertEquals("oidc", representation.getProviderId());
assertEquals("IMPORT", representation.getConfig().get(IdentityProviderMapperModel.SYNC_MODE));
assertEquals("clientId", representation.getConfig().get("clientId"));
assertEquals(ComponentRepresentation.SECRET_VALUE, representation.getConfig().get("clientSecret"));
assertEquals(OIDCLoginProtocol.CLIENT_SECRET_BASIC, representation.getConfig().get("clientAuthMethod"));
@ -278,6 +286,7 @@ public class IdentityProviderTest extends AbstractAdminTest {
public void testCreateWithJWT() {
IdentityProviderRepresentation newIdentityProvider = createRep("new-identity-provider", "oidc");
newIdentityProvider.getConfig().put(IdentityProviderModel.SYNC_MODE, "IMPORT");
newIdentityProvider.getConfig().put("clientId", "clientId");
newIdentityProvider.getConfig().put("clientAuthMethod", OIDCLoginProtocol.PRIVATE_KEY_JWT);
@ -294,6 +303,7 @@ public class IdentityProviderTest extends AbstractAdminTest {
assertNotNull(representation.getInternalId());
assertEquals("new-identity-provider", representation.getAlias());
assertEquals("oidc", representation.getProviderId());
assertEquals("IMPORT", representation.getConfig().get(IdentityProviderMapperModel.SYNC_MODE));
assertEquals("clientId", representation.getConfig().get("clientId"));
assertNull(representation.getConfig().get("clientSecret"));
assertEquals(OIDCLoginProtocol.PRIVATE_KEY_JWT, representation.getConfig().get("clientAuthMethod"));
@ -306,6 +316,7 @@ public class IdentityProviderTest extends AbstractAdminTest {
public void testUpdate() {
IdentityProviderRepresentation newIdentityProvider = createRep("update-identity-provider", "oidc");
newIdentityProvider.getConfig().put(IdentityProviderModel.SYNC_MODE, "IMPORT");
newIdentityProvider.getConfig().put("clientId", "clientId");
newIdentityProvider.getConfig().put("clientSecret", "some secret value");
@ -676,6 +687,7 @@ public class IdentityProviderTest extends AbstractAdminTest {
mapper.setIdentityProviderMapper("oidc-hardcoded-role-idp-mapper");
Map<String, String> config = new HashMap<>();
config.put("role", "offline_access");
config.put(IdentityProviderMapperModel.SYNC_MODE, IdentityProviderMapperSyncMode.INHERIT.toString());
mapper.setConfig(config);
// createRep and add mapper
@ -692,6 +704,7 @@ public class IdentityProviderTest extends AbstractAdminTest {
// get mapper
mapper = provider.getMapperById(id);
Assert.assertEquals("INHERIT", mappers.get(0).getConfig().get(IdentityProviderMapperModel.SYNC_MODE));
Assert.assertNotNull("mapperById not null", mapper);
Assert.assertEquals("mapper id", id, mapper.getId());
Assert.assertNotNull("mapper.config exists", mapper.getConfig());
@ -734,6 +747,7 @@ public class IdentityProviderTest extends AbstractAdminTest {
mapper.setName("my_mapper");
mapper.setIdentityProviderMapper("oidc-hardcoded-role-idp-mapper");
Map<String, String> config = new HashMap<>();
config.put(IdentityProviderMapperModel.SYNC_MODE, IdentityProviderMapperSyncMode.INHERIT.toString());
config.put("role", "");
mapper.setConfig(config);
@ -743,7 +757,7 @@ public class IdentityProviderTest extends AbstractAdminTest {
List<IdentityProviderMapperRepresentation> mappers = provider.getMappers();
assertEquals(1, mappers.size());
assertEquals(0, mappers.get(0).getConfig().size());
assertEquals(1, mappers.get(0).getConfig().size());
mapper = provider.getMapperById(mapperId);
mapper.getConfig().put("role", "offline_access");
@ -751,8 +765,9 @@ public class IdentityProviderTest extends AbstractAdminTest {
provider.update(mapperId, mapper);
mappers = provider.getMappers();
assertEquals("INHERIT", mappers.get(0).getConfig().get(IdentityProviderMapperModel.SYNC_MODE));
assertEquals(1, mappers.size());
assertEquals(1, mappers.get(0).getConfig().size());
assertEquals(2, mappers.get(0).getConfig().size());
assertEquals("offline_access", mappers.get(0).getConfig().get("role"));
}
@ -768,6 +783,7 @@ public class IdentityProviderTest extends AbstractAdminTest {
mapper.setName("my_mapper");
mapper.setIdentityProviderMapper("oidc-hardcoded-role-idp-mapper");
Map<String, String> config = new HashMap<>();
config.put(IdentityProviderMapperModel.SYNC_MODE, IdentityProviderMapperSyncMode.INHERIT.toString());
config.put("role", "offline_access");
mapper.setConfig(config);

View file

@ -1,23 +1,13 @@
package org.keycloak.testsuite.broker;
import java.net.URI;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientRequestFilter;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
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.common.util.Time;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ComponentRepresentation;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
@ -35,6 +25,19 @@ import org.keycloak.testsuite.util.OAuthClient;
import org.keycloak.testsuite.util.RealmBuilder;
import org.openqa.selenium.TimeoutException;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientRequestFilter;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import java.net.URI;
import java.util.Arrays;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
@ -63,21 +66,25 @@ public abstract class AbstractAdvancedBrokerTest extends AbstractBrokerTest {
protected void createRoleMappersForConsumerRealm() {
createRoleMappersForConsumerRealm(IdentityProviderMapperSyncMode.FORCE);
}
protected void createRoleMappersForConsumerRealm(IdentityProviderMapperSyncMode syncMode) {
log.debug("adding mappers to identity provider in realm " + bc.consumerRealmName());
RealmResource realm = adminClient.realm(bc.consumerRealmName());
IdentityProviderResource idpResource = realm.identityProviders().get(bc.getIDPAlias());
for (IdentityProviderMapperRepresentation mapper : createIdentityProviderMappers()) {
for (IdentityProviderMapperRepresentation mapper : createIdentityProviderMappers(syncMode)) {
mapper.setIdentityProviderAlias(bc.getIDPAlias());
Response resp = idpResource.addMapper(mapper);
resp.close();
}
}
protected abstract Iterable<IdentityProviderMapperRepresentation> createIdentityProviderMappers();
protected abstract Iterable<IdentityProviderMapperRepresentation> createIdentityProviderMappers(IdentityProviderMapperSyncMode syncMode);
protected abstract void createAdditionalMapperWithCustomSyncMode(IdentityProviderMapperSyncMode syncMode);
/**
* Refers to in old test suite: org.keycloak.testsuite.broker.AbstractKeycloakIdentityProviderTest#testAccountManagementLinkIdentity
@ -315,18 +322,35 @@ public abstract class AbstractAdvancedBrokerTest extends AbstractBrokerTest {
assertEquals("Account is disabled, contact your administrator.", errorPage.getError());
}
// KEYCLOAK-3987
@Test
public void grantNewRoleFromToken() {
public void mapperDoesNotGrantNewRoleFromTokenWithSyncModeImport() {
testMapperAssigningRoles(IdentityProviderMapperSyncMode.IMPORT, false);
}
@Test
public void mapperGrantsNewRoleFromTokenWithInheritedSyncModeForce() {
RealmResource realm = adminClient.realm(bc.consumerRealmName());
realm.identityProviders().get(bc.getIDPAlias())
.update(bc.setUpIdentityProvider(suiteContext, IdentityProviderSyncMode.FORCE));
testMapperAssigningRoles(IdentityProviderMapperSyncMode.INHERIT, true);
}
@Test
public void mapperDoesNotGrantNewRoleFromTokenWithInheritedSyncModeImport() {
RealmResource realm = adminClient.realm(bc.consumerRealmName());
realm.identityProviders().get(bc.getIDPAlias())
.update(bc.setUpIdentityProvider(suiteContext, IdentityProviderSyncMode.IMPORT));
testMapperAssigningRoles(IdentityProviderMapperSyncMode.INHERIT, false);
}
private void testMapperAssigningRoles(IdentityProviderMapperSyncMode anImport, boolean isAssigned) {
createRolesForRealm(bc.providerRealmName());
createRolesForRealm(bc.consumerRealmName());
createRoleMappersForConsumerRealm();
createRoleMappersForConsumerRealm(anImport);
RoleRepresentation managerRole = adminClient.realm(bc.providerRealmName()).roles().get(ROLE_MANAGER).toRepresentation();
RoleRepresentation userRole = adminClient.realm(bc.providerRealmName()).roles().get(ROLE_USER).toRepresentation();
@ -336,7 +360,9 @@ public abstract class AbstractAdvancedBrokerTest extends AbstractBrokerTest {
logInAsUserInIDPForFirstTime();
Set<String> currentRoles = userResource.roles().realmLevel().listAll().stream()
UserResource consumerUserResource = adminClient.realm(bc.consumerRealmName()).users().get(
adminClient.realm(bc.consumerRealmName()).users().search(bc.getUserLogin()).get(0).getId());
Set<String> currentRoles = consumerUserResource.roles().realmLevel().listAll().stream()
.map(RoleRepresentation::getName)
.collect(Collectors.toSet());
@ -350,15 +376,63 @@ public abstract class AbstractAdvancedBrokerTest extends AbstractBrokerTest {
logInAsUserInIDP();
currentRoles = userResource.roles().realmLevel().listAll().stream()
currentRoles = consumerUserResource.roles().realmLevel().listAll().stream()
.map(RoleRepresentation::getName)
.collect(Collectors.toSet());
assertThat(currentRoles, hasItems(ROLE_MANAGER, ROLE_USER));
if (isAssigned) {
assertThat(currentRoles, hasItems(ROLE_MANAGER, ROLE_USER));
} else {
assertThat(currentRoles, hasItems(ROLE_MANAGER));
assertThat(currentRoles, not(hasItems(ROLE_USER)));
}
logoutFromRealm(bc.providerRealmName());
logoutFromRealm(bc.consumerRealmName());
logoutFromRealm(bc.providerRealmName());
}
@Test
public void differentMappersCanHaveDifferentSyncModes() {
createRolesForRealm(bc.providerRealmName());
createRolesForRealm(bc.consumerRealmName());
createRoleMappersForConsumerRealm(IdentityProviderMapperSyncMode.INHERIT);
createAdditionalMapperWithCustomSyncMode(IdentityProviderMapperSyncMode.FORCE);
RoleRepresentation managerRole = adminClient.realm(bc.providerRealmName()).roles().get(ROLE_MANAGER).toRepresentation();
RoleRepresentation userRole = adminClient.realm(bc.providerRealmName()).roles().get(ROLE_USER).toRepresentation();
RoleRepresentation friendlyManagerRole = adminClient.realm(bc.providerRealmName()).roles().get(ROLE_FRIENDLY_MANAGER).toRepresentation();
UserResource userResource = adminClient.realm(bc.providerRealmName()).users().get(userId);
userResource.roles().realmLevel().add(Collections.singletonList(managerRole));
logInAsUserInIDPForFirstTime();
UserResource consumerUserResource = adminClient.realm(bc.consumerRealmName()).users().get(
adminClient.realm(bc.consumerRealmName()).users().search(bc.getUserLogin()).get(0).getId());
Set<String> currentRoles = consumerUserResource.roles().realmLevel().listAll().stream()
.map(RoleRepresentation::getName)
.collect(Collectors.toSet());
assertThat(currentRoles, hasItems(ROLE_MANAGER));
assertThat(currentRoles, not(hasItems(ROLE_USER, ROLE_FRIENDLY_MANAGER)));
logoutFromRealm(bc.consumerRealmName());
userResource.roles().realmLevel().add(Arrays.asList(userRole, friendlyManagerRole));
logInAsUserInIDP();
currentRoles = consumerUserResource.roles().realmLevel().listAll().stream()
.map(RoleRepresentation::getName)
.collect(Collectors.toSet());
assertThat(currentRoles, hasItems(ROLE_MANAGER, ROLE_FRIENDLY_MANAGER));
assertThat(currentRoles, not(hasItems(ROLE_USER)));
logoutFromRealm(bc.consumerRealmName());
logoutFromRealm(bc.providerRealmName());
}
// KEYCLOAK-4016
@Test

View file

@ -14,6 +14,8 @@ 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.HardcodedUserSessionAttributeMapper;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
@ -582,6 +584,7 @@ public abstract class AbstractFirstBrokerLoginTest extends AbstractInitializedBa
hardCodedSessionNoteMapper.setIdentityProviderAlias(bc.getIDPAlias());
hardCodedSessionNoteMapper.setIdentityProviderMapper(HardcodedUserSessionAttributeMapper.PROVIDER_ID);
hardCodedSessionNoteMapper.setConfig(ImmutableMap.<String, String>builder()
.put(IdentityProviderMapperModel.SYNC_MODE, IdentityProviderSyncMode.IMPORT.toString())
.put(HardcodedUserSessionAttributeMapper.ATTRIBUTE_VALUE, "sessionvalue")
.put(HardcodedUserSessionAttributeMapper.ATTRIBUTE, "user-session-attr")
.build());

View file

@ -0,0 +1,84 @@
package org.keycloak.testsuite.broker;
import org.junit.Before;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.MappingsRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.util.UserBuilder;
import javax.ws.rs.core.Response;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.Assert.assertThat;
import static org.keycloak.testsuite.admin.ApiUtil.createUserAndResetPasswordWithAdminClient;
/**
* @author hmlnarik
* <a href="mailto:external.benjamin.weimer@bosch-si.com">Benjamin Weimer</a>,
* <a href="mailto:external.martin.idel@bosch.io">Martin Idel</a>,
*/
public abstract class AbstractIdentityProviderMapperTest extends AbstractBaseBrokerTest {
protected RealmResource realm;
@Before
public void addClients() {
addClientsToProviderAndConsumer();
realm = adminClient.realm(bc.consumerRealmName());
}
protected IdentityProviderRepresentation setupIdentityProvider() {
log.debug("adding identity provider to realm " + bc.consumerRealmName());
final IdentityProviderRepresentation idp = bc.setUpIdentityProvider(suiteContext);
realm.identityProviders().create(idp).close();
return idp;
}
protected void createUserInProviderRealm(Map<String, List<String>> attributes) {
log.debug("Creating user in realm " + bc.providerRealmName());
UserRepresentation user = UserBuilder.create()
.username(bc.getUserLogin())
.email(bc.getUserEmail())
.build();
user.setEmailVerified(true);
user.setAttributes(attributes);
this.userId = createUserAndResetPasswordWithAdminClient(adminClient.realm(bc.providerRealmName()), user, bc.getUserPassword());
}
protected UserRepresentation findUser(String realm, String userName, String email) {
UsersResource consumerUsers = adminClient.realm(realm).users();
List<UserRepresentation> users = consumerUsers.list();
assertThat("There must be exactly one user", users, hasSize(1));
UserRepresentation user = users.get(0);
assertThat("Username has to match", user.getUsername(), equalTo(userName));
assertThat("Email has to match", user.getEmail(), equalTo(email));
MappingsRepresentation roles = consumerUsers.get(user.getId()).roles().getAll();
List<String> realmRoles = roles.getRealmMappings().stream()
.map(RoleRepresentation::getName)
.collect(Collectors.toList());
user.setRealmRoles(realmRoles);
Map<String, List<String>> clientRoles = new HashMap<>();
roles.getClientMappings().forEach((key, value) -> clientRoles.put(key, value.getMappings().stream()
.map(RoleRepresentation::getName)
.collect(Collectors.toList())));
user.setClientRoles(clientRoles);
return user;
}
}

View file

@ -0,0 +1,79 @@
package org.keycloak.testsuite.broker;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertThat;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
/**
* @author hmlnarik,
* <a href="mailto:external.benjamin.weimer@bosch-si.com">Benjamin Weimer</a>,
* <a href="mailto:external.martin.idel@bosch.io">Martin Idel</a>,
*/
public abstract class AbstractRoleMapperTest extends AbstractIdentityProviderMapperTest {
private static final String CLIENT = "realm-management";
private static final String CLIENT_ROLE = "view-realm";
public static final String ROLE_USER = "user";
public static final String CLIENT_ROLE_MAPPER_REPRESENTATION = CLIENT + "." + CLIENT_ROLE;
protected abstract void createMapperInIdp(IdentityProviderRepresentation idp, IdentityProviderMapperSyncMode syncMode);
protected void updateUser() {
}
protected UserRepresentation loginAsUserTwiceWithMapper(
IdentityProviderMapperSyncMode syncMode, boolean createAfterFirstLogin, Map<String, List<String>> userConfig) {
final IdentityProviderRepresentation idp = setupIdentityProvider();
if (!createAfterFirstLogin) {
createMapperInIdp(idp, syncMode);
}
createUserInProviderRealm(userConfig);
createUserRoleAndGrantToUserInProviderRealm();
logInAsUserInIDPForFirstTime();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
if (!createAfterFirstLogin) {
assertThatRoleHasBeenAssignedInConsumerRealmTo(user);
} else {
assertThatRoleHasNotBeenAssignedInConsumerRealmTo(user);
}
if (createAfterFirstLogin) {
createMapperInIdp(idp, syncMode);
}
logoutFromRealm(bc.consumerRealmName());
updateUser();
logInAsUserInIDP();
user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
return user;
}
protected void createUserRoleAndGrantToUserInProviderRealm() {
RoleRepresentation userRole = new RoleRepresentation(ROLE_USER,null, false);
adminClient.realm(bc.providerRealmName()).roles().create(userRole);
RoleRepresentation role = adminClient.realm(bc.providerRealmName()).roles().get(ROLE_USER).toRepresentation();
UserResource userResource = adminClient.realm(bc.providerRealmName()).users().get(userId);
userResource.roles().realmLevel().add(Collections.singletonList(role));
}
protected void assertThatRoleHasBeenAssignedInConsumerRealmTo(UserRepresentation user) {
assertThat(user.getClientRoles().get(CLIENT), contains(CLIENT_ROLE));
}
protected void assertThatRoleHasNotBeenAssignedInConsumerRealmTo(UserRepresentation user) {
assertThat(user.getClientRoles().get(CLIENT), not(contains(CLIENT_ROLE)));
}
}

View file

@ -1,33 +1,32 @@
package org.keycloak.testsuite.broker;
import org.keycloak.admin.client.resource.IdentityProviderResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.util.UserBuilder;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.ws.rs.core.Response;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.hamcrest.Matchers.*;
import static org.keycloak.testsuite.admin.ApiUtil.*;
import org.junit.Test;
import org.keycloak.admin.client.resource.IdentityProviderResource;
import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
/**
*
* @author hmlnarik
*/
public abstract class AbstractUserAttributeMapperTest extends AbstractBaseBrokerTest {
public abstract class AbstractUserAttributeMapperTest extends AbstractIdentityProviderMapperTest {
protected static final String MAPPED_ATTRIBUTE_NAME = "mapped-user-attribute";
protected static final String MAPPED_ATTRIBUTE_FRIENDLY_NAME = "mapped-user-attribute-friendly";
@ -41,60 +40,18 @@ public abstract class AbstractUserAttributeMapperTest extends AbstractBaseBroker
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, MAPPED_ATTRIBUTE_NAME)
.build();
protected abstract Iterable<IdentityProviderMapperRepresentation> createIdentityProviderMappers();
protected abstract Iterable<IdentityProviderMapperRepresentation> createIdentityProviderMappers(IdentityProviderMapperSyncMode syncMode);
@Before
public void addIdentityProviderToConsumerRealm() {
log.debug("adding identity provider to realm " + bc.consumerRealmName());
RealmResource realm = adminClient.realm(bc.consumerRealmName());
final IdentityProviderRepresentation idp = bc.setUpIdentityProvider(suiteContext);
Response resp = realm.identityProviders().create(idp);
resp.close();
public void addIdentityProviderToConsumerRealm(IdentityProviderMapperSyncMode syncMode) {
IdentityProviderRepresentation idp = setupIdentityProvider();
IdentityProviderResource idpResource = realm.identityProviders().get(idp.getAlias());
for (IdentityProviderMapperRepresentation mapper : createIdentityProviderMappers()) {
for (IdentityProviderMapperRepresentation mapper : createIdentityProviderMappers(syncMode)) {
mapper.setIdentityProviderAlias(bc.getIDPAlias());
resp = idpResource.addMapper(mapper);
resp.close();
idpResource.addMapper(mapper).close();
}
}
@Before
public void addClients() {
addClientsToProviderAndConsumer();
}
protected void createUserInProviderRealm(Map<String, List<String>> attributes) {
log.debug("creating user in realm " + bc.providerRealmName());
UserRepresentation user = UserBuilder.create()
.username(bc.getUserLogin())
.email(bc.getUserEmail())
.build();
user.setEmailVerified(true);
user.setAttributes(attributes);
this.userId = createUserAndResetPasswordWithAdminClient(adminClient.realm(bc.providerRealmName()), user, bc.getUserPassword());
}
private UserRepresentation findUser(String realm, String userName, String email) {
UsersResource consumerUsers = adminClient.realm(realm).users();
int userCount = consumerUsers.count();
assertThat("There must be at least one user", userCount, greaterThan(0));
List<UserRepresentation> users = consumerUsers.search("", 0, userCount);
for (UserRepresentation user : users) {
if (user.getUsername().equals(userName) && user.getEmail().equals(email)) {
return user;
}
}
fail("User " + userName + " not found in " + realm + " realm");
return null;
}
private void assertUserAttributes(Map<String, List<String>> attrs, UserRepresentation userRep) {
Set<String> mappedAttrNames = attrs.entrySet().stream()
.filter(me -> me.getValue() != null && ! me.getValue().isEmpty())
@ -127,7 +84,22 @@ public abstract class AbstractUserAttributeMapperTest extends AbstractBaseBroker
}
}
protected void testValueMapping(Map<String, List<String>> initialUserAttributes, Map<String, List<String>> modifiedUserAttributes) {
private void testValueMappingForImportSyncMode(Map<String, List<String>> initialUserAttributes, Map<String, List<String>> modifiedUserAttributes) {
addIdentityProviderToConsumerRealm(IdentityProviderMapperSyncMode.IMPORT);
testValueMapping(initialUserAttributes, modifiedUserAttributes, initialUserAttributes);
}
private void testValueMappingForForceSyncMode(Map<String, List<String>> initialUserAttributes, Map<String, List<String>> modifiedUserAttributes) {
addIdentityProviderToConsumerRealm(IdentityProviderMapperSyncMode.FORCE);
testValueMapping(initialUserAttributes, modifiedUserAttributes, modifiedUserAttributes);
}
private void testValueMappingForLegacySyncMode(Map<String, List<String>> initialUserAttributes, Map<String, List<String>> modifiedUserAttributes) {
addIdentityProviderToConsumerRealm(IdentityProviderMapperSyncMode.LEGACY);
testValueMapping(initialUserAttributes, modifiedUserAttributes, modifiedUserAttributes);
}
private void testValueMapping(Map<String, List<String>> initialUserAttributes, Map<String, List<String>> modifiedUserAttributes, Map<String, List<String>> assertedModifiedAttributes) {
String email = bc.getUserEmail();
createUserInProviderRealm(initialUserAttributes);
@ -160,12 +132,23 @@ public abstract class AbstractUserAttributeMapperTest extends AbstractBaseBroker
logInAsUserInIDP();
userRep = findUser(bc.consumerRealmName(), bc.getUserLogin(), email);
assertUserAttributes(modifiedUserAttributes, userRep);
assertUserAttributes(assertedModifiedAttributes, userRep);
}
@Test
public void testBasicMappingSingleValue() {
testValueMapping(ImmutableMap.<String, List<String>>builder()
public void testBasicMappingSingleValueForce() {
testValueMappingForForceSyncMode(ImmutableMap.<String, List<String>>builder()
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("value 1").build())
.build(),
ImmutableMap.<String, List<String>>builder()
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("second value").build())
.build()
);
}
@Test
public void testBasicMappingSingleValueImport() {
testValueMappingForImportSyncMode(ImmutableMap.<String, List<String>>builder()
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("value 1").build())
.build(),
ImmutableMap.<String, List<String>>builder()
@ -176,7 +159,7 @@ public abstract class AbstractUserAttributeMapperTest extends AbstractBaseBroker
@Test
public void testBasicMappingEmail() {
testValueMapping(ImmutableMap.<String, List<String>>builder()
testValueMappingForForceSyncMode(ImmutableMap.<String, List<String>>builder()
.put("email", ImmutableList.<String>builder().add(bc.getUserEmail()).build())
.put("nested.email", ImmutableList.<String>builder().add(bc.getUserEmail()).build())
.put("dotted.email", ImmutableList.<String>builder().add(bc.getUserEmail()).build())
@ -190,8 +173,8 @@ public abstract class AbstractUserAttributeMapperTest extends AbstractBaseBroker
}
@Test
public void testBasicMappingClearValue() {
testValueMapping(ImmutableMap.<String, List<String>>builder()
public void testBasicMappingAttributeGetsModifiedInSyncModeForce() {
testValueMappingForForceSyncMode(ImmutableMap.<String, List<String>>builder()
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("value 1").build())
.build(),
ImmutableMap.<String, List<String>>builder()
@ -201,8 +184,8 @@ public abstract class AbstractUserAttributeMapperTest extends AbstractBaseBroker
}
@Test
public void testBasicMappingRemoveValue() {
testValueMapping(ImmutableMap.<String, List<String>>builder()
public void testBasicMappingAttributeGetsRemovedInSyncModeForce() {
testValueMappingForForceSyncMode(ImmutableMap.<String, List<String>>builder()
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("value 1").build())
.build(),
ImmutableMap.<String, List<String>>builder()
@ -211,8 +194,8 @@ public abstract class AbstractUserAttributeMapperTest extends AbstractBaseBroker
}
@Test
public void testBasicMappingMultipleValues() {
testValueMapping(ImmutableMap.<String, List<String>>builder()
public void testBasicMappingAttributeWithMultipleValuesIsModifiedInSyncModeForce() {
testValueMappingForForceSyncMode(ImmutableMap.<String, List<String>>builder()
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("value 1").add("value 2").build())
.build(),
ImmutableMap.<String, List<String>>builder()
@ -222,8 +205,9 @@ public abstract class AbstractUserAttributeMapperTest extends AbstractBaseBroker
}
@Test
public void testAddBasicMappingMultipleValues() {
testValueMapping(ImmutableMap.<String, List<String>>builder()
public void testBasicMappingAttributeWithMultipleValuesIsModifiedInSyncModeLegacy() {
testValueMappingForLegacySyncMode(ImmutableMap.<String, List<String>>builder()
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("value 1").add("value 2").build())
.build(),
ImmutableMap.<String, List<String>>builder()
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("second value").add("second value 2").build())
@ -232,11 +216,32 @@ public abstract class AbstractUserAttributeMapperTest extends AbstractBaseBroker
}
@Test
public void testDeleteBasicMappingMultipleValues() {
testValueMapping(ImmutableMap.<String, List<String>>builder()
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("second value").add("second value 2").build())
public void testBasicMappingAttributeWithMultipleValuesDoesNotGetModifiedInSyncModeImport() {
testValueMappingForImportSyncMode(ImmutableMap.<String, List<String>>builder()
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("value 1").add("value 2").build())
.build(),
ImmutableMap.<String, List<String>>builder()
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("second value").add("second value 2").build())
.build()
);
}
@Test
public void testBasicMappingAttributeWithMultipleValuesGetsAddedInSyncModeForce() {
testValueMappingForForceSyncMode(ImmutableMap.<String, List<String>>builder()
.build(),
ImmutableMap.<String, List<String>>builder()
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("second value").add("second value 2").build())
.build()
);
}
@Test
public void testBasicMappingAttributeWithMultipleValuesDoesNotGetAddedInSyncModeImport() {
testValueMappingForImportSyncMode(ImmutableMap.<String, List<String>>builder()
.build(),
ImmutableMap.<String, List<String>>builder()
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("second value").add("second value 2").build())
.build()
);
}

View file

@ -0,0 +1,102 @@
package org.keycloak.testsuite.broker;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.keycloak.testsuite.broker.BrokerTestTools.waitForPage;
import static org.keycloak.testsuite.broker.KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME;
import java.util.List;
import org.junit.Test;
import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.Assert;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
/**
* @author <a href="mailto:external.martin.idel@bosch.io">Martin Idel</a>,
*/
public abstract class AbstractUsernameTemplateMapperTest extends AbstractIdentityProviderMapperTest {
protected abstract String getMapperTemplate();
protected abstract void createMapperInIdp(IdentityProviderRepresentation idp, IdentityProviderMapperSyncMode syncMode);
@Test
public void testUsernameGetsInsertedFromClaim() {
loginAsUserTwiceWithMapperWillNotUpdateUsername(IdentityProviderMapperSyncMode.IMPORT);
}
@Test
public void testUsernameGetsUpdatedFromClaimInForceMode() {
loginAsUserTwiceWithMapperUpdatesUsername(IdentityProviderMapperSyncMode.FORCE);
}
@Test
public void testUsernameDoesNotGetUpdatedInLegacyMode() {
loginAsUserTwiceWithMapperWillNotUpdateUsername(IdentityProviderMapperSyncMode.LEGACY);
}
private void loginAsUserTwiceWithMapperUpdatesUsername(IdentityProviderMapperSyncMode syncMode) {
loginAsUserTwiceWithMapper(syncMode, "customusername", "newname", true);
}
private void loginAsUserTwiceWithMapperWillNotUpdateUsername(IdentityProviderMapperSyncMode syncMode) {
loginAsUserTwiceWithMapper(syncMode, "customusername", "newname", false);
}
private void loginAsUserTwiceWithMapper(
IdentityProviderMapperSyncMode syncMode, String userName, String updatedUserName, boolean updatingUserName) {
final IdentityProviderRepresentation idp = setupIdentityProvider();
createMapperInIdp(idp, syncMode);
// The ATTRIBUTE_TO_MAP_NAME gets mapped to a claim by the setup. It's value will always be an array, therefore the [] around the value
createUserInProviderRealm(ImmutableMap.<String, List<String>>builder()
.put(ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add(userName).build())
.build());
logInAsUserInIDPForFirstTime();
String mappedUserName = String.format(getMapperTemplate(), userName);
findUser(bc.consumerRealmName(), mappedUserName, bc.getUserEmail());
logoutFromRealm(bc.consumerRealmName());
updateUser(updatedUserName);
logInAsUserInIDP();
String updatedMappedUserName = String.format(getMapperTemplate(), updatedUserName);
UserRepresentation user = findUser(bc.consumerRealmName(), updatingUserName ? updatedMappedUserName : mappedUserName, bc.getUserEmail());
if (updatingUserName) {
assertThat(user.getUsername(), is(updatedMappedUserName));
} else {
assertThat(user.getUsername(), is(mappedUserName));
}
}
// We don't want to update the username - that needs to be done by the mapper
@Override
protected void logInAsUserInIDPForFirstTime() {
logInAsUserInIDP();
waitForPage(driver, "update account information", false);
Assert.assertTrue(updateAccountInformationPage.isCurrent());
Assert.assertTrue("We must be on correct realm right now",
driver.getCurrentUrl().contains("/auth/realms/" + bc.consumerRealmName() + "/"));
log.debug("Updating info on updateAccount page");
updateAccountInformationPage.updateAccountInformation(bc.getUserEmail(), "FirstName", "LastName");
}
private void updateUser(String updatedUserName) {
UserRepresentation user = findUser(bc.providerRealmName(), bc.getUserLogin(), bc.getUserEmail());
ImmutableMap<String, List<String>> matchingAttributes = ImmutableMap.<String, List<String>>builder()
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add(updatedUserName).build())
.build();
user.setAttributes(matchingAttributes);
adminClient.realm(bc.providerRealmName()).users().get(user.getId()).update(user);
}
}

View file

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

View file

@ -1,5 +1,6 @@
package org.keycloak.testsuite.broker;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
@ -30,7 +31,14 @@ public interface BrokerConfiguration {
/**
* @return Representation of the identity provider for declaration in the broker
*/
IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext);
default IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext) {
return setUpIdentityProvider(suiteContext, IdentityProviderSyncMode.IMPORT);
}
/**
* @return Representation of the identity provider for declaration in the broker
*/
IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext, IdentityProviderSyncMode force);
/**
* @return Name of realm containing identity provider. Must be consistent with {@link #createProviderRealm()}

View file

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

View file

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

View file

@ -0,0 +1,134 @@
package org.keycloak.testsuite.broker;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertThat;
import static org.keycloak.models.IdentityProviderMapperSyncMode.FORCE;
import static org.keycloak.models.IdentityProviderMapperSyncMode.IMPORT;
import java.util.HashMap;
import org.junit.Test;
import org.keycloak.admin.client.resource.IdentityProviderResource;
import org.keycloak.broker.provider.HardcodedAttributeMapper;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import com.google.common.collect.ImmutableMap;
/**
* <a href="mailto:external.martin.idel@bosch.io">Martin Idel</a>,
*/
public class HardcodedUserAttributeMapperTest extends AbstractIdentityProviderMapperTest {
private static final String USER_ATTRIBUTE = "user-attribute";
private static final String USER_ATTRIBUTE_VALUE = "user-attribute";
@Test
public void addHardcodedAttributeOnFirstLogin() {
final IdentityProviderRepresentation idp = setupIdentityProvider();
createMapperInIdp(idp, IMPORT);
createUserInProviderRealm();
logInAsUserInIDPForFirstTime();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
assertThatAttributeHasBeenAssigned(user);
}
@Test
public void hardcodedAttributeGetsAddedEvenIfMapperIsAddedLaterInSyncModeForce() {
UserRepresentation user = loginAsUserTwiceWithMapper(FORCE, true);
assertThatAttributeHasBeenAssigned(user);
}
@Test
public void hardcodedAttributeDoesNotGetAddedIfMapperIsAddedLaterInSyncModeImport() {
UserRepresentation user = loginAsUserTwiceWithMapper(IMPORT, true);
assertThatAttributeHasNotBeenAssigned(user);
}
@Test
public void hardcodedAttributeDoesNotGetAddedAgainInSyncModeImport() {
UserRepresentation user = loginAsUserTwiceWithMapper(IMPORT, false);
assertThatAttributeHasNotBeenAssigned(user);
}
@Test
public void hardcodedAttributeGetsUpdatedInSyncModeForce() {
UserRepresentation user = loginAsUserTwiceWithMapper(FORCE, false);
assertThatAttributeHasBeenAssigned(user);
}
protected UserRepresentation loginAsUserTwiceWithMapper(
IdentityProviderMapperSyncMode syncMode, boolean createAfterFirstLogin) {
final IdentityProviderRepresentation idp = setupIdentityProvider();
if (!createAfterFirstLogin) {
createMapperInIdp(idp, syncMode);
}
createUserInProviderRealm();
logInAsUserInIDPForFirstTime();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
if (!createAfterFirstLogin) {
assertThatAttributeHasBeenAssigned(user);
} else {
assertThatAttributeHasNotBeenAssigned(user);
}
if (createAfterFirstLogin) {
createMapperInIdp(idp, syncMode);
}
logoutFromRealm(bc.consumerRealmName());
if (user.getAttributes() != null) {
user.setAttributes(new HashMap<>());
}
adminClient.realm(bc.consumerRealmName()).users().get(user.getId()).update(user);
logInAsUserInIDP();
return findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
}
protected void createMapperInIdp(IdentityProviderRepresentation idp, IdentityProviderMapperSyncMode syncMode) {
IdentityProviderMapperRepresentation advancedClaimToRoleMapper = new IdentityProviderMapperRepresentation();
advancedClaimToRoleMapper.setName("hardcoded-attribute-mapper");
advancedClaimToRoleMapper.setIdentityProviderMapper(HardcodedAttributeMapper.PROVIDER_ID);
advancedClaimToRoleMapper.setConfig(ImmutableMap.<String, String>builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(HardcodedAttributeMapper.ATTRIBUTE, USER_ATTRIBUTE)
.put(HardcodedAttributeMapper.ATTRIBUTE_VALUE, USER_ATTRIBUTE_VALUE)
.build());
IdentityProviderResource idpResource = realm.identityProviders().get(idp.getAlias());
advancedClaimToRoleMapper.setIdentityProviderAlias(bc.getIDPAlias());
idpResource.addMapper(advancedClaimToRoleMapper).close();
}
protected void createUserInProviderRealm() {
createUserInProviderRealm(new HashMap<>());
}
protected void assertThatAttributeHasBeenAssigned(UserRepresentation user) {
assertThat(user.getAttributes().get(USER_ATTRIBUTE), contains(USER_ATTRIBUTE_VALUE));
}
protected void assertThatAttributeHasNotBeenAssigned(UserRepresentation user) {
if (user.getAttributes() != null) {
assertThat(user.getAttributes().get(USER_ATTRIBUTE), not(contains(USER_ATTRIBUTE_VALUE)));
}
}
@Override
protected BrokerConfiguration getBrokerConfiguration() {
return new KcOidcBrokerConfiguration();
}
}

View file

@ -0,0 +1,162 @@
package org.keycloak.testsuite.broker;
import com.google.common.collect.ImmutableMap;
import org.junit.Test;
import org.keycloak.admin.client.resource.IdentityProviderResource;
import org.keycloak.admin.client.resource.ProtocolMappersResource;
import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.protocol.oidc.mappers.HardcodedClaim;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.social.github.GitHubUserAttributeMapper;
import java.util.HashMap;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
import static org.keycloak.models.IdentityProviderMapperSyncMode.FORCE;
import static org.keycloak.models.IdentityProviderMapperSyncMode.IMPORT;
import static org.keycloak.models.IdentityProviderMapperSyncMode.LEGACY;
import static org.keycloak.testsuite.broker.KcOidcBrokerConfiguration.HARDOCDED_CLAIM;
import static org.keycloak.testsuite.broker.KcOidcBrokerConfiguration.HARDOCDED_VALUE;
import static org.keycloak.testsuite.broker.KcOidcBrokerConfiguration.USER_INFO_CLAIM;
/**
* @author <a href="mailto:external.martin.idel@bosch.io">Martin Idel</a>
*/
public class JsonUserAttributeMapperTest extends AbstractIdentityProviderMapperTest {
public static final String USER_ATTRIBUTE = "user-attribute";
@Override
protected BrokerConfiguration getBrokerConfiguration() {
return new KcOidcBrokerConfiguration();
}
@Test
public void loginWithIdentityProviderMapsJsonAttributeToUserAttributeButDoesNotModify() {
UserRepresentation user = createMapperThenModifyAttribute(IMPORT, "new-value");
assertUserAttribute(HARDOCDED_VALUE, user);
}
@Test
public void loginWithIdentityProviderDeletesAttributeInForceMode() {
UserRepresentation user = createMapperThenDeleteAttribute(FORCE);
assertAbsentUserAttribute(user);
}
@Test
public void loginWithIdentityProviderDoesNotDeleteAttributeInLegacyMode() {
UserRepresentation user = createMapperThenDeleteAttribute(LEGACY);
assertUserAttribute(HARDOCDED_VALUE, user);
}
@Test
public void loginWithIdentityProviderModifiesAttributeInForceMode() {
UserRepresentation user = createMapperThenModifyAttribute(FORCE, "new-value");
assertUserAttribute("new-value", user);
}
@Test
public void loginWithIdentityProviderAddsUserAttributeInForceNameWhenMapperIsCreatedLater() {
UserRepresentation user = loginAndThenCreateMapperThenLoginAgain(FORCE);
assertUserAttribute(HARDOCDED_VALUE, user);
}
@Test
public void loginWithIdentityProviderDoesNotAddUserAttributeInImportNameWhenMapperIsCreatedLater() {
UserRepresentation user = loginAndThenCreateMapperThenLoginAgain(IMPORT);
assertAbsentUserAttribute(user);
}
private UserRepresentation loginAndThenCreateMapperThenLoginAgain(IdentityProviderMapperSyncMode syncMode) {
return loginAsUserTwiceWithMapper(syncMode, true, HARDOCDED_CLAIM, HARDOCDED_VALUE);
}
private UserRepresentation createMapperThenDeleteAttribute(IdentityProviderMapperSyncMode syncMode) {
return loginAsUserTwiceWithMapper(syncMode, false, "deleted", "deleted");
}
private UserRepresentation createMapperThenModifyAttribute(IdentityProviderMapperSyncMode syncMode, String updatedValue) {
return loginAsUserTwiceWithMapper(syncMode, false, HARDOCDED_CLAIM, updatedValue);
}
private UserRepresentation loginAsUserTwiceWithMapper(
IdentityProviderMapperSyncMode syncMode, boolean createAfterFirstLogin, String claim, String updatedValue) {
final IdentityProviderRepresentation idp = setupIdentityProvider();
if (!createAfterFirstLogin) {
createGithubProviderMapper(idp, syncMode);
}
createUserInProviderRealm(new HashMap<>());
logInAsUserInIDPForFirstTime();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
if (!createAfterFirstLogin) {
assertUserAttribute(HARDOCDED_VALUE, user);
} else {
assertAbsentUserAttribute(user);
}
if (createAfterFirstLogin) {
createGithubProviderMapper(idp, syncMode);
}
logoutFromRealm(bc.consumerRealmName());
if (!createAfterFirstLogin) {
updateClaimSentToIDP(claim, updatedValue);
}
logInAsUserInIDP();
return findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
}
private void updateClaimSentToIDP(String claim, String updatedValue) {
ProtocolMapperRepresentation claimMapper = null;
ProtocolMappersResource protocolMappers = adminClient.realm(bc.providerRealmName()).clients().get(BrokerTestConstants.CLIENT_ID).getProtocolMappers();
for (ProtocolMapperRepresentation representation : protocolMappers.getMappers()) {
if (representation.getProtocolMapper().equals(HardcodedClaim.PROVIDER_ID)) {
claimMapper = representation;
}
}
assertThat(claimMapper, notNullValue());
claimMapper.getConfig().put(HardcodedClaim.CLAIM_VALUE, "{\"" + claim + "\": \"" + updatedValue + "\"}");
adminClient.realm(bc.providerRealmName()).clients().get(BrokerTestConstants.CLIENT_ID).getProtocolMappers().update(claimMapper.getId(), claimMapper);
}
private void assertUserAttribute(String value, UserRepresentation userRep) {
assertThat(userRep.getAttributes(), notNullValue());
assertThat(userRep.getAttributes().get(USER_ATTRIBUTE), containsInAnyOrder(value));
}
private void assertAbsentUserAttribute(UserRepresentation userRep) {
assertThat(userRep.getAttributes(), nullValue());
}
private void createGithubProviderMapper(IdentityProviderRepresentation idp, IdentityProviderMapperSyncMode syncMode) {
IdentityProviderMapperRepresentation githubProvider = new IdentityProviderMapperRepresentation();
githubProvider.setName("json-attribute-mapper");
githubProvider.setIdentityProviderMapper(GitHubUserAttributeMapper.PROVIDER_ID);
githubProvider.setConfig(ImmutableMap.<String, String>builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(AbstractJsonUserAttributeMapper.CONF_JSON_FIELD, USER_INFO_CLAIM + "." + HARDOCDED_CLAIM)
.put(AbstractJsonUserAttributeMapper.CONF_USER_ATTRIBUTE, USER_ATTRIBUTE)
.build());
IdentityProviderResource idpResource = realm.identityProviders().get(idp.getAlias());
githubProvider.setIdentityProviderAlias(bc.getIDPAlias());
idpResource.addMapper(githubProvider).close();
}
}

View file

@ -1,5 +1,6 @@
package org.keycloak.testsuite.broker;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.testsuite.arquillian.SuiteContext;
@ -21,10 +22,10 @@ public class KcOidcBrokerClientSecretBasicAuthTest extends AbstractBrokerTest {
private class KcOidcBrokerConfigurationWithBasicAuthAuthentication extends KcOidcBrokerConfiguration {
@Override
public IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext) {
public IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext, IdentityProviderSyncMode syncMode) {
IdentityProviderRepresentation idp = createIdentityProvider(IDP_OIDC_ALIAS, IDP_OIDC_PROVIDER_ID);
Map<String, String> config = idp.getConfig();
applyDefaultConfiguration(suiteContext, config);
applyDefaultConfiguration(suiteContext, config, syncMode);
config.put("clientAuthMethod", OIDCLoginProtocol.CLIENT_SECRET_BASIC);
return idp;
}

View file

@ -9,6 +9,7 @@ import java.util.List;
import java.util.Map;
import org.keycloak.authentication.authenticators.client.JWTClientSecretAuthenticator;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
@ -35,10 +36,10 @@ public class KcOidcBrokerClientSecretJwtTest extends AbstractBrokerTest {
}
@Override
public IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext) {
public IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext, IdentityProviderSyncMode syncMode) {
IdentityProviderRepresentation idp = createIdentityProvider(IDP_OIDC_ALIAS, IDP_OIDC_PROVIDER_ID);
Map<String, String> config = idp.getConfig();
applyDefaultConfiguration(suiteContext, config);
applyDefaultConfiguration(suiteContext, config, syncMode);
config.put("clientAuthMethod", OIDCLoginProtocol.CLIENT_SECRET_JWT);
return idp;
}

View file

@ -1,7 +1,10 @@
package org.keycloak.testsuite.broker;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.protocol.ProtocolMapperUtils;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.mappers.HardcodedClaim;
import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper;
import org.keycloak.protocol.oidc.mappers.UserAttributeMapper;
import org.keycloak.protocol.oidc.mappers.UserPropertyMapper;
@ -29,6 +32,9 @@ public class KcOidcBrokerConfiguration implements BrokerConfiguration {
protected static final String ATTRIBUTE_TO_MAP_NAME = "user-attribute";
protected static final String ATTRIBUTE_TO_MAP_NAME_2 = "user-attribute-2";
public static final String USER_INFO_CLAIM = "user-claim";
public static final String HARDOCDED_CLAIM = "test";
public static final String HARDOCDED_VALUE = "value";
@Override
public RealmRepresentation createProviderRealm() {
@ -131,7 +137,18 @@ public class KcOidcBrokerConfiguration implements BrokerConfiguration {
userAttrMapperConfig2.put(OIDCAttributeMapperHelper.INCLUDE_IN_USERINFO, "true");
userAttrMapperConfig2.put(ProtocolMapperUtils.MULTIVALUED, "true");
client.setProtocolMappers(Arrays.asList(emailMapper, userAttrMapper, userAttrMapper2, nestedAttrMapper, dottedAttrMapper));
ProtocolMapperRepresentation hardcodedJsonClaim = new ProtocolMapperRepresentation();
hardcodedJsonClaim.setName("json-mapper");
hardcodedJsonClaim.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
hardcodedJsonClaim.setProtocolMapper(HardcodedClaim.PROVIDER_ID);
Map<String, String> hardcodedJsonClaimMapperConfig = hardcodedJsonClaim.getConfig();
hardcodedJsonClaimMapperConfig.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, KcOidcBrokerConfiguration.USER_INFO_CLAIM);
hardcodedJsonClaimMapperConfig.put(OIDCAttributeMapperHelper.JSON_TYPE, "JSON");
hardcodedJsonClaimMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true");
hardcodedJsonClaimMapperConfig.put(HardcodedClaim.CLAIM_VALUE, "{\"" + HARDOCDED_CLAIM + "\": \"" + HARDOCDED_VALUE + "\"}");
client.setProtocolMappers(Arrays.asList(emailMapper, userAttrMapper, userAttrMapper2, nestedAttrMapper, dottedAttrMapper, hardcodedJsonClaim));
return Collections.singletonList(client);
}
@ -156,16 +173,17 @@ public class KcOidcBrokerConfiguration implements BrokerConfiguration {
}
@Override
public IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext) {
public IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext, IdentityProviderSyncMode syncMode) {
IdentityProviderRepresentation idp = createIdentityProvider(IDP_OIDC_ALIAS, IDP_OIDC_PROVIDER_ID);
Map<String, String> config = idp.getConfig();
applyDefaultConfiguration(suiteContext, config);
applyDefaultConfiguration(suiteContext, config, syncMode);
return idp;
}
protected void applyDefaultConfiguration(final SuiteContext suiteContext, final Map<String, String> config) {
protected void applyDefaultConfiguration(final SuiteContext suiteContext, final Map<String, String> config, IdentityProviderSyncMode syncMode) {
config.put(IdentityProviderModel.SYNC_MODE, syncMode.toString());
config.put("clientId", CLIENT_ID);
config.put("clientSecret", CLIENT_SECRET);
config.put("prompt", "login");

View file

@ -18,6 +18,7 @@ package org.keycloak.testsuite.broker;
import java.util.Map;
import org.junit.Test;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import static org.keycloak.testsuite.broker.BrokerTestTools.waitForPage;
@ -43,11 +44,11 @@ public class KcOidcBrokerHiddenIdpHintTest extends AbstractInitializedBaseBroker
private class KcOidcHiddenBrokerConfiguration extends KcOidcBrokerConfiguration {
@Override
public IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext) {
public IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext, IdentityProviderSyncMode syncMode) {
IdentityProviderRepresentation idp = createIdentityProvider(IDP_OIDC_ALIAS, IDP_OIDC_PROVIDER_ID);
Map<String, String> config = idp.getConfig();
applyDefaultConfiguration(suiteContext, config);
applyDefaultConfiguration(suiteContext, config, syncMode);
config.put("hideOnLoginPage", "true");
return idp;
}

View file

@ -13,6 +13,7 @@ import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
import org.junit.Test;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.Assert;
@ -30,11 +31,11 @@ public class KcOidcBrokerLoginHintTest extends AbstractBrokerTest {
private class KcOidcBrokerConfigurationWithLoginHint extends KcOidcBrokerConfiguration {
@Override
public IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext) {
public IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext, IdentityProviderSyncMode syncMode) {
IdentityProviderRepresentation idp = createIdentityProvider(IDP_OIDC_ALIAS, IDP_OIDC_PROVIDER_ID);
Map<String, String> config = idp.getConfig();
applyDefaultConfiguration(suiteContext, config);
applyDefaultConfiguration(suiteContext, config, syncMode);
config.put("loginHint", "true");
return idp;
}

View file

@ -10,6 +10,7 @@ import static org.keycloak.testsuite.broker.BrokerTestTools.createIdentityProvid
import static org.keycloak.testsuite.broker.BrokerTestTools.waitForPage;
import org.apache.commons.lang3.StringUtils;
import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.Assert;
@ -25,11 +26,11 @@ public class KcOidcBrokerNoLoginHintTest extends AbstractBrokerTest {
private class KcOidcBrokerConfigurationWithNoLoginHint extends KcOidcBrokerConfiguration {
@Override
public IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext) {
public IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext, IdentityProviderSyncMode syncMode) {
IdentityProviderRepresentation idp = createIdentityProvider(IDP_OIDC_ALIAS, IDP_OIDC_PROVIDER_ID);
Map<String, String> config = idp.getConfig();
applyDefaultConfiguration(suiteContext, config);
applyDefaultConfiguration(suiteContext, config, syncMode);
config.put("loginHint", "false");
return idp;
}

View file

@ -11,6 +11,7 @@ import java.util.List;
import java.util.Map;
import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.Assert;
@ -31,10 +32,10 @@ public class KcOidcBrokerParameterForwardTest extends AbstractBrokerTest {
private class KcOidcBrokerConfigurationWithParameterForward extends KcOidcBrokerConfiguration {
@Override
public IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext) {
public IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext, IdentityProviderSyncMode syncMode) {
IdentityProviderRepresentation idp = createIdentityProvider(IDP_OIDC_ALIAS, IDP_OIDC_PROVIDER_ID);
Map<String, String> config = idp.getConfig();
applyDefaultConfiguration(suiteContext, config);
applyDefaultConfiguration(suiteContext, config, syncMode);
config.put("forwardParameters", FORWARDED_PARAMETER +", " + PARAMETER_NOT_SET);
return idp;
}

View file

@ -18,6 +18,7 @@ package org.keycloak.testsuite.broker;
import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator;
import org.keycloak.crypto.Algorithm;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
@ -58,10 +59,10 @@ public class KcOidcBrokerPrivateKeyJwtTest extends AbstractBrokerTest {
}
@Override
public IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext) {
public IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext, IdentityProviderSyncMode syncMode) {
IdentityProviderRepresentation idp = createIdentityProvider(IDP_OIDC_ALIAS, IDP_OIDC_PROVIDER_ID);
Map<String, String> config = idp.getConfig();
applyDefaultConfiguration(suiteContext, config);
applyDefaultConfiguration(suiteContext, config, syncMode);
config.put("clientSecret", null);
config.put("clientAuthMethod", OIDCLoginProtocol.PRIVATE_KEY_JWT);
return idp;

View file

@ -21,6 +21,8 @@ import java.util.Map;
import org.junit.Test;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.Assert;
@ -226,8 +228,9 @@ public class KcOidcBrokerPromptNoneRedirectTest extends AbstractInitializedBaseB
* Override the default configuration to unset the {@code prompt} parameter and specify that the IDP accepts forwarded
* auth requests with {@code prompt=none}.
*/
protected void applyDefaultConfiguration(final SuiteContext suiteContext, final Map<String, String> config) {
super.applyDefaultConfiguration(suiteContext, config);
@Override
protected void applyDefaultConfiguration(final SuiteContext suiteContext, final Map<String, String> config, IdentityProviderSyncMode syncMode) {
super.applyDefaultConfiguration(suiteContext, config, syncMode);
config.remove("prompt");
config.put("acceptsPromptNoneForwardFromClient", "true");
}

View file

@ -1,6 +1,7 @@
package org.keycloak.testsuite.broker;
import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.Assert;
@ -75,8 +76,9 @@ public class KcOidcBrokerPromptParameterTest extends AbstractBrokerTest {
}
private class KcOidcBrokerConfiguration2 extends KcOidcBrokerConfiguration {
protected void applyDefaultConfiguration(final SuiteContext suiteContext, final Map<String, String> config) {
super.applyDefaultConfiguration(suiteContext, config);
@Override
protected void applyDefaultConfiguration(final SuiteContext suiteContext, final Map<String, String> config, IdentityProviderSyncMode syncMode) {
super.applyDefaultConfiguration(suiteContext, config, syncMode);
config.remove("prompt");
}
}

View file

@ -1,14 +1,5 @@
package org.keycloak.testsuite.broker;
import static org.keycloak.testsuite.admin.ApiUtil.removeUserByUsername;
import static org.keycloak.testsuite.broker.BrokerRunOnServerUtil.configurePostBrokerLoginWithOTP;
import static org.keycloak.testsuite.broker.BrokerTestConstants.REALM_PROV_NAME;
import static org.keycloak.testsuite.broker.BrokerTestTools.getAuthRoot;
import static org.keycloak.testsuite.broker.BrokerTestTools.waitForPage;
import static org.keycloak.testsuite.util.ProtocolMapperUtil.createHardcodedClaim;
import java.util.List;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import org.junit.Test;
@ -16,20 +7,39 @@ import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.ClientsResource;
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.UsersResource;
import org.keycloak.broker.oidc.OIDCIdentityProviderConfig;
import org.keycloak.broker.oidc.mappers.ExternalKeycloakRoleToRoleMapper;
import org.keycloak.broker.oidc.mappers.UserAttributeMapper;
import org.keycloak.crypto.Algorithm;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.Assert;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertThat;
import static org.keycloak.testsuite.admin.ApiUtil.removeUserByUsername;
import static org.keycloak.testsuite.broker.BrokerRunOnServerUtil.configurePostBrokerLoginWithOTP;
import static org.keycloak.testsuite.broker.BrokerTestConstants.REALM_PROV_NAME;
import static org.keycloak.testsuite.broker.BrokerTestTools.getAuthRoot;
import static org.keycloak.testsuite.broker.BrokerTestTools.waitForPage;
import static org.keycloak.testsuite.util.ProtocolMapperUtil.createHardcodedClaim;
/**
* Final class as it's not intended to be overriden. Feel free to remove "final" if you really know what you are doing.
*/
@ -41,11 +51,12 @@ public final class KcOidcBrokerTest extends AbstractAdvancedBrokerTest {
}
@Override
protected Iterable<IdentityProviderMapperRepresentation> createIdentityProviderMappers() {
protected Iterable<IdentityProviderMapperRepresentation> createIdentityProviderMappers(IdentityProviderMapperSyncMode syncMode) {
IdentityProviderMapperRepresentation attrMapper1 = new IdentityProviderMapperRepresentation();
attrMapper1.setName("manager-role-mapper");
attrMapper1.setIdentityProviderMapper(ExternalKeycloakRoleToRoleMapper.PROVIDER_ID);
attrMapper1.setConfig(ImmutableMap.<String,String>builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put("external.role", ROLE_MANAGER)
.put("role", ROLE_MANAGER)
.build());
@ -54,6 +65,7 @@ public final class KcOidcBrokerTest extends AbstractAdvancedBrokerTest {
attrMapper2.setName("user-role-mapper");
attrMapper2.setIdentityProviderMapper(ExternalKeycloakRoleToRoleMapper.PROVIDER_ID);
attrMapper2.setConfig(ImmutableMap.<String,String>builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put("external.role", ROLE_USER)
.put("role", ROLE_USER)
.build());
@ -61,6 +73,63 @@ public final class KcOidcBrokerTest extends AbstractAdvancedBrokerTest {
return Lists.newArrayList(attrMapper1, attrMapper2);
}
@Override
protected void createAdditionalMapperWithCustomSyncMode(IdentityProviderMapperSyncMode syncMode) {
IdentityProviderMapperRepresentation friendlyManagerMapper = new IdentityProviderMapperRepresentation();
friendlyManagerMapper.setName("friendly-manager-role-mapper");
friendlyManagerMapper.setIdentityProviderMapper(ExternalKeycloakRoleToRoleMapper.PROVIDER_ID);
friendlyManagerMapper.setConfig(ImmutableMap.<String,String>builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put("external.role", ROLE_FRIENDLY_MANAGER)
.put("role", ROLE_FRIENDLY_MANAGER)
.build());
friendlyManagerMapper.setIdentityProviderAlias(bc.getIDPAlias());
RealmResource realm = adminClient.realm(bc.consumerRealmName());
IdentityProviderResource idpResource = realm.identityProviders().get(bc.getIDPAlias());
idpResource.addMapper(friendlyManagerMapper).close();
}
@Test
public void mapperDoesNothingForLegacyMode() {
createRolesForRealm(bc.providerRealmName());
createRolesForRealm(bc.consumerRealmName());
createRoleMappersForConsumerRealm(IdentityProviderMapperSyncMode.LEGACY);
RoleRepresentation managerRole = adminClient.realm(bc.providerRealmName()).roles().get(ROLE_MANAGER).toRepresentation();
RoleRepresentation userRole = adminClient.realm(bc.providerRealmName()).roles().get(ROLE_USER).toRepresentation();
UserResource userResource = adminClient.realm(bc.providerRealmName()).users().get(userId);
userResource.roles().realmLevel().add(Collections.singletonList(managerRole));
logInAsUserInIDPForFirstTime();
UserResource consumerUserResource = adminClient.realm(bc.consumerRealmName()).users().get(
adminClient.realm(bc.consumerRealmName()).users().search(bc.getUserLogin()).get(0).getId());
Set<String> currentRoles = consumerUserResource.roles().realmLevel().listAll().stream()
.map(RoleRepresentation::getName)
.collect(Collectors.toSet());
assertThat(currentRoles, hasItems(ROLE_MANAGER));
assertThat(currentRoles, not(hasItems(ROLE_USER)));
logoutFromRealm(bc.consumerRealmName());
userResource.roles().realmLevel().add(Collections.singletonList(userRole));
logInAsUserInIDP();
currentRoles = consumerUserResource.roles().realmLevel().listAll().stream()
.map(RoleRepresentation::getName)
.collect(Collectors.toSet());
assertThat(currentRoles, hasItems(ROLE_MANAGER));
assertThat(currentRoles, not(hasItems(ROLE_USER)));
logoutFromRealm(bc.consumerRealmName());
logoutFromRealm(bc.providerRealmName());
}
@Test
public void loginFetchingUserFromUserEndpoint() {
RealmResource realm = realmsResouce().realm(bc.providerRealmName());
@ -131,6 +200,7 @@ public final class KcOidcBrokerTest extends AbstractAdvancedBrokerTest {
hardCodedSessionNoteMapper.setIdentityProviderAlias(bc.getIDPAlias());
hardCodedSessionNoteMapper.setIdentityProviderMapper(UserAttributeMapper.PROVIDER_ID);
hardCodedSessionNoteMapper.setConfig(ImmutableMap.<String, String>builder()
.put(IdentityProviderMapperModel.SYNC_MODE, IdentityProviderMapperSyncMode.INHERIT.toString())
.put(UserAttributeMapper.USER_ATTRIBUTE, "hard-coded")
.put(UserAttributeMapper.CLAIM, "hard-coded")
.build());

View file

@ -1,6 +1,7 @@
package org.keycloak.testsuite.broker;
import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.Assert;
@ -28,10 +29,10 @@ public class KcOidcBrokerUiLocalesDisabledTest extends AbstractBrokerTest {
private class KcOidcBrokerConfigurationWithUiLocalesDisabled extends KcOidcBrokerConfiguration {
@Override
public IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext) {
public IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext, IdentityProviderSyncMode syncMode) {
IdentityProviderRepresentation idp = createIdentityProvider(IDP_OIDC_ALIAS, IDP_OIDC_PROVIDER_ID);
Map<String, String> config = idp.getConfig();
applyDefaultConfiguration(suiteContext, config);
applyDefaultConfiguration(suiteContext, config, syncMode);
config.put("uiLocales", "false");
return idp;
}

View file

@ -1,6 +1,7 @@
package org.keycloak.testsuite.broker;
import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.Assert;
@ -27,10 +28,10 @@ public class KcOidcBrokerUiLocalesEnabledTest extends AbstractBrokerTest {
private class KcOidcBrokerConfigurationWithUiLocalesEnabled extends KcOidcBrokerConfiguration {
@Override
public IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext) {
public IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext, IdentityProviderSyncMode syncMode) {
IdentityProviderRepresentation idp = createIdentityProvider(IDP_OIDC_ALIAS, IDP_OIDC_PROVIDER_ID);
Map<String, String> config = idp.getConfig();
applyDefaultConfiguration(suiteContext, config);
applyDefaultConfiguration(suiteContext, config, syncMode);
config.put("uiLocales", "true");
return idp;
}

View file

@ -1,5 +1,6 @@
package org.keycloak.testsuite.broker;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.testsuite.arquillian.SuiteContext;
@ -13,8 +14,8 @@ public class KcOidcBrokerVaultConfiguration extends KcOidcBrokerConfiguration {
public static final KcOidcBrokerVaultConfiguration INSTANCE = new KcOidcBrokerVaultConfiguration();
@Override
public IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext) {
IdentityProviderRepresentation idpRep = super.setUpIdentityProvider(suiteContext);
public IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext, IdentityProviderSyncMode syncMode) {
IdentityProviderRepresentation idpRep = super.setUpIdentityProvider(suiteContext, syncMode);
idpRep.getConfig().put("clientSecret", VAULT_CLIENT_SECRET);

View file

@ -0,0 +1,40 @@
package org.keycloak.testsuite.broker;
import org.keycloak.admin.client.resource.IdentityProviderResource;
import org.keycloak.broker.oidc.mappers.UsernameTemplateMapper;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import com.google.common.collect.ImmutableMap;
/**
* @author <a href="mailto:external.martin.idel@bosch.io">Martin Idel</a>
*/
public class KcOidcUsernameTemplateMapperTest extends AbstractUsernameTemplateMapperTest {
@Override
protected void createMapperInIdp(IdentityProviderRepresentation idp, IdentityProviderMapperSyncMode syncMode) {
IdentityProviderMapperRepresentation usernameTemplateMapper = new IdentityProviderMapperRepresentation();
usernameTemplateMapper.setName("oidc-username-template-mapper");
usernameTemplateMapper.setIdentityProviderMapper(UsernameTemplateMapper.PROVIDER_ID);
usernameTemplateMapper.setConfig(ImmutableMap.<String, String>builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put("template", "${ALIAS}-${CLAIM.user-attribute}")
.build());
IdentityProviderResource idpResource = realm.identityProviders().get(idp.getAlias());
usernameTemplateMapper.setIdentityProviderAlias(bc.getIDPAlias());
idpResource.addMapper(usernameTemplateMapper).close();
}
@Override
protected String getMapperTemplate() {
return "kc-oidc-idp-[%s]";
}
@Override
protected BrokerConfiguration getBrokerConfiguration() {
return new KcOidcBrokerConfiguration();
}
}

View file

@ -5,6 +5,8 @@
*/
package org.keycloak.testsuite.broker;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.protocol.ProtocolMapperUtils;
import org.keycloak.protocol.saml.SamlConfigAttributes;
import org.keycloak.protocol.saml.SamlProtocol;
@ -187,7 +189,7 @@ public class KcSamlBrokerConfiguration implements BrokerConfiguration {
}
@Override
public IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext) {
public IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext, IdentityProviderSyncMode syncMode) {
IdentityProviderRepresentation idp = createIdentityProvider(IDP_SAML_ALIAS, IDP_SAML_PROVIDER_ID);
idp.setTrustEmail(true);
@ -196,6 +198,7 @@ public class KcSamlBrokerConfiguration implements BrokerConfiguration {
Map<String, String> config = idp.getConfig();
config.put(IdentityProviderModel.SYNC_MODE, syncMode.toString());
config.put(SINGLE_SIGN_ON_SERVICE_URL, getAuthRoot(suiteContext) + "/auth/realms/" + REALM_PROV_NAME + "/protocol/saml");
config.put(SINGLE_LOGOUT_SERVICE_URL, getAuthRoot(suiteContext) + "/auth/realms/" + REALM_PROV_NAME + "/protocol/saml");
config.put(NAME_ID_POLICY_FORMAT, "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress");

View file

@ -1,7 +1,10 @@
package org.keycloak.testsuite.broker;
import org.keycloak.admin.client.resource.IdentityProviderResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.UserResource;
import com.google.common.collect.ImmutableMap;
import org.keycloak.broker.oidc.mappers.ExternalKeycloakRoleToRoleMapper;
import org.keycloak.broker.saml.mappers.AttributeToRoleMapper;
import org.keycloak.broker.saml.mappers.UserAttributeMapper;
import org.keycloak.dom.saml.v2.assertion.AssertionType;
@ -10,6 +13,9 @@ import org.keycloak.dom.saml.v2.assertion.AttributeType;
import org.keycloak.dom.saml.v2.assertion.StatementAbstractType;
import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
import org.keycloak.dom.saml.v2.protocol.ResponseType;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
@ -59,11 +65,12 @@ public final class KcSamlBrokerTest extends AbstractAdvancedBrokerTest {
private static final String EMPTY_ATTRIBUTE_NAME = "empty.attribute.name";
@Override
protected Iterable<IdentityProviderMapperRepresentation> createIdentityProviderMappers() {
protected Iterable<IdentityProviderMapperRepresentation> createIdentityProviderMappers(IdentityProviderMapperSyncMode syncMode) {
IdentityProviderMapperRepresentation attrMapper1 = new IdentityProviderMapperRepresentation();
attrMapper1.setName("manager-role-mapper");
attrMapper1.setIdentityProviderMapper(AttributeToRoleMapper.PROVIDER_ID);
attrMapper1.setConfig(ImmutableMap.<String,String>builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(UserAttributeMapper.ATTRIBUTE_NAME, "Role")
.put(ATTRIBUTE_VALUE, ROLE_MANAGER)
.put("role", ROLE_MANAGER)
@ -73,6 +80,7 @@ public final class KcSamlBrokerTest extends AbstractAdvancedBrokerTest {
attrMapper2.setName("user-role-mapper");
attrMapper2.setIdentityProviderMapper(AttributeToRoleMapper.PROVIDER_ID);
attrMapper2.setConfig(ImmutableMap.<String,String>builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(UserAttributeMapper.ATTRIBUTE_NAME, "Role")
.put(ATTRIBUTE_VALUE, ROLE_USER)
.put("role", ROLE_USER)
@ -82,6 +90,7 @@ public final class KcSamlBrokerTest extends AbstractAdvancedBrokerTest {
attrMapper3.setName("friendly-mapper");
attrMapper3.setIdentityProviderMapper(AttributeToRoleMapper.PROVIDER_ID);
attrMapper3.setConfig(ImmutableMap.<String,String>builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(UserAttributeMapper.ATTRIBUTE_FRIENDLY_NAME, AbstractUserAttributeMapperTest.ATTRIBUTE_TO_MAP_FRIENDLY_NAME)
.put(ATTRIBUTE_VALUE, ROLE_FRIENDLY_MANAGER)
.put("role", ROLE_FRIENDLY_MANAGER)
@ -91,6 +100,7 @@ public final class KcSamlBrokerTest extends AbstractAdvancedBrokerTest {
attrMapper4.setName("user-role-dot-guide-mapper");
attrMapper4.setIdentityProviderMapper(AttributeToRoleMapper.PROVIDER_ID);
attrMapper4.setConfig(ImmutableMap.<String,String>builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(UserAttributeMapper.ATTRIBUTE_NAME, "Role")
.put(ATTRIBUTE_VALUE, ROLE_USER_DOT_GUIDE)
.put("role", ROLE_USER_DOT_GUIDE)
@ -100,22 +110,37 @@ public final class KcSamlBrokerTest extends AbstractAdvancedBrokerTest {
attrMapper5.setName("empty-attribute-to-role-mapper");
attrMapper5.setIdentityProviderMapper(AttributeToRoleMapper.PROVIDER_ID);
attrMapper5.setConfig(ImmutableMap.<String,String>builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(UserAttributeMapper.ATTRIBUTE_NAME, EMPTY_ATTRIBUTE_NAME)
.put(ATTRIBUTE_VALUE, "")
.put("role", EMPTY_ATTRIBUTE_ROLE)
.build());
return Arrays.asList(new IdentityProviderMapperRepresentation[] { attrMapper1, attrMapper2, attrMapper3, attrMapper4, attrMapper5 });
return Arrays.asList(attrMapper1, attrMapper2, attrMapper3, attrMapper4, attrMapper5 );
}
protected void createAdditionalMapperWithCustomSyncMode(IdentityProviderMapperSyncMode syncMode) {
IdentityProviderMapperRepresentation friendlyManagerMapper = new IdentityProviderMapperRepresentation();
friendlyManagerMapper.setName("friendly-manager-role-mapper");
friendlyManagerMapper.setIdentityProviderMapper(AttributeToRoleMapper.PROVIDER_ID);
friendlyManagerMapper.setConfig(ImmutableMap.<String,String>builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(UserAttributeMapper.ATTRIBUTE_NAME, "Role")
.put(ATTRIBUTE_VALUE, ROLE_FRIENDLY_MANAGER)
.put("role", ROLE_FRIENDLY_MANAGER)
.build());
friendlyManagerMapper.setIdentityProviderAlias(bc.getIDPAlias());
RealmResource realm = adminClient.realm(bc.consumerRealmName());
IdentityProviderResource idpResource = realm.identityProviders().get(bc.getIDPAlias());
idpResource.addMapper(friendlyManagerMapper).close();
}
// KEYCLOAK-3987
@Test
@Override
public void grantNewRoleFromToken() {
public void mapperUpdatesRolesOnEveryLogInForLegacyMode() {
createRolesForRealm(bc.providerRealmName());
createRolesForRealm(bc.consumerRealmName());
createRoleMappersForConsumerRealm();
createRoleMappersForConsumerRealm(IdentityProviderMapperSyncMode.FORCE);
RoleRepresentation managerRole = adminClient.realm(bc.providerRealmName()).roles().get(ROLE_MANAGER).toRepresentation();
RoleRepresentation friendlyManagerRole = adminClient.realm(bc.providerRealmName()).roles().get(ROLE_FRIENDLY_MANAGER).toRepresentation();
@ -171,7 +196,6 @@ public final class KcSamlBrokerTest extends AbstractAdvancedBrokerTest {
createRoleMappersForConsumerRealm();
RoleRepresentation managerRole = adminClient.realm(bc.providerRealmName()).roles().get(ROLE_MANAGER).toRepresentation();
RoleRepresentation friendlyManagerRole = adminClient.realm(bc.providerRealmName()).roles().get(ROLE_FRIENDLY_MANAGER).toRepresentation();
RoleRepresentation userRole = adminClient.realm(bc.providerRealmName()).roles().get(ROLE_USER).toRepresentation();
RoleRepresentation userRoleDotGuide = adminClient.realm(bc.providerRealmName()).roles().get(ROLE_USER_DOT_GUIDE).toRepresentation();

View file

@ -3,6 +3,7 @@ package org.keycloak.testsuite.broker;
import org.keycloak.broker.saml.SAMLIdentityProviderConfig;
import org.keycloak.crypto.Algorithm;
import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.protocol.saml.SamlConfigAttributes;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
@ -112,8 +113,8 @@ public class KcSamlSignedBrokerTest extends AbstractBrokerTest {
}
@Override
public IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext) {
IdentityProviderRepresentation result = super.setUpIdentityProvider(suiteContext);
public IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext, IdentityProviderSyncMode syncMode) {
IdentityProviderRepresentation result = super.setUpIdentityProvider(suiteContext, syncMode);
String providerCert = KeyUtils.getActiveKey(adminClient.realm(providerRealmName()).keys().getKeyMetadata(), Algorithm.RS256).getCertificate();
Assert.assertThat(providerCert, Matchers.notNullValue());

View file

@ -1,5 +1,6 @@
package org.keycloak.testsuite.broker;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
@ -61,8 +62,8 @@ public class KcSamlSignedDocumentOnlyBrokerTest extends AbstractBrokerTest {
}
@Override
public IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext) {
IdentityProviderRepresentation result = super.setUpIdentityProvider(suiteContext);
public IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext, IdentityProviderSyncMode syncMode) {
IdentityProviderRepresentation result = super.setUpIdentityProvider(suiteContext, syncMode);
Map<String, String> config = result.getConfig();

View file

@ -0,0 +1,42 @@
package org.keycloak.testsuite.broker;
import static org.keycloak.broker.saml.mappers.UsernameTemplateMapper.PROVIDER_ID;
import org.keycloak.admin.client.resource.IdentityProviderResource;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import com.google.common.collect.ImmutableMap;
/**
* @author <a href="mailto:external.martin.idel@bosch.io">Martin Idel</a>
*/
public class KcSamlUsernameTemplateMapperTest extends AbstractUsernameTemplateMapperTest {
@Override
protected void createMapperInIdp(IdentityProviderRepresentation idp, IdentityProviderMapperSyncMode syncMode) {
IdentityProviderMapperRepresentation usernameTemplateMapper = new IdentityProviderMapperRepresentation();
usernameTemplateMapper.setName("saml-username-template-mapper");
usernameTemplateMapper.setIdentityProviderMapper(PROVIDER_ID);
usernameTemplateMapper.setConfig(ImmutableMap.<String, String>builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put("template", "${ALIAS}-${ATTRIBUTE.user-attribute}")
.build());
IdentityProviderResource idpResource = realm.identityProviders().get(idp.getAlias());
usernameTemplateMapper.setIdentityProviderAlias(bc.getIDPAlias());
idpResource.addMapper(usernameTemplateMapper).close();
}
@Override
protected String getMapperTemplate() {
return "kc-saml-idp-%s";
}
@Override
protected BrokerConfiguration getBrokerConfiguration() {
return new KcSamlBrokerConfiguration();
}
}

View file

@ -1,45 +1,32 @@
package org.keycloak.testsuite.broker;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.keycloak.models.IdentityProviderMapperSyncMode.FORCE;
import static org.keycloak.models.IdentityProviderMapperSyncMode.IMPORT;
import java.util.Arrays;
import java.util.List;
import org.junit.Test;
import org.keycloak.admin.client.resource.IdentityProviderResource;
import org.keycloak.broker.oidc.mappers.AdvancedClaimToRoleMapper;
import org.keycloak.broker.provider.ConfigConstants;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.admin.client.resource.IdentityProviderResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.broker.oidc.mappers.AdvancedClaimToRoleMapper;
import org.keycloak.broker.provider.ConfigConstants;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.MappingsRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.util.UserBuilder;
import javax.ws.rs.core.Response;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertThat;
import static org.keycloak.testsuite.admin.ApiUtil.createUserAndResetPasswordWithAdminClient;
/**
* @author hmlnarik, <a href="mailto:external.benjamin.weimer@bosch-si.com">Benjamin Weimer</a>
* @author hmlnarik,
* <a href="mailto:external.benjamin.weimer@bosch-si.com">Benjamin Weimer</a>,
* <a href="mailto:external.martin.idel@bosch.io">Martin Idel</a>
*/
public class OidcAdvancedClaimToRoleMapperTest extends AbstractBaseBrokerTest {
private static final String CLIENT = "realm-management";
private static final String CLIENT_ROLE = "view-realm";
private static final String CLIENT_ROLE_MAPPER_REPRESENTATION = CLIENT + "." + CLIENT_ROLE;
public class OidcAdvancedClaimToRoleMapperTest extends AbstractRoleMapperTest {
private static final String CLAIMS = "[\n" +
" {\n" +
@ -63,17 +50,13 @@ public class OidcAdvancedClaimToRoleMapperTest extends AbstractBaseBrokerTest {
" }\n" +
"]";
private String newValueForAttribute2 = "";
@Override
protected BrokerConfiguration getBrokerConfiguration() {
return new KcOidcBrokerConfiguration();
}
@Before
public void addClients() {
addClientsToProviderAndConsumer();
}
@Test
public void valueMatchesRegexTest() {
AdvancedClaimToRoleMapper advancedClaimToRoleMapper = new AdvancedClaimToRoleMapper();
@ -98,9 +81,7 @@ public class OidcAdvancedClaimToRoleMapperTest extends AbstractBaseBrokerTest {
logInAsUserInIDPForFirstTime();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
assertThatRoleHasBeenAssigned(user);
logoutFromRealm(bc.consumerRealmName());
assertThatRoleHasBeenAssignedInConsumerRealmTo(user);
}
@Test
@ -114,9 +95,7 @@ public class OidcAdvancedClaimToRoleMapperTest extends AbstractBaseBrokerTest {
logInAsUserInIDPForFirstTime();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
assertThatRoleHasNotBeenAssigned(user);
logoutFromRealm(bc.consumerRealmName());
assertThatRoleHasNotBeenAssignedInConsumerRealmTo(user);
}
@Test
@ -130,9 +109,7 @@ public class OidcAdvancedClaimToRoleMapperTest extends AbstractBaseBrokerTest {
logInAsUserInIDPForFirstTime();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
assertThatRoleHasBeenAssigned(user);
logoutFromRealm(bc.consumerRealmName());
assertThatRoleHasBeenAssignedInConsumerRealmTo(user);
}
@Test
@ -146,9 +123,7 @@ public class OidcAdvancedClaimToRoleMapperTest extends AbstractBaseBrokerTest {
logInAsUserInIDPForFirstTime();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
assertThatRoleHasBeenAssigned(user);
logoutFromRealm(bc.consumerRealmName());
assertThatRoleHasBeenAssignedInConsumerRealmTo(user);
}
@ -163,84 +138,76 @@ public class OidcAdvancedClaimToRoleMapperTest extends AbstractBaseBrokerTest {
logInAsUserInIDPForFirstTime();
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
assertThatRoleHasNotBeenAssigned(user);
logoutFromRealm(bc.consumerRealmName());
assertThatRoleHasNotBeenAssignedInConsumerRealmTo(user);
}
@Test
public void updateBrokeredUserMismatchDeletesRole() {
createAdvancedClaimToRoleMapper(CLAIMS, false);
createUserInProviderRealm(ImmutableMap.<String, List<String>>builder()
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("value 1").build())
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2, ImmutableList.<String>builder().add("value 2").build())
.build());
newValueForAttribute2 = "value mismatch";
UserRepresentation user = createMapperAndLoginAsUserTwiceWithMapper(FORCE, false);
logInAsUserInIDPForFirstTime();
assertThatRoleHasNotBeenAssignedInConsumerRealmTo(user);
}
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
assertThatRoleHasBeenAssigned(user);
@Test
public void updateBrokeredUserMismatchDoesNotDeleteRoleInImportMode() {
newValueForAttribute2 = "value mismatch";
UserRepresentation user = createMapperAndLoginAsUserTwiceWithMapper(IMPORT, false);
logoutFromRealm(bc.consumerRealmName());
// update
user = findUser(bc.providerRealmName(), bc.getUserLogin(), bc.getUserEmail());
ImmutableMap<String, List<String>> mismatchingAttributes = ImmutableMap.<String, List<String>>builder()
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("value 1").build())
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2, ImmutableList.<String>builder().add("value mismatch").build())
.build();
user.setAttributes(mismatchingAttributes);
adminClient.realm(bc.providerRealmName()).users().get(user.getId()).update(user);
logInAsUserInIDP();
user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
assertThatRoleHasNotBeenAssigned(user);
assertThatRoleHasBeenAssignedInConsumerRealmTo(user);
}
@Test
public void updateBrokeredUserMatchDoesntDeleteRole() {
createAdvancedClaimToRoleMapper(CLAIMS, false);
createUserInProviderRealm(ImmutableMap.<String, List<String>>builder()
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("value 1").build())
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2, ImmutableList.<String>builder().add("value 2").build())
.build());
newValueForAttribute2 = "value 2";
UserRepresentation user = createMapperAndLoginAsUserTwiceWithMapper(FORCE, false);
logInAsUserInIDPForFirstTime();
assertThatRoleHasBeenAssignedInConsumerRealmTo(user);
}
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
assertThatRoleHasBeenAssigned(user);
@Test
public void updateBrokeredUserAssignsRoleInForceModeWhenCreatingTheMapperAfterFirstLogin() {
newValueForAttribute2 = "value 2";
UserRepresentation user = createMapperAndLoginAsUserTwiceWithMapper(FORCE, true);
logoutFromRealm(bc.consumerRealmName());
assertThatRoleHasBeenAssignedInConsumerRealmTo(user);
}
// update
user = findUser(bc.providerRealmName(), bc.getUserLogin(), bc.getUserEmail());
public UserRepresentation createMapperAndLoginAsUserTwiceWithMapper(IdentityProviderMapperSyncMode syncMode, boolean createAfterFirstLogin) {
return loginAsUserTwiceWithMapper(syncMode, createAfterFirstLogin, ImmutableMap.<String, List<String>>builder()
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("value 1").build())
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2, ImmutableList.<String>builder().add("value 2").build())
.build());
}
@Override
protected void updateUser() {
UserRepresentation user = findUser(bc.providerRealmName(), bc.getUserLogin(), bc.getUserEmail());
ImmutableMap<String, List<String>> matchingAttributes = ImmutableMap.<String, List<String>>builder()
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("value 1").build())
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2, ImmutableList.<String>builder().add("value 2").build())
.put("some.other.attribute", ImmutableList.<String>builder().add("some value").build())
.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(newValueForAttribute2).build())
.put("some.other.attribute", ImmutableList.<String>builder().add("some value").build())
.build();
user.setAttributes(matchingAttributes);
adminClient.realm(bc.providerRealmName()).users().get(user.getId()).update(user);
}
logInAsUserInIDP();
user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
assertThatRoleHasBeenAssigned(user);
@Override
protected void createMapperInIdp(IdentityProviderRepresentation idp, IdentityProviderMapperSyncMode syncMode) {
createAdvancedClaimToRoleMapperInIdp(idp, CLAIMS, false, syncMode);
}
private void createAdvancedClaimToRoleMapper(String claimsRepresentation, boolean areClaimValuesRegex) {
log.debug("adding identity provider to realm " + bc.consumerRealmName());
RealmResource realm = adminClient.realm(bc.consumerRealmName());
final IdentityProviderRepresentation idp = bc.setUpIdentityProvider(suiteContext);
Response resp = realm.identityProviders().create(idp);
resp.close();
IdentityProviderRepresentation idp = setupIdentityProvider();
createAdvancedClaimToRoleMapperInIdp(idp, claimsRepresentation, areClaimValuesRegex, IMPORT);
}
protected void createAdvancedClaimToRoleMapperInIdp(IdentityProviderRepresentation idp , String claimsRepresentation, boolean areClaimValuesRegex, IdentityProviderMapperSyncMode syncMode) {
IdentityProviderMapperRepresentation advancedClaimToRoleMapper = new IdentityProviderMapperRepresentation();
advancedClaimToRoleMapper.setName("advanced-claim-to-role-mapper");
advancedClaimToRoleMapper.setIdentityProviderMapper(AdvancedClaimToRoleMapper.PROVIDER_ID);
advancedClaimToRoleMapper.setConfig(ImmutableMap.<String, String>builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(AdvancedClaimToRoleMapper.CLAIM_PROPERTY_NAME, claimsRepresentation)
.put(AdvancedClaimToRoleMapper.ARE_CLAIM_VALUES_REGEX_PROPERTY_NAME, areClaimValuesRegex ? "true" : "false")
.put(ConfigConstants.ROLE, CLIENT_ROLE_MAPPER_REPRESENTATION)
@ -248,52 +215,6 @@ public class OidcAdvancedClaimToRoleMapperTest extends AbstractBaseBrokerTest {
IdentityProviderResource idpResource = realm.identityProviders().get(idp.getAlias());
advancedClaimToRoleMapper.setIdentityProviderAlias(bc.getIDPAlias());
resp = idpResource.addMapper(advancedClaimToRoleMapper);
resp.close();
}
private void createUserInProviderRealm(Map<String, List<String>> attributes) {
log.debug("Creating user in realm " + bc.providerRealmName());
UserRepresentation user = UserBuilder.create()
.username(bc.getUserLogin())
.email(bc.getUserEmail())
.build();
user.setEmailVerified(true);
user.setAttributes(attributes);
this.userId = createUserAndResetPasswordWithAdminClient(adminClient.realm(bc.providerRealmName()), user, bc.getUserPassword());
}
private UserRepresentation findUser(String realm, String userName, String email) {
UsersResource consumerUsers = adminClient.realm(realm).users();
List<UserRepresentation> users = consumerUsers.list();
assertThat("There must be exactly one user", users, hasSize(1));
UserRepresentation user = users.get(0);
assertThat("Username has to match", user.getUsername(), equalTo(userName));
assertThat("Email has to match", user.getEmail(), equalTo(email));
MappingsRepresentation roles = consumerUsers.get(user.getId()).roles().getAll();
List<String> realmRoles = roles.getRealmMappings().stream()
.map(RoleRepresentation::getName)
.collect(Collectors.toList());
user.setRealmRoles(realmRoles);
Map<String, List<String>> clientRoles = new HashMap<>();
roles.getClientMappings().forEach((key, value) -> clientRoles.put(key, value.getMappings().stream()
.map(RoleRepresentation::getName)
.collect(Collectors.toList())));
user.setClientRoles(clientRoles);
return user;
}
private void assertThatRoleHasBeenAssigned(UserRepresentation user) {
assertThat(user.getClientRoles().get(CLIENT), contains(CLIENT_ROLE));
}
private void assertThatRoleHasNotBeenAssigned(UserRepresentation user) {
assertThat(user.getClientRoles().get(CLIENT), not(contains(CLIENT_ROLE)));
idpResource.addMapper(advancedClaimToRoleMapper).close();
}
}

View file

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

View file

@ -1,6 +1,8 @@
package org.keycloak.testsuite.broker;
import org.keycloak.broker.oidc.mappers.UserAttributeMapper;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
import com.google.common.collect.ImmutableMap;
@ -14,11 +16,12 @@ public class OidcUserAttributeMapperTest extends AbstractUserAttributeMapperTest
}
@Override
protected Iterable<IdentityProviderMapperRepresentation> createIdentityProviderMappers() {
protected Iterable<IdentityProviderMapperRepresentation> createIdentityProviderMappers(IdentityProviderMapperSyncMode syncMode) {
IdentityProviderMapperRepresentation attrMapper1 = new IdentityProviderMapperRepresentation();
attrMapper1.setName("attribute-mapper");
attrMapper1.setIdentityProviderMapper(UserAttributeMapper.PROVIDER_ID);
attrMapper1.setConfig(ImmutableMap.<String,String>builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(UserAttributeMapper.CLAIM, KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME)
.put(UserAttributeMapper.USER_ATTRIBUTE, MAPPED_ATTRIBUTE_NAME)
.build());
@ -27,6 +30,7 @@ public class OidcUserAttributeMapperTest extends AbstractUserAttributeMapperTest
emailAttrMapper.setName("attribute-mapper-email");
emailAttrMapper.setIdentityProviderMapper(UserAttributeMapper.PROVIDER_ID);
emailAttrMapper.setConfig(ImmutableMap.<String,String>builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(UserAttributeMapper.CLAIM, "email")
.put(UserAttributeMapper.USER_ATTRIBUTE, "email")
.build());
@ -35,6 +39,7 @@ public class OidcUserAttributeMapperTest extends AbstractUserAttributeMapperTest
nestedEmailAttrMapper.setName("nested-attribute-mapper-email");
nestedEmailAttrMapper.setIdentityProviderMapper(UserAttributeMapper.PROVIDER_ID);
nestedEmailAttrMapper.setConfig(ImmutableMap.<String,String>builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(UserAttributeMapper.CLAIM, "nested.email")
.put(UserAttributeMapper.USER_ATTRIBUTE, "nested.email")
.build());
@ -43,6 +48,7 @@ public class OidcUserAttributeMapperTest extends AbstractUserAttributeMapperTest
dottedEmailAttrMapper.setName("dotted-attribute-mapper-email");
dottedEmailAttrMapper.setIdentityProviderMapper(UserAttributeMapper.PROVIDER_ID);
dottedEmailAttrMapper.setConfig(ImmutableMap.<String,String>builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(UserAttributeMapper.CLAIM, "dotted\\.email")
.put(UserAttributeMapper.USER_ATTRIBUTE, "dotted.email")
.build());

View file

@ -1,6 +1,8 @@
package org.keycloak.testsuite.broker;
import org.keycloak.broker.saml.mappers.UserAttributeMapper;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
import com.google.common.collect.ImmutableMap;
@ -14,11 +16,12 @@ public class SamlUserAttributeMapperTest extends AbstractUserAttributeMapperTest
}
@Override
protected Iterable<IdentityProviderMapperRepresentation> createIdentityProviderMappers() {
protected Iterable<IdentityProviderMapperRepresentation> createIdentityProviderMappers(IdentityProviderMapperSyncMode syncMode) {
IdentityProviderMapperRepresentation attrMapperEmail = new IdentityProviderMapperRepresentation();
attrMapperEmail.setName("attribute-mapper-email");
attrMapperEmail.setIdentityProviderMapper(UserAttributeMapper.PROVIDER_ID);
attrMapperEmail.setConfig(ImmutableMap.<String,String>builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(UserAttributeMapper.ATTRIBUTE_FRIENDLY_NAME, "email")
.put(UserAttributeMapper.USER_ATTRIBUTE, "email")
.build());
@ -27,6 +30,7 @@ public class SamlUserAttributeMapperTest extends AbstractUserAttributeMapperTest
attrMapperNestedEmail.setName("nested-attribute-mapper-email");
attrMapperNestedEmail.setIdentityProviderMapper(UserAttributeMapper.PROVIDER_ID);
attrMapperNestedEmail.setConfig(ImmutableMap.<String,String>builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(UserAttributeMapper.ATTRIBUTE_NAME, "nested.email")
.put(UserAttributeMapper.USER_ATTRIBUTE, "nested.email")
.build());
@ -35,6 +39,7 @@ public class SamlUserAttributeMapperTest extends AbstractUserAttributeMapperTest
attrMapperDottedEmail.setName("dotted-attribute-mapper-email");
attrMapperDottedEmail.setIdentityProviderMapper(UserAttributeMapper.PROVIDER_ID);
attrMapperDottedEmail.setConfig(ImmutableMap.<String,String>builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(UserAttributeMapper.ATTRIBUTE_NAME, "dotted.email")
.put(UserAttributeMapper.USER_ATTRIBUTE, "dotted.email")
.build());
@ -43,6 +48,7 @@ public class SamlUserAttributeMapperTest extends AbstractUserAttributeMapperTest
attrMapper1.setName("attribute-mapper");
attrMapper1.setIdentityProviderMapper(UserAttributeMapper.PROVIDER_ID);
attrMapper1.setConfig(ImmutableMap.<String,String>builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(UserAttributeMapper.ATTRIBUTE_NAME, KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME)
.put(UserAttributeMapper.USER_ATTRIBUTE, MAPPED_ATTRIBUTE_NAME)
.build());
@ -51,6 +57,7 @@ public class SamlUserAttributeMapperTest extends AbstractUserAttributeMapperTest
attrMapper2.setName("attribute-mapper-friendly");
attrMapper2.setIdentityProviderMapper(UserAttributeMapper.PROVIDER_ID);
attrMapper2.setConfig(ImmutableMap.<String,String>builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put(UserAttributeMapper.ATTRIBUTE_FRIENDLY_NAME, ATTRIBUTE_TO_MAP_FRIENDLY_NAME)
.put(UserAttributeMapper.USER_ATTRIBUTE, MAPPED_ATTRIBUTE_FRIENDLY_NAME)
.build());

View file

@ -13,6 +13,7 @@ import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.common.Profile;
import org.keycloak.models.ClientModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
@ -370,7 +371,10 @@ public class SocialLoginTest extends AbstractKeycloakTest {
}
public IdentityProviderRepresentation buildIdp(Provider provider) {
IdentityProviderRepresentation idp = IdentityProviderBuilder.create().alias(provider.id()).providerId(provider.id()).build();
IdentityProviderRepresentation idp = IdentityProviderBuilder.create()
.alias(provider.id())
.providerId(provider.id())
.build();
idp.setEnabled(true);
idp.setStoreToken(true);
idp.getConfig().put("clientId", getConfig(provider, "clientId"));

View file

@ -2,17 +2,29 @@ package org.keycloak.testsuite.cli.admin;
import org.junit.Assert;
import org.junit.Test;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.broker.saml.SAMLIdentityProviderConfig;
import org.keycloak.broker.saml.SAMLIdentityProviderFactory;
import org.keycloak.client.admin.cli.config.FileConfigHandler;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.testsuite.cli.KcAdmExec;
import org.keycloak.testsuite.updaters.IdentityProviderCreator;
import org.keycloak.testsuite.util.IdentityProviderBuilder;
import org.keycloak.testsuite.util.TempFileResource;
import org.keycloak.util.JsonSerialization;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.keycloak.testsuite.cli.KcAdmExec.execute;
/**
@ -40,6 +52,23 @@ public class KcAdmCreateTest extends AbstractAdmCliTest {
}
}
@Test
public void testCreateIDPWithoutSyncMode() throws IOException {
final String realm = "test";
final RealmResource realmResource = adminClient.realm(realm);
FileConfigHandler handler = initCustomConfigFile();
try (TempFileResource configFile = new TempFileResource(handler.getConfigFile())) {
loginAsUser(configFile.getFile(), serverUrl, realm, "user1", "userpass");
final File idpJson = new File("target/test-classes/cli/idp-keycloak-without-sync-mode.json");
KcAdmExec exe = execute("create identity-provider/instances/ -r " + realm + " -f " + idpJson.getAbsolutePath() + " --config " + configFile.getFile());
assertExitCodeAndStdErrSize(exe, 0, 1);
}
// If the sync mode is not present on creating the idp, it will never be added automatically. However, the model will always assume "LEGACY", so no errors should occur.
Assert.assertNull(realmResource.identityProviders().get("idpAlias").toRepresentation().getConfig().get(IdentityProviderModel.SYNC_MODE));
}
@Test
public void testCreateThoroughly() throws IOException {

View file

@ -11,6 +11,7 @@
"linkOnly" : false,
"firstBrokerLoginFlowAlias" : "first broker login",
"config" : {
"syncMode": "IMPORT",
"nameIDPolicyFormat" : "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
"postBindingResponse" : "false",
"singleLogoutServiceUrl" : "https://saml.idp/saml",

View file

@ -0,0 +1,21 @@
{
"alias" : "idpAlias",
"displayName" : "SAML_UPDATED",
"providerId" : "saml",
"enabled" : true,
"updateProfileFirstLoginMode" : "on",
"trustEmail" : false,
"storeToken" : false,
"addReadTokenRoleOnCreate" : false,
"authenticateByDefault" : false,
"linkOnly" : false,
"firstBrokerLoginFlowAlias" : "first broker login",
"config" : {
"nameIDPolicyFormat" : "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
"postBindingResponse" : "false",
"singleLogoutServiceUrl" : "https://saml.idp/saml",
"postBindingAuthnRequest" : "false",
"singleSignOnServiceUrl" : "https://saml.idp/saml",
"backchannelSupported" : "false"
}
}

View file

@ -1166,6 +1166,7 @@
"alias" : "google1",
"enabled": true,
"config": {
"syncMode": "IMPORT",
"clientId": "googleId",
"clientSecret": "googleSecret"
}

View file

@ -24,6 +24,7 @@
"alias" : "google1",
"enabled": true,
"config": {
"syncMode": "IMPORT",
"clientId": "googleId",
"clientSecret": "googleSecret"
}
@ -33,6 +34,7 @@
"alias" : "facebook1",
"enabled": true,
"config": {
"syncMode": "IMPORT",
"clientId": "facebookId",
"clientSecret": "facebookSecret"
}
@ -42,6 +44,7 @@
"alias" : "twitter1",
"enabled": true,
"config": {
"syncMode": "IMPORT",
"clientId": "twitterId",
"clientSecret": "twitterSecret"
}

View file

@ -64,6 +64,7 @@
"authenticateByDefault" : false,
"firstBrokerLoginFlowAlias" : "first broker login",
"config" : {
"syncMode": "IMPORT",
"nameIDPolicyFormat" : "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
"postBindingAuthnRequest" : "true",
"postBindingResponse" : "true",
@ -78,6 +79,7 @@
"identityProviderAlias" : "saml-leaf",
"identityProviderMapper" : "saml-role-idp-mapper",
"config" : {
"syncMode": "INHERIT",
"attribute.value" : "manager",
"role" : "${url.realm.consumer}/app/auth.manager",
"attribute.name" : "Role"

View file

@ -0,0 +1,48 @@
/*
* Copyright 2019 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.console.page.idp;
import org.jboss.arquillian.graphene.page.Page;
import org.keycloak.testsuite.console.page.AdminConsoleCreate;
/**
* @author Martin Idel <external.Martin.Idel@bosch.io>
*/
public class CreateIdentityProviderMapper extends AdminConsoleCreate {
public String idp;
@Page
private IdentityProviderMapperForm form;
public CreateIdentityProviderMapper() {
setEntity("identity-provider-mappers");
}
public void setIdp(String idp) {
this.idp = idp;
}
@Override
public String getUriFragment() {
return super.getUriFragment() + "/" + idp + "";
}
public IdentityProviderMapperForm form() {
return form;
}
}

View file

@ -21,6 +21,7 @@ import org.keycloak.testsuite.console.page.fragment.KcPassword;
import org.keycloak.testsuite.page.Form;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.ui.Select;
import static org.keycloak.testsuite.util.UIUtils.setTextInputValue;
@ -34,6 +35,15 @@ public class IdentityProviderForm extends Form {
@FindBy(id = "clientSecret")
private KcPassword clientSecretInput;
@FindBy(id = "syncMode")
private Select syncMode;
@FindBy(linkText = "Mappers")
private WebElement mappersTab;
@FindBy(linkText = "Create")
private WebElement mapperCreateButton;
public void setClientId(final String value) {
setTextInputValue(clientIdInput, value);
}
@ -42,7 +52,20 @@ public class IdentityProviderForm extends Form {
clientSecretInput.setValue(value);
}
public void setSyncMode(final String value) {
syncMode.selectByVisibleText(value);
}
public String syncMode() {
return syncMode.getFirstSelectedOption().getText();
}
public KcPassword clientSecret() {
return clientSecretInput;
}
public void createMapper() {
mappersTab.click();
mapperCreateButton.click();
}
}

View file

@ -0,0 +1,48 @@
/*
* Copyright 2019 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.console.page.idp;
import org.keycloak.testsuite.page.Form;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.ui.Select;
import static org.keycloak.testsuite.util.UIUtils.setTextInputValue;
/**
* @author Martin Idel <external.Martin.Idel@bosch.io>
*/
public class IdentityProviderMapperForm extends Form {
@FindBy(id = "name")
private WebElement name;
@FindBy(id = "syncMode")
private Select syncMode;
public void setName(final String value) {
setTextInputValue(name, value);
}
public void setSyncMode(final String value) {
syncMode.selectByVisibleText(value);
}
public String syncMode() {
return syncMode.getFirstSelectedOption().getText();
}
}

View file

@ -22,18 +22,22 @@ import org.junit.Before;
import org.junit.Test;
import org.keycloak.testsuite.console.AbstractConsoleTest;
import org.keycloak.testsuite.console.page.idp.CreateIdentityProvider;
import org.keycloak.testsuite.console.page.idp.CreateIdentityProviderMapper;
import org.keycloak.testsuite.console.page.idp.IdentityProvider;
import org.keycloak.testsuite.console.page.idp.IdentityProviders;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.keycloak.testsuite.util.UIUtils.refreshPageAndWaitForLoad;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlEquals;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
/**
*
* @author Petr Mensik
* @author Vaclav Muzikar <vmuzikar@redhat.com>
* @author Martin Idel <external.Martin.Idel@bosch.io>
*/
public class IdentityProviderTest extends AbstractConsoleTest {
@Page
@ -45,6 +49,9 @@ public class IdentityProviderTest extends AbstractConsoleTest {
@Page
private CreateIdentityProvider createIdentityProviderPage;
@Page
private CreateIdentityProviderMapper createIdentityProviderMapperPage;
@Before
public void beforeIdentityProviderTest() {
identityProvidersPage.navigateTo();
@ -91,6 +98,44 @@ public class IdentityProviderTest extends AbstractConsoleTest {
assertPasswordIsMasked();
}
@Test
public void settingAndSavingSyncMode() {
createIdentityProviderPage.setProviderId("google");
identityProviderPage.setIds("google", "google");
identityProvidersPage.addProvider("google");
assertCurrentUrlEquals(createIdentityProviderPage);
identityProviderPage.form().setSyncMode("force");
createIdentityProviderPage.form().setClientId("test-google");
createIdentityProviderPage.form().setClientSecret("secret");
createIdentityProviderPage.form().save();
assertAlertSuccess();
refreshPageAndWaitForLoad();
assertCurrentUrlEquals(identityProviderPage);
assertSyncModeIsSetToForce();
identityProviderPage.form().createMapper();
createIdentityProviderMapperPage.setIdp("google");
assertCurrentUrlEquals(createIdentityProviderMapperPage);
createIdentityProviderMapperPage.form().setName("TestMapper");
createIdentityProviderMapperPage.form().setSyncMode("import");
createIdentityProviderMapperPage.form().save();
assertAlertSuccess();
refreshPageAndWaitForLoad();
assertMapperSyncModeIsSetToImport();
}
private void assertMapperSyncModeIsSetToImport() {
assertEquals("import", createIdentityProviderMapperPage.form().syncMode());
}
private void assertSyncModeIsSetToForce() {
assertEquals("force", identityProviderPage.form().syncMode());
}
private void assertEyeButtonIsDisabled() {
assertTrue("Eye button is not disabled", identityProviderPage.form().clientSecret().isEyeButtonDisabled());
}

View file

@ -501,6 +501,14 @@ last-refresh=Letzte Aktualisierung
#gui-order=GUI order
#first-broker-login-flow=First Login Flow
#post-broker-login-flow=Post Login Flow
sync-mode=Synchronisationsmodus
sync-mode.tooltip=Standardsyncmodus für alle Mapper. Mögliche Werte sind: 'Legacy' um das alte Verhalten beizubehalten, 'Importieren' um den Nutzer einmalig zu importieren, 'Erzwingen' um den Nutzer immer zu importieren.
sync-mode.inherit=Standard erben
sync-mode.legacy=Legacy
sync-mode.import=Importieren
sync-mode.force=Erzwingen
sync-mode-override=Überschriebene Synchronisation
sync-mode-override.tooltip=Überschreibt den normalen Synchronisationsmodus des IDP für diesen Mapper. Were sind 'Legacy' um das alte Verhalten beizubehalten, 'Importieren' um den Nutzer einmalig zu importieren, 'Erzwingen' um den Nutzer immer zu updaten.
#redirect-uri=Redirect URI
#redirect-uri.tooltip=The redirect uri to use when configuring the identity provider.
#alias=Alias

View file

@ -540,6 +540,14 @@ provider=Provider
gui-order=GUI order
first-broker-login-flow=First Login Flow
post-broker-login-flow=Post Login Flow
sync-mode=Sync Mode
sync-mode.tooltip=Default sync mode for all mappers. The sync mode determines when user data will be synced using the mappers. Possible values are: 'legacy' to keep the behaviour before this option was introduced, 'import' to only import the user once during first login of the user with this identity provider, 'force' to always update the user during every login with this identity provider".
sync-mode.inherit=inherit
sync-mode.legacy=legacy
sync-mode.import=import
sync-mode.force=force
sync-mode-override=Sync Mode Override
sync-mode-override.tooltip=Overrides the default sync mode of the IDP for this mapper. Values are: 'legacy' to keep the behaviour before this option was introduced, 'import' to only import the user once during first login of the user with this identity provider, 'force' to always update the user during every login with this identity provider" and 'inherit' to use the sync mode defined in the identity provider for this mapper.
redirect-uri=Redirect URI
redirect-uri.tooltip=The redirect uri to use when configuring the identity provider.
alias=Alias

View file

@ -900,6 +900,7 @@ module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload
$scope.identityProvider.authenticateByDefault = false;
$scope.identityProvider.firstBrokerLoginFlowAlias = 'first broker login';
$scope.identityProvider.config.useJwksUrl = 'true';
$scope.identityProvider.config.syncMode = 'IMPORT';
$scope.newIdentityProvider = true;
}
@ -2090,6 +2091,7 @@ module.controller('IdentityProviderMapperCreateCtrl', function($scope, realm, id
// make first type the default
$scope.mapperType = mapperTypes[Object.keys(mapperTypes)[0]];
$scope.mapper.config.syncMode = 'INHERIT';
$scope.$watch(function() {
return $location.path();

View file

@ -27,6 +27,22 @@
</div>
<kc-tooltip>{{:: 'mapper.name.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix">
<label class="col-md-2 control-label" for="syncMode">{{:: 'sync-mode-override' | translate}} <span class="required">*</span></label>
<div class="col-sm-6">
<div>
<select class="form-control" id="syncMode"
ng-model="mapper.config.syncMode"
required>
<option id="syncMode_inherit" name="syncMode" value="INHERIT">{{:: 'sync-mode.inherit' | translate}}</option>
<option id="syncMode_legacy" name="syncMode" value="LEGACY">{{:: 'sync-mode.legacy' | translate}}</option>
<option id="syncMode_import" name="syncMode" value="IMPORT">{{:: 'sync-mode.import' | translate}}</option>
<option id="syncMode_force" name="syncMode" value="FORCE">{{:: 'sync-mode.force' | translate}}</option>
</select>
</div>
</div>
<kc-tooltip>{{:: 'sync-mode-override.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group" data-ng-show="create">
<label class="col-md-2 control-label" for="mapperTypeCreate">{{:: 'mapper-type' | translate}}</label>
<div class="col-sm-6">

View file

@ -113,6 +113,21 @@
</div>
<kc-tooltip>{{:: 'post-broker-login-flow.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="syncMode">{{:: 'sync-mode' | translate}}</label>
<div class="col-md-6">
<div>
<select class="form-control" id="syncMode"
ng-model="identityProvider.config.syncMode"
required>
<option id="syncMode_import" name="syncMode" value="IMPORT">{{:: 'sync-mode.import' | translate}}</option>
<option id="syncMode_legacy" name="syncMode" value="LEGACY">{{:: 'sync-mode.legacy' | translate}}</option>
<option id="syncMode_force" name="syncMode" value="FORCE">{{:: 'sync-mode.force' | translate}}</option>
</select>
</div>
</div>
<kc-tooltip>{{:: 'sync-mode.tooltip' | translate}}</kc-tooltip>
</div>
</fieldset>
<div class="form-group">

View file

@ -113,6 +113,21 @@
</div>
<kc-tooltip>{{:: 'post-broker-login-flow.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="syncMode">{{:: 'sync-mode' | translate}}</label>
<div class="col-md-6">
<div>
<select class="form-control" id="syncMode"
ng-model="identityProvider.config.syncMode"
required>
<option id="syncMode_import" name="syncMode" value="IMPORT">{{:: 'sync-mode.import' | translate}}</option>
<option id="syncMode_legacy" name="syncMode" value="LEGACY">{{:: 'sync-mode.legacy' | translate}}</option>
<option id="syncMode_force" name="syncMode" value="FORCE">{{:: 'sync-mode.force' | translate}}</option>
</select>
</div>
</div>
<kc-tooltip>{{:: 'sync-mode.tooltip' | translate}}</kc-tooltip>
</div>
</fieldset>
<div class="form-group">

View file

@ -107,6 +107,21 @@
</div>
<kc-tooltip>{{:: 'post-broker-login-flow.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="syncMode">{{:: 'sync-mode' | translate}}</label>
<div class="col-md-6">
<div>
<select class="form-control" id="syncMode"
ng-model="identityProvider.config.syncMode"
required>
<option id="syncMode_import" name="syncMode" value="IMPORT">{{:: 'sync-mode.import' | translate}}</option>
<option id="syncMode_legacy" name="syncMode" value="LEGACY">{{:: 'sync-mode.legacy' | translate}}</option>
<option id="syncMode_force" name="syncMode" value="FORCE">{{:: 'sync-mode.force' | translate}}</option>
</select>
</div>
</div>
<kc-tooltip>{{:: 'sync-mode.tooltip' | translate}}</kc-tooltip>
</div>
</fieldset>
<fieldset>
<legend uncollapsed><span class="text">{{:: 'openid-connect-config' | translate}}</span> <kc-tooltip>{{:: 'openid-connect-config.tooltip' | translate}}</kc-tooltip></legend>

View file

@ -135,6 +135,21 @@
</div>
<kc-tooltip>{{:: 'post-broker-login-flow.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="syncMode">{{:: 'sync-mode' | translate}}</label>
<div class="col-md-6">
<div>
<select class="form-control" id="syncMode"
ng-model="identityProvider.config.syncMode"
required>
<option id="syncMode_import" name="syncMode" value="IMPORT">{{:: 'sync-mode.import' | translate}}</option>
<option id="syncMode_legacy" name="syncMode" value="LEGACY">{{:: 'sync-mode.legacy' | translate}}</option>
<option id="syncMode_force" name="syncMode" value="FORCE">{{:: 'sync-mode.force' | translate}}</option>
</select>
</div>
</div>
<kc-tooltip>{{:: 'sync-mode.tooltip' | translate}}</kc-tooltip>
</div>
</fieldset>
<div class="form-group">

View file

@ -135,6 +135,21 @@
</div>
<kc-tooltip>{{:: 'post-broker-login-flow.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="syncMode">{{:: 'sync-mode' | translate}}</label>
<div class="col-md-6">
<div>
<select class="form-control" id="syncMode"
ng-model="identityProvider.config.syncMode"
required>
<option id="syncMode_import" name="syncMode" value="IMPORT">{{:: 'sync-mode.import' | translate}}</option>
<option id="syncMode_legacy" name="syncMode" value="LEGACY">{{:: 'sync-mode.legacy' | translate}}</option>
<option id="syncMode_force" name="syncMode" value="FORCE">{{:: 'sync-mode.force' | translate}}</option>
</select>
</div>
</div>
<kc-tooltip>{{:: 'sync-mode.tooltip' | translate}}</kc-tooltip>
</div>
</fieldset>
<div class="form-group">

View file

@ -107,6 +107,21 @@
</div>
<kc-tooltip>{{:: 'post-broker-login-flow.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="syncMode">{{:: 'sync-mode' | translate}}</label>
<div class="col-md-6">
<div>
<select class="form-control" id="syncMode"
ng-model="identityProvider.config.syncMode"
required>
<option id="syncMode_import" name="syncMode" value="IMPORT">{{:: 'sync-mode.import' | translate}}</option>
<option id="syncMode_legacy" name="syncMode" value="LEGACY">{{:: 'sync-mode.legacy' | translate}}</option>
<option id="syncMode_force" name="syncMode" value="FORCE">{{:: 'sync-mode.force' | translate}}</option>
</select>
</div>
</div>
<kc-tooltip>{{:: 'sync-mode.tooltip' | translate}}</kc-tooltip>
</div>
</fieldset>
<fieldset>
<legend uncollapsed><span class="text">{{:: 'saml-config' | translate}}</span> <kc-tooltip>{{:: 'identity-provider.saml-config.tooltip' | translate}}</kc-tooltip></legend>

View file

@ -128,6 +128,21 @@
</div>
<kc-tooltip>{{:: 'post-broker-login-flow.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="syncMode">{{:: 'sync-mode' | translate}}</label>
<div class="col-md-6">
<div>
<select class="form-control" id="syncMode"
ng-model="identityProvider.config.syncMode"
required>
<option id="syncMode_import" name="syncMode" value="IMPORT">{{:: 'sync-mode.import' | translate}}</option>
<option id="syncMode_legacy" name="syncMode" value="LEGACY">{{:: 'sync-mode.legacy' | translate}}</option>
<option id="syncMode_force" name="syncMode" value="FORCE">{{:: 'sync-mode.force' | translate}}</option>
</select>
</div>
</div>
<kc-tooltip>{{:: 'sync-mode.tooltip' | translate}}</kc-tooltip>
</div>
</fieldset>
<div class="form-group">