KEYCLOAK-12757 New Identity Provider Mapper "Advanced Claim to Role Mapper" with
following features * Regex support for claim values. * Support for multiple claims.
This commit is contained in:
parent
210fd92d23
commit
dd9ad305ca
14 changed files with 661 additions and 22 deletions
|
@ -17,8 +17,14 @@
|
||||||
|
|
||||||
package org.keycloak.models;
|
package org.keycloak.models;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies a mapping from broker login to user data.
|
* Specifies a mapping from broker login to user data.
|
||||||
|
@ -28,6 +34,9 @@ import java.util.Map;
|
||||||
*/
|
*/
|
||||||
public class IdentityProviderMapperModel implements Serializable {
|
public class IdentityProviderMapperModel implements Serializable {
|
||||||
|
|
||||||
|
private static final TypeReference<List<StringPair>> MAP_TYPE_REPRESENTATION = new TypeReference<List<StringPair>>() {
|
||||||
|
};
|
||||||
|
|
||||||
protected String id;
|
protected String id;
|
||||||
protected String name;
|
protected String name;
|
||||||
protected String identityProviderAlias;
|
protected String identityProviderAlias;
|
||||||
|
@ -75,6 +84,17 @@ public class IdentityProviderMapperModel implements Serializable {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getConfigMap(String configKey) {
|
||||||
|
String configMap = config.get(configKey);
|
||||||
|
|
||||||
|
try {
|
||||||
|
List<StringPair> map = JsonSerialization.readValue(configMap, MAP_TYPE_REPRESENTATION);
|
||||||
|
return map.stream().collect(Collectors.toMap(StringPair::getKey, StringPair::getValue));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Could not deserialize json: " + configMap, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
|
@ -91,4 +111,25 @@ public class IdentityProviderMapperModel implements Serializable {
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return id.hashCode();
|
return id.hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class StringPair {
|
||||||
|
private String key;
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
public String getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKey(String key) {
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,6 +57,11 @@ public class ProviderConfigProperty {
|
||||||
*/
|
*/
|
||||||
public static final String TEXT_TYPE="Text";
|
public static final String TEXT_TYPE="Text";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure multiple (key, value) pairs
|
||||||
|
*/
|
||||||
|
public static final String MAP_TYPE ="Map";
|
||||||
|
|
||||||
protected String name;
|
protected String name;
|
||||||
protected String label;
|
protected String label;
|
||||||
protected String helpText;
|
protected String helpText;
|
||||||
|
|
|
@ -118,7 +118,7 @@ public abstract class AbstractClaimMapper extends AbstractIdentityProviderMapper
|
||||||
} else if (value instanceof List) {
|
} else if (value instanceof List) {
|
||||||
List list = (List)value;
|
List list = (List)value;
|
||||||
for (Object val : list) {
|
for (Object val : list) {
|
||||||
if (valueEquals(desiredValue, val)) return true;
|
if (valueEquals(desiredValue, val)) return true;
|
||||||
}
|
}
|
||||||
} else if (value instanceof JsonNode) {
|
} else if (value instanceof JsonNode) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -0,0 +1,159 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.broker.oidc.mappers;
|
||||||
|
|
||||||
|
import org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory;
|
||||||
|
import org.keycloak.broker.oidc.OIDCIdentityProviderFactory;
|
||||||
|
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||||
|
import org.keycloak.broker.provider.ConfigConstants;
|
||||||
|
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||||
|
import org.keycloak.models.IdentityProviderMapperModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke, Benjamin Weimer</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class AdvancedClaimToRoleMapper extends AbstractClaimMapper {
|
||||||
|
|
||||||
|
public static final String CLAIM_PROPERTY_NAME = "claims";
|
||||||
|
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 List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
ProviderConfigProperty claimsProperty = new ProviderConfigProperty();
|
||||||
|
claimsProperty.setName(CLAIM_PROPERTY_NAME);
|
||||||
|
claimsProperty.setLabel("Claims");
|
||||||
|
claimsProperty.setHelpText("Name and value of the claims to search for in token. You can reference nested claims using a '.', i.e. 'address.locality'. To use dot (.) literally, escape it with backslash (\\.)");
|
||||||
|
claimsProperty.setType(ProviderConfigProperty.MAP_TYPE);
|
||||||
|
configProperties.add(claimsProperty);
|
||||||
|
ProviderConfigProperty isClaimValueRegexProperty = new ProviderConfigProperty();
|
||||||
|
isClaimValueRegexProperty.setName(ARE_CLAIM_VALUES_REGEX_PROPERTY_NAME);
|
||||||
|
isClaimValueRegexProperty.setLabel("Regex Claim Values");
|
||||||
|
isClaimValueRegexProperty.setHelpText("If enabled claim values are interpreted as regular expressions.");
|
||||||
|
isClaimValueRegexProperty.setType(ProviderConfigProperty.BOOLEAN_TYPE);
|
||||||
|
configProperties.add(isClaimValueRegexProperty);
|
||||||
|
ProviderConfigProperty roleProperty = new ProviderConfigProperty();
|
||||||
|
roleProperty.setName(ConfigConstants.ROLE);
|
||||||
|
roleProperty.setLabel("Role");
|
||||||
|
roleProperty.setHelpText("Role to grant to user if claim is present. Click 'Select Role' button to browse roles, or just type it in the textbox. To reference an application role the syntax is appname.approle, i.e. myapp.myrole");
|
||||||
|
roleProperty.setType(ProviderConfigProperty.ROLE_TYPE);
|
||||||
|
configProperties.add(roleProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String PROVIDER_ID = "oidc-advanced-role-idp-mapper";
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ProviderConfigProperty> getConfigProperties() {
|
||||||
|
return configProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return PROVIDER_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getCompatibleProviders() {
|
||||||
|
return COMPATIBLE_PROVIDERS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayCategory() {
|
||||||
|
return "Role Importer";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayType() {
|
||||||
|
return "Advanced Claim to Role";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void importNewUser(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);
|
||||||
|
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
|
||||||
|
user.grantRole(role);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateBrokeredUser(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);
|
||||||
|
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
|
||||||
|
user.deleteRoleMapping(role);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHelpText() {
|
||||||
|
return "If all claims exists, grant the user the specified realm or application role.";
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean hasAllClaimValues(IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
|
Map<String, String> claims = mapperModel.getConfigMap(CLAIM_PROPERTY_NAME);
|
||||||
|
Boolean areClaimValuesRegex = Boolean.valueOf(mapperModel.getConfig().get(ARE_CLAIM_VALUES_REGEX_PROPERTY_NAME));
|
||||||
|
|
||||||
|
for (Map.Entry<String, String> claim : claims.entrySet()) {
|
||||||
|
Object value = getClaimValue(context, claim.getKey());
|
||||||
|
|
||||||
|
boolean claimValuesMismatch = !(areClaimValuesRegex ? valueMatchesRegex(claim.getValue(), value) : valueEquals(claim.getValue(), value));
|
||||||
|
if (claimValuesMismatch) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean valueMatchesRegex(String regex, Object value) {
|
||||||
|
if (value instanceof List) {
|
||||||
|
List list = (List) value;
|
||||||
|
for (Object val : list) {
|
||||||
|
if (valueMatchesRegex(regex, val)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (value != null) {
|
||||||
|
String stringValue = value.toString();
|
||||||
|
if (stringValue != null && stringValue.matches(regex)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ org.keycloak.broker.provider.HardcodedRoleMapper
|
||||||
org.keycloak.broker.provider.HardcodedAttributeMapper
|
org.keycloak.broker.provider.HardcodedAttributeMapper
|
||||||
org.keycloak.broker.provider.HardcodedUserSessionAttributeMapper
|
org.keycloak.broker.provider.HardcodedUserSessionAttributeMapper
|
||||||
org.keycloak.broker.oidc.mappers.ClaimToRoleMapper
|
org.keycloak.broker.oidc.mappers.ClaimToRoleMapper
|
||||||
|
org.keycloak.broker.oidc.mappers.AdvancedClaimToRoleMapper
|
||||||
org.keycloak.broker.oidc.mappers.ExternalKeycloakRoleToRoleMapper
|
org.keycloak.broker.oidc.mappers.ExternalKeycloakRoleToRoleMapper
|
||||||
org.keycloak.broker.oidc.mappers.UserAttributeMapper
|
org.keycloak.broker.oidc.mappers.UserAttributeMapper
|
||||||
org.keycloak.broker.oidc.mappers.UsernameTemplateMapper
|
org.keycloak.broker.oidc.mappers.UsernameTemplateMapper
|
||||||
|
|
|
@ -361,12 +361,12 @@ public class IdentityProviderTest extends AbstractAdminTest {
|
||||||
create(createRep("keycloak-oidc", "keycloak-oidc"));
|
create(createRep("keycloak-oidc", "keycloak-oidc"));
|
||||||
provider = realm.identityProviders().get("keycloak-oidc");
|
provider = realm.identityProviders().get("keycloak-oidc");
|
||||||
mapperTypes = provider.getMapperTypes();
|
mapperTypes = provider.getMapperTypes();
|
||||||
assertMapperTypes(mapperTypes, "keycloak-oidc-role-to-role-idp-mapper", "oidc-user-attribute-idp-mapper", "oidc-role-idp-mapper", "oidc-username-idp-mapper");
|
assertMapperTypes(mapperTypes, "keycloak-oidc-role-to-role-idp-mapper", "oidc-user-attribute-idp-mapper", "oidc-role-idp-mapper", "oidc-username-idp-mapper", "oidc-advanced-role-idp-mapper");
|
||||||
|
|
||||||
create(createRep("oidc", "oidc"));
|
create(createRep("oidc", "oidc"));
|
||||||
provider = realm.identityProviders().get("oidc");
|
provider = realm.identityProviders().get("oidc");
|
||||||
mapperTypes = provider.getMapperTypes();
|
mapperTypes = provider.getMapperTypes();
|
||||||
assertMapperTypes(mapperTypes, "oidc-user-attribute-idp-mapper", "oidc-role-idp-mapper", "oidc-username-idp-mapper");
|
assertMapperTypes(mapperTypes, "oidc-user-attribute-idp-mapper", "oidc-role-idp-mapper", "oidc-username-idp-mapper", "oidc-advanced-role-idp-mapper");
|
||||||
|
|
||||||
create(createRep("saml", "saml"));
|
create(createRep("saml", "saml"));
|
||||||
provider = realm.identityProviders().get("saml");
|
provider = realm.identityProviders().get("saml");
|
||||||
|
|
|
@ -32,7 +32,6 @@ public abstract class AbstractUserAttributeMapperTest extends AbstractBaseBroker
|
||||||
|
|
||||||
protected static final String MAPPED_ATTRIBUTE_NAME = "mapped-user-attribute";
|
protected static final String MAPPED_ATTRIBUTE_NAME = "mapped-user-attribute";
|
||||||
protected static final String MAPPED_ATTRIBUTE_FRIENDLY_NAME = "mapped-user-attribute-friendly";
|
protected static final String MAPPED_ATTRIBUTE_FRIENDLY_NAME = "mapped-user-attribute-friendly";
|
||||||
protected static final String ATTRIBUTE_TO_MAP_NAME = "user-attribute";
|
|
||||||
protected static final String ATTRIBUTE_TO_MAP_FRIENDLY_NAME = "user-attribute-friendly";
|
protected static final String ATTRIBUTE_TO_MAP_FRIENDLY_NAME = "user-attribute-friendly";
|
||||||
|
|
||||||
private static final Set<String> PROTECTED_NAMES = ImmutableSet.<String>builder().add("email").add("lastName").add("firstName").build();
|
private static final Set<String> PROTECTED_NAMES = ImmutableSet.<String>builder().add("email").add("lastName").add("firstName").build();
|
||||||
|
@ -40,7 +39,7 @@ public abstract class AbstractUserAttributeMapperTest extends AbstractBaseBroker
|
||||||
.put("dotted.email", "dotted.email")
|
.put("dotted.email", "dotted.email")
|
||||||
.put("nested.email", "nested.email")
|
.put("nested.email", "nested.email")
|
||||||
.put(ATTRIBUTE_TO_MAP_FRIENDLY_NAME, MAPPED_ATTRIBUTE_FRIENDLY_NAME)
|
.put(ATTRIBUTE_TO_MAP_FRIENDLY_NAME, MAPPED_ATTRIBUTE_FRIENDLY_NAME)
|
||||||
.put(ATTRIBUTE_TO_MAP_NAME, MAPPED_ATTRIBUTE_NAME)
|
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, MAPPED_ATTRIBUTE_NAME)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
protected abstract Iterable<IdentityProviderMapperRepresentation> createIdentityProviderMappers();
|
protected abstract Iterable<IdentityProviderMapperRepresentation> createIdentityProviderMappers();
|
||||||
|
@ -188,10 +187,10 @@ public abstract class AbstractUserAttributeMapperTest extends AbstractBaseBroker
|
||||||
@Test
|
@Test
|
||||||
public void testBasicMappingSingleValue() {
|
public void testBasicMappingSingleValue() {
|
||||||
testValueMapping(ImmutableMap.<String, List<String>>builder()
|
testValueMapping(ImmutableMap.<String, List<String>>builder()
|
||||||
.put(ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("value 1").build())
|
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("value 1").build())
|
||||||
.build(),
|
.build(),
|
||||||
ImmutableMap.<String, List<String>>builder()
|
ImmutableMap.<String, List<String>>builder()
|
||||||
.put(ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("second value").build())
|
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("second value").build())
|
||||||
.build()
|
.build()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -214,10 +213,10 @@ public abstract class AbstractUserAttributeMapperTest extends AbstractBaseBroker
|
||||||
@Test
|
@Test
|
||||||
public void testBasicMappingClearValue() {
|
public void testBasicMappingClearValue() {
|
||||||
testValueMapping(ImmutableMap.<String, List<String>>builder()
|
testValueMapping(ImmutableMap.<String, List<String>>builder()
|
||||||
.put(ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("value 1").build())
|
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("value 1").build())
|
||||||
.build(),
|
.build(),
|
||||||
ImmutableMap.<String, List<String>>builder()
|
ImmutableMap.<String, List<String>>builder()
|
||||||
.put(ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().build())
|
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().build())
|
||||||
.build()
|
.build()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -225,7 +224,7 @@ public abstract class AbstractUserAttributeMapperTest extends AbstractBaseBroker
|
||||||
@Test
|
@Test
|
||||||
public void testBasicMappingRemoveValue() {
|
public void testBasicMappingRemoveValue() {
|
||||||
testValueMapping(ImmutableMap.<String, List<String>>builder()
|
testValueMapping(ImmutableMap.<String, List<String>>builder()
|
||||||
.put(ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("value 1").build())
|
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("value 1").build())
|
||||||
.build(),
|
.build(),
|
||||||
ImmutableMap.<String, List<String>>builder()
|
ImmutableMap.<String, List<String>>builder()
|
||||||
.build()
|
.build()
|
||||||
|
@ -235,10 +234,10 @@ public abstract class AbstractUserAttributeMapperTest extends AbstractBaseBroker
|
||||||
@Test
|
@Test
|
||||||
public void testBasicMappingMultipleValues() {
|
public void testBasicMappingMultipleValues() {
|
||||||
testValueMapping(ImmutableMap.<String, List<String>>builder()
|
testValueMapping(ImmutableMap.<String, List<String>>builder()
|
||||||
.put(ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("value 1").add("value 2").build())
|
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("value 1").add("value 2").build())
|
||||||
.build(),
|
.build(),
|
||||||
ImmutableMap.<String, List<String>>builder()
|
ImmutableMap.<String, List<String>>builder()
|
||||||
.put(ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("second value").add("second value 2").build())
|
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("second value").add("second value 2").build())
|
||||||
.build()
|
.build()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -248,7 +247,7 @@ public abstract class AbstractUserAttributeMapperTest extends AbstractBaseBroker
|
||||||
testValueMapping(ImmutableMap.<String, List<String>>builder()
|
testValueMapping(ImmutableMap.<String, List<String>>builder()
|
||||||
.build(),
|
.build(),
|
||||||
ImmutableMap.<String, List<String>>builder()
|
ImmutableMap.<String, List<String>>builder()
|
||||||
.put(ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("second value").add("second value 2").build())
|
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("second value").add("second value 2").build())
|
||||||
.build()
|
.build()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -256,7 +255,7 @@ public abstract class AbstractUserAttributeMapperTest extends AbstractBaseBroker
|
||||||
@Test
|
@Test
|
||||||
public void testDeleteBasicMappingMultipleValues() {
|
public void testDeleteBasicMappingMultipleValues() {
|
||||||
testValueMapping(ImmutableMap.<String, List<String>>builder()
|
testValueMapping(ImmutableMap.<String, List<String>>builder()
|
||||||
.put(ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("second value").add("second value 2").build())
|
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("second value").add("second value 2").build())
|
||||||
.build(),
|
.build(),
|
||||||
ImmutableMap.<String, List<String>>builder()
|
ImmutableMap.<String, List<String>>builder()
|
||||||
.build()
|
.build()
|
||||||
|
|
|
@ -21,13 +21,15 @@ import static org.keycloak.testsuite.broker.BrokerTestConstants.*;
|
||||||
import static org.keycloak.testsuite.broker.BrokerTestTools.*;
|
import static org.keycloak.testsuite.broker.BrokerTestTools.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @author hmlnarik
|
* @author hmlnarik
|
||||||
*/
|
*/
|
||||||
public class KcOidcBrokerConfiguration implements BrokerConfiguration {
|
public class KcOidcBrokerConfiguration implements BrokerConfiguration {
|
||||||
|
|
||||||
public static final KcOidcBrokerConfiguration INSTANCE = new KcOidcBrokerConfiguration();
|
public static final KcOidcBrokerConfiguration INSTANCE = new KcOidcBrokerConfiguration();
|
||||||
|
|
||||||
|
protected static final String ATTRIBUTE_TO_MAP_NAME = "user-attribute";
|
||||||
|
protected static final String ATTRIBUTE_TO_MAP_NAME_2 = "user-attribute-2";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RealmRepresentation createProviderRealm() {
|
public RealmRepresentation createProviderRealm() {
|
||||||
RealmRepresentation realm = new RealmRepresentation();
|
RealmRepresentation realm = new RealmRepresentation();
|
||||||
|
@ -106,15 +108,29 @@ public class KcOidcBrokerConfiguration implements BrokerConfiguration {
|
||||||
userAttrMapper.setProtocolMapper(UserAttributeMapper.PROVIDER_ID);
|
userAttrMapper.setProtocolMapper(UserAttributeMapper.PROVIDER_ID);
|
||||||
|
|
||||||
Map<String, String> userAttrMapperConfig = userAttrMapper.getConfig();
|
Map<String, String> userAttrMapperConfig = userAttrMapper.getConfig();
|
||||||
userAttrMapperConfig.put(ProtocolMapperUtils.USER_ATTRIBUTE, AbstractUserAttributeMapperTest.ATTRIBUTE_TO_MAP_NAME);
|
userAttrMapperConfig.put(ProtocolMapperUtils.USER_ATTRIBUTE, ATTRIBUTE_TO_MAP_NAME);
|
||||||
userAttrMapperConfig.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, AbstractUserAttributeMapperTest.ATTRIBUTE_TO_MAP_NAME);
|
userAttrMapperConfig.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, ATTRIBUTE_TO_MAP_NAME);
|
||||||
userAttrMapperConfig.put(OIDCAttributeMapperHelper.JSON_TYPE, ProviderConfigProperty.STRING_TYPE);
|
userAttrMapperConfig.put(OIDCAttributeMapperHelper.JSON_TYPE, ProviderConfigProperty.STRING_TYPE);
|
||||||
userAttrMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true");
|
userAttrMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true");
|
||||||
userAttrMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true");
|
userAttrMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true");
|
||||||
userAttrMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_USERINFO, "true");
|
userAttrMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_USERINFO, "true");
|
||||||
userAttrMapperConfig.put(ProtocolMapperUtils.MULTIVALUED, "true");
|
userAttrMapperConfig.put(ProtocolMapperUtils.MULTIVALUED, "true");
|
||||||
|
|
||||||
client.setProtocolMappers(Arrays.asList(emailMapper, userAttrMapper, nestedAttrMapper, dottedAttrMapper));
|
ProtocolMapperRepresentation userAttrMapper2 = new ProtocolMapperRepresentation();
|
||||||
|
userAttrMapper2.setName("attribute - name - 2");
|
||||||
|
userAttrMapper2.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
|
||||||
|
userAttrMapper2.setProtocolMapper(UserAttributeMapper.PROVIDER_ID);
|
||||||
|
|
||||||
|
Map<String, String> userAttrMapperConfig2 = userAttrMapper2.getConfig();
|
||||||
|
userAttrMapperConfig2.put(ProtocolMapperUtils.USER_ATTRIBUTE, ATTRIBUTE_TO_MAP_NAME_2);
|
||||||
|
userAttrMapperConfig2.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, ATTRIBUTE_TO_MAP_NAME_2);
|
||||||
|
userAttrMapperConfig2.put(OIDCAttributeMapperHelper.JSON_TYPE, ProviderConfigProperty.STRING_TYPE);
|
||||||
|
userAttrMapperConfig2.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true");
|
||||||
|
userAttrMapperConfig2.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true");
|
||||||
|
userAttrMapperConfig2.put(OIDCAttributeMapperHelper.INCLUDE_IN_USERINFO, "true");
|
||||||
|
userAttrMapperConfig2.put(ProtocolMapperUtils.MULTIVALUED, "true");
|
||||||
|
|
||||||
|
client.setProtocolMappers(Arrays.asList(emailMapper, userAttrMapper, userAttrMapper2, nestedAttrMapper, dottedAttrMapper));
|
||||||
|
|
||||||
return Collections.singletonList(client);
|
return Collections.singletonList(client);
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,8 +124,8 @@ public class KcSamlBrokerConfiguration implements BrokerConfiguration {
|
||||||
userAttrMapper.setProtocolMapper(UserAttributeStatementMapper.PROVIDER_ID);
|
userAttrMapper.setProtocolMapper(UserAttributeStatementMapper.PROVIDER_ID);
|
||||||
|
|
||||||
Map<String, String> userAttrMapperConfig = userAttrMapper.getConfig();
|
Map<String, String> userAttrMapperConfig = userAttrMapper.getConfig();
|
||||||
userAttrMapperConfig.put(ProtocolMapperUtils.USER_ATTRIBUTE, AbstractUserAttributeMapperTest.ATTRIBUTE_TO_MAP_NAME);
|
userAttrMapperConfig.put(ProtocolMapperUtils.USER_ATTRIBUTE, KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME);
|
||||||
userAttrMapperConfig.put(AttributeStatementHelper.SAML_ATTRIBUTE_NAME, AbstractUserAttributeMapperTest.ATTRIBUTE_TO_MAP_NAME);
|
userAttrMapperConfig.put(AttributeStatementHelper.SAML_ATTRIBUTE_NAME, KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME);
|
||||||
userAttrMapperConfig.put(AttributeStatementHelper.SAML_ATTRIBUTE_NAMEFORMAT, AttributeStatementHelper.BASIC);
|
userAttrMapperConfig.put(AttributeStatementHelper.SAML_ATTRIBUTE_NAMEFORMAT, AttributeStatementHelper.BASIC);
|
||||||
userAttrMapperConfig.put(AttributeStatementHelper.FRIENDLY_NAME, "");
|
userAttrMapperConfig.put(AttributeStatementHelper.FRIENDLY_NAME, "");
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,319 @@
|
||||||
|
package org.keycloak.testsuite.broker;
|
||||||
|
|
||||||
|
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>
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
|
||||||
|
private static final String CLAIMS = "[\n" +
|
||||||
|
" {\n" +
|
||||||
|
" \"key\": \"" + KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME + "\",\n" +
|
||||||
|
" \"value\": \"value 1\"\n" +
|
||||||
|
" },\n" +
|
||||||
|
" {\n" +
|
||||||
|
" \"key\": \"" + KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2 + "\",\n" +
|
||||||
|
" \"value\": \"value 2\"\n" +
|
||||||
|
" }\n" +
|
||||||
|
"]";
|
||||||
|
|
||||||
|
private static final String CLAIMS_REGEX = "[\n" +
|
||||||
|
" {\n" +
|
||||||
|
" \"key\": \"" + KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME + "\",\n" +
|
||||||
|
" \"value\": \"va.*\"\n" +
|
||||||
|
" },\n" +
|
||||||
|
" {\n" +
|
||||||
|
" \"key\": \"" + KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2 + "\",\n" +
|
||||||
|
" \"value\": \"value 2\"\n" +
|
||||||
|
" }\n" +
|
||||||
|
"]";
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected BrokerConfiguration getBrokerConfiguration() {
|
||||||
|
return new KcOidcBrokerConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void addClients() {
|
||||||
|
List<ClientRepresentation> clients = bc.createProviderClients(suiteContext);
|
||||||
|
if (clients != null) {
|
||||||
|
RealmResource providerRealm = adminClient.realm(bc.providerRealmName());
|
||||||
|
for (ClientRepresentation client : clients) {
|
||||||
|
log.debug("adding client " + client.getName() + " to realm " + bc.providerRealmName());
|
||||||
|
|
||||||
|
Response resp = providerRealm.clients().create(client);
|
||||||
|
resp.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clients = bc.createConsumerClients(suiteContext);
|
||||||
|
if (clients != null) {
|
||||||
|
RealmResource consumerRealm = adminClient.realm(bc.consumerRealmName());
|
||||||
|
for (ClientRepresentation client : clients) {
|
||||||
|
log.debug("adding client " + client.getName() + " to realm " + bc.consumerRealmName());
|
||||||
|
|
||||||
|
Response resp = consumerRealm.clients().create(client);
|
||||||
|
resp.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void valueMatchesRegexTest() {
|
||||||
|
AdvancedClaimToRoleMapper advancedClaimToRoleMapper = new AdvancedClaimToRoleMapper();
|
||||||
|
|
||||||
|
assertThat(advancedClaimToRoleMapper.valueMatchesRegex("AB.*", "AB_ADMIN"), is(true));
|
||||||
|
assertThat(advancedClaimToRoleMapper.valueMatchesRegex("AB.*", "AA_ADMIN"), is(false));
|
||||||
|
assertThat(advancedClaimToRoleMapper.valueMatchesRegex("99.*", 999), is(true));
|
||||||
|
assertThat(advancedClaimToRoleMapper.valueMatchesRegex("98.*", 999), is(false));
|
||||||
|
assertThat(advancedClaimToRoleMapper.valueMatchesRegex("99\\..*", 99.9), is(true));
|
||||||
|
assertThat(advancedClaimToRoleMapper.valueMatchesRegex("AB.*", null), is(false));
|
||||||
|
assertThat(advancedClaimToRoleMapper.valueMatchesRegex("AB.*", Arrays.asList("AB_ADMIN", "AA_ADMIN")), is(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void allClaimValuesMatch() {
|
||||||
|
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());
|
||||||
|
|
||||||
|
logInAsUserInIDPForFirstTime();
|
||||||
|
|
||||||
|
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
|
||||||
|
assertThatRoleHasBeenAssigned(user);
|
||||||
|
|
||||||
|
logoutFromRealm(bc.consumerRealmName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void claimValuesMismatch() {
|
||||||
|
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 mismatch").build())
|
||||||
|
.build());
|
||||||
|
|
||||||
|
logInAsUserInIDPForFirstTime();
|
||||||
|
|
||||||
|
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
|
||||||
|
assertThatRoleHasNotBeenAssigned(user);
|
||||||
|
|
||||||
|
logoutFromRealm(bc.consumerRealmName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void claimValuesMatchIfNoClaimsSpecified() {
|
||||||
|
createAdvancedClaimToRoleMapper("[]", false);
|
||||||
|
createUserInProviderRealm(ImmutableMap.<String, List<String>>builder()
|
||||||
|
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("some value").build())
|
||||||
|
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2, ImmutableList.<String>builder().add("some value").build())
|
||||||
|
.build());
|
||||||
|
|
||||||
|
logInAsUserInIDPForFirstTime();
|
||||||
|
|
||||||
|
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
|
||||||
|
assertThatRoleHasBeenAssigned(user);
|
||||||
|
|
||||||
|
logoutFromRealm(bc.consumerRealmName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void allClaimValuesMatchRegex() {
|
||||||
|
createAdvancedClaimToRoleMapper(CLAIMS_REGEX, true);
|
||||||
|
createUserInProviderRealm(ImmutableMap.<String, List<String>>builder()
|
||||||
|
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("value 1").build())
|
||||||
|
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2, ImmutableList.<String>builder().add("value 2").build())
|
||||||
|
.build());
|
||||||
|
|
||||||
|
logInAsUserInIDPForFirstTime();
|
||||||
|
|
||||||
|
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
|
||||||
|
assertThatRoleHasBeenAssigned(user);
|
||||||
|
|
||||||
|
logoutFromRealm(bc.consumerRealmName());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void claimValuesMismatchRegex() {
|
||||||
|
createAdvancedClaimToRoleMapper(CLAIMS_REGEX, true);
|
||||||
|
createUserInProviderRealm(ImmutableMap.<String, List<String>>builder()
|
||||||
|
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME, ImmutableList.<String>builder().add("mismatch").build())
|
||||||
|
.put(KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME_2, ImmutableList.<String>builder().add("value 2").build())
|
||||||
|
.build());
|
||||||
|
|
||||||
|
logInAsUserInIDPForFirstTime();
|
||||||
|
|
||||||
|
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
|
||||||
|
assertThatRoleHasNotBeenAssigned(user);
|
||||||
|
|
||||||
|
logoutFromRealm(bc.consumerRealmName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@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());
|
||||||
|
|
||||||
|
logInAsUserInIDPForFirstTime();
|
||||||
|
|
||||||
|
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
|
||||||
|
assertThatRoleHasBeenAssigned(user);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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());
|
||||||
|
|
||||||
|
logInAsUserInIDPForFirstTime();
|
||||||
|
|
||||||
|
UserRepresentation user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
|
||||||
|
assertThatRoleHasBeenAssigned(user);
|
||||||
|
|
||||||
|
logoutFromRealm(bc.consumerRealmName());
|
||||||
|
|
||||||
|
// update
|
||||||
|
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();
|
||||||
|
user.setAttributes(matchingAttributes);
|
||||||
|
adminClient.realm(bc.providerRealmName()).users().get(user.getId()).update(user);
|
||||||
|
|
||||||
|
logInAsUserInIDP();
|
||||||
|
user = findUser(bc.consumerRealmName(), bc.getUserLogin(), bc.getUserEmail());
|
||||||
|
|
||||||
|
assertThatRoleHasBeenAssigned(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
IdentityProviderMapperRepresentation advancedClaimToRoleMapper = new IdentityProviderMapperRepresentation();
|
||||||
|
advancedClaimToRoleMapper.setName("advanced-claim-to-role-mapper");
|
||||||
|
advancedClaimToRoleMapper.setIdentityProviderMapper(AdvancedClaimToRoleMapper.PROVIDER_ID);
|
||||||
|
advancedClaimToRoleMapper.setConfig(ImmutableMap.<String, String>builder()
|
||||||
|
.put(AdvancedClaimToRoleMapper.CLAIM_PROPERTY_NAME, claimsRepresentation)
|
||||||
|
.put(AdvancedClaimToRoleMapper.ARE_CLAIM_VALUES_REGEX_PROPERTY_NAME, areClaimValuesRegex ? "true" : "false")
|
||||||
|
.put(ConfigConstants.ROLE, CLIENT_ROLE_MAPPER_REPRESENTATION)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
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)));
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,7 +19,7 @@ public class OidcUserAttributeMapperTest extends AbstractUserAttributeMapperTest
|
||||||
attrMapper1.setName("attribute-mapper");
|
attrMapper1.setName("attribute-mapper");
|
||||||
attrMapper1.setIdentityProviderMapper(UserAttributeMapper.PROVIDER_ID);
|
attrMapper1.setIdentityProviderMapper(UserAttributeMapper.PROVIDER_ID);
|
||||||
attrMapper1.setConfig(ImmutableMap.<String,String>builder()
|
attrMapper1.setConfig(ImmutableMap.<String,String>builder()
|
||||||
.put(UserAttributeMapper.CLAIM, ATTRIBUTE_TO_MAP_NAME)
|
.put(UserAttributeMapper.CLAIM, KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME)
|
||||||
.put(UserAttributeMapper.USER_ATTRIBUTE, MAPPED_ATTRIBUTE_NAME)
|
.put(UserAttributeMapper.USER_ATTRIBUTE, MAPPED_ATTRIBUTE_NAME)
|
||||||
.build());
|
.build());
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,7 @@ public class SamlUserAttributeMapperTest extends AbstractUserAttributeMapperTest
|
||||||
attrMapper1.setName("attribute-mapper");
|
attrMapper1.setName("attribute-mapper");
|
||||||
attrMapper1.setIdentityProviderMapper(UserAttributeMapper.PROVIDER_ID);
|
attrMapper1.setIdentityProviderMapper(UserAttributeMapper.PROVIDER_ID);
|
||||||
attrMapper1.setConfig(ImmutableMap.<String,String>builder()
|
attrMapper1.setConfig(ImmutableMap.<String,String>builder()
|
||||||
.put(UserAttributeMapper.ATTRIBUTE_NAME, ATTRIBUTE_TO_MAP_NAME)
|
.put(UserAttributeMapper.ATTRIBUTE_NAME, KcOidcBrokerConfiguration.ATTRIBUTE_TO_MAP_NAME)
|
||||||
.put(UserAttributeMapper.USER_ATTRIBUTE, MAPPED_ATTRIBUTE_NAME)
|
.put(UserAttributeMapper.USER_ATTRIBUTE, MAPPED_ATTRIBUTE_NAME)
|
||||||
.build());
|
.build());
|
||||||
|
|
||||||
|
|
|
@ -2727,6 +2727,10 @@ module.controller('RoleSelectorModalCtrl', function($scope, realm, config, confi
|
||||||
module.controller('ProviderConfigCtrl', function ($modal, $scope, $route, ComponentUtils, Client) {
|
module.controller('ProviderConfigCtrl', function ($modal, $scope, $route, ComponentUtils, Client) {
|
||||||
clientSelectControl($scope, $route.current.params.realm, Client);
|
clientSelectControl($scope, $route.current.params.realm, Client);
|
||||||
$scope.fileNames = {};
|
$scope.fileNames = {};
|
||||||
|
$scope.newMapEntries = {};
|
||||||
|
var cachedMaps = {};
|
||||||
|
var cachedParsedMaps = {};
|
||||||
|
var focusMapValueId = null;
|
||||||
|
|
||||||
// KEYCLOAK-4463
|
// KEYCLOAK-4463
|
||||||
$scope.initEditor = function(editor){
|
$scope.initEditor = function(editor){
|
||||||
|
@ -2802,6 +2806,70 @@ module.controller('ProviderConfigCtrl', function ($modal, $scope, $route, Compon
|
||||||
reader.readAsText($files[0]);
|
reader.readAsText($files[0]);
|
||||||
$scope.fileNames[optionName] = $files[0].name;
|
$scope.fileNames[optionName] = $files[0].name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$scope.addMapEntry = function(optionName) {
|
||||||
|
$scope.removeMapEntry(optionName, $scope.newMapEntries[optionName].key)
|
||||||
|
|
||||||
|
var parsedMap = JSON.parse($scope.config[optionName]);
|
||||||
|
parsedMap.push($scope.newMapEntries[optionName]);
|
||||||
|
$scope.config[optionName] = JSON.stringify(parsedMap);
|
||||||
|
|
||||||
|
delete $scope.newMapEntries[optionName];
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.removeMapEntry = function(optionName, key) {
|
||||||
|
var parsedMap = JSON.parse($scope.config[optionName]);
|
||||||
|
|
||||||
|
for(var i = parsedMap.length - 1; i >= 0; i--) {
|
||||||
|
if(parsedMap[i]['key'] === key) {
|
||||||
|
parsedMap.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.config[optionName] = JSON.stringify(parsedMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.updateMapEntry = function(optionName, key, value) {
|
||||||
|
var parsedMap = JSON.parse($scope.config[optionName]);
|
||||||
|
|
||||||
|
for(var i = parsedMap.length - 1; i >= 0; i--) {
|
||||||
|
if(parsedMap[i]['key'] === key) {
|
||||||
|
parsedMap[i]['value'] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$scope.config[optionName] = JSON.stringify(parsedMap);
|
||||||
|
|
||||||
|
focusMapValueId = "mapValue-" + optionName + "-" + key;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.jsonParseMap = function(optionName) {
|
||||||
|
|
||||||
|
if(cachedParsedMaps[optionName] === undefined) {
|
||||||
|
cachedMaps[optionName] = "[]";
|
||||||
|
cachedParsedMaps[optionName] = [];
|
||||||
|
|
||||||
|
if(!$scope.config.hasOwnProperty(optionName)){
|
||||||
|
$scope.config[optionName]=cachedMaps[optionName];
|
||||||
|
} else {
|
||||||
|
cachedMaps[optionName] = $scope.config[optionName];
|
||||||
|
cachedParsedMaps[optionName] = JSON.parse(cachedMaps[optionName]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var mapChanged = $scope.config[optionName] !== cachedMaps[optionName];
|
||||||
|
|
||||||
|
if(mapChanged){
|
||||||
|
cachedMaps[optionName] = $scope.config[optionName];
|
||||||
|
cachedParsedMaps[optionName] = JSON.parse(cachedMaps[optionName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!mapChanged && focusMapValueId !== null){
|
||||||
|
document.getElementById(focusMapValueId).focus();
|
||||||
|
focusMapValueId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cachedParsedMaps[optionName];
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
module.directive('kcProviderConfig', function ($modal) {
|
module.directive('kcProviderConfig', function ($modal) {
|
||||||
|
|
|
@ -55,6 +55,37 @@
|
||||||
<textarea class="form-control" data-ng-model="config[ option.name ]"/>
|
<textarea class="form-control" data-ng-model="config[ option.name ]"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6" data-ng-if="option.type == 'Map'">
|
||||||
|
<table class="table table-striped table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{{:: 'key' | translate}}</th>
|
||||||
|
<th>{{:: 'value' | translate}}</th>
|
||||||
|
<th>{{:: 'actions' | translate}}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
<tr ng-repeat="mapEntry in jsonParseMap(option.name)">
|
||||||
|
<td>{{mapEntry['key']}}</td>
|
||||||
|
<td><input ng-model="mapEntry['value']"
|
||||||
|
ng-change="updateMapEntry(option.name, mapEntry['key'], mapEntry['value'])"
|
||||||
|
class="form-control" type="text" name="{{mapEntry['key']}}" id="mapValue-{{option.name}}-{{mapEntry['key']}}"/></td>
|
||||||
|
<td class="kc-action-cell" id="removeMapEntry-{{option.name}}" data-ng-click="removeMapEntry(option.name, mapEntry['key'])">{{:: 'delete' | translate}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><input ng-model="newMapEntries[option.name].key" class="form-control" type="text" id="newMapEntryKey-{{option.name}}"/></td>
|
||||||
|
<td><input ng-model="newMapEntries[option.name].value" class="form-control" type="text" id="newMapEntryValue-{{option.name}}"/></td>
|
||||||
|
<td class="kc-action-cell" id="addMapEntry-{{option.name}}" data-ng-click="addMapEntry(option.name)"
|
||||||
|
data-ng-disabled="!newMapEntry.key.length || !newMapEntry.value.length">{{:: 'add' | translate}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
<kc-tooltip>{{:: option.helpText | translate}}</kc-tooltip>
|
<kc-tooltip>{{:: option.helpText | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in a new issue