KEYCLOAK-8708: Provide aggregation of group attributes for mappers

This commit is contained in:
rmartinc 2018-10-31 13:06:00 +01:00 committed by Marek Posolda
parent 36b0d8b80e
commit cbe59f03b7
10 changed files with 676 additions and 9 deletions

View file

@ -54,6 +54,7 @@ import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@ -409,14 +410,25 @@ public final class KeycloakModelUtils {
}
public static List<String> resolveAttribute(UserModel user, String name) {
public static Collection<String> resolveAttribute(UserModel user, String name, boolean aggregateAttrs) {
List<String> values = user.getAttribute(name);
if (!values.isEmpty()) return values;
Set<String> aggrValues = new HashSet<String>();
if (!values.isEmpty()) {
if (!aggregateAttrs) {
return values;
}
aggrValues.addAll(values);
}
for (GroupModel group : user.getGroups()) {
values = resolveAttribute(group, name);
if (values != null) return values;
if (values != null && !values.isEmpty()) {
if (!aggregateAttrs) {
return values;
}
aggrValues.addAll(values);
}
}
return Collections.emptyList();
return aggrValues;
}

View file

@ -44,6 +44,7 @@ public class ProtocolMapperUtils {
public static final String USER_ATTRIBUTE = "user.attribute";
public static final String USER_SESSION_NOTE = "user.session.note";
public static final String MULTIVALUED = "multivalued";
public static final String AGGREGATE_ATTRS = "aggregate.attrs";
public static final String USER_MODEL_PROPERTY_LABEL = "usermodel.prop.label";
public static final String USER_MODEL_PROPERTY_HELP_TEXT = "usermodel.prop.tooltip";
public static final String USER_MODEL_ATTRIBUTE_LABEL = "usermodel.attr.label";
@ -64,7 +65,9 @@ public class ProtocolMapperUtils {
public static final String USER_SESSION_MODEL_NOTE_LABEL = "userSession.modelNote.label";
public static final String USER_SESSION_MODEL_NOTE_HELP_TEXT = "userSession.modelNote.tooltip";
public static final String MULTIVALUED_LABEL = "multivalued.label";
public static final String AGGREGATE_ATTRS_LABEL = "aggregate.attrs.label";
public static final String MULTIVALUED_HELP_TEXT = "multivalued.tooltip";
public static final String AGGREGATE_ATTRS_HELP_TEXT = "aggregate.attrs.tooltip";
// Role name mapper can move some roles to different positions
public static final int PRIORITY_ROLE_NAMES_MAPPER = 10;

View file

@ -26,6 +26,7 @@ import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.IDToken;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
@ -57,6 +58,12 @@ public class UserAttributeMapper extends AbstractOIDCProtocolMapper implements O
property.setType(ProviderConfigProperty.BOOLEAN_TYPE);
configProperties.add(property);
property = new ProviderConfigProperty();
property.setName(ProtocolMapperUtils.AGGREGATE_ATTRS);
property.setLabel(ProtocolMapperUtils.AGGREGATE_ATTRS_LABEL);
property.setHelpText(ProtocolMapperUtils.AGGREGATE_ATTRS_HELP_TEXT);
property.setType(ProviderConfigProperty.BOOLEAN_TYPE);
configProperties.add(property);
}
public static final String PROVIDER_ID = "oidc-usermodel-attribute-mapper";
@ -90,7 +97,8 @@ public class UserAttributeMapper extends AbstractOIDCProtocolMapper implements O
UserModel user = userSession.getUser();
String attributeName = mappingModel.getConfig().get(ProtocolMapperUtils.USER_ATTRIBUTE);
List<String> attributeValue = KeycloakModelUtils.resolveAttribute(user, attributeName);
boolean aggregateAttrs = Boolean.valueOf(mappingModel.getConfig().get(ProtocolMapperUtils.AGGREGATE_ATTRS));
Collection<String> attributeValue = KeycloakModelUtils.resolveAttribute(user, attributeName, aggregateAttrs);
if (attributeValue == null) return;
OIDCAttributeMapperHelper.mapClaim(token, mappingModel, attributeValue);
}
@ -99,6 +107,15 @@ public class UserAttributeMapper extends AbstractOIDCProtocolMapper implements O
String userAttribute,
String tokenClaimName, String claimType,
boolean accessToken, boolean idToken, boolean multivalued) {
return createClaimMapper(name, userAttribute, tokenClaimName, claimType,
accessToken, idToken, multivalued, false);
}
public static ProtocolMapperModel createClaimMapper(String name,
String userAttribute,
String tokenClaimName, String claimType,
boolean accessToken, boolean idToken,
boolean multivalued, boolean aggregateAttrs) {
ProtocolMapperModel mapper = OIDCAttributeMapperHelper.createClaimMapper(name, userAttribute,
tokenClaimName, claimType,
accessToken, idToken,
@ -107,6 +124,9 @@ public class UserAttributeMapper extends AbstractOIDCProtocolMapper implements O
if (multivalued) {
mapper.getConfig().put(ProtocolMapperUtils.MULTIVALUED, "true");
}
if (aggregateAttrs) {
mapper.getConfig().put(ProtocolMapperUtils.AGGREGATE_ATTRS, "true");
}
return mapper;
}

View file

@ -26,6 +26,7 @@ import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -53,7 +54,7 @@ public class AttributeStatementHelper {
}
public static void addAttributes(AttributeStatementType attributeStatement, ProtocolMapperModel mappingModel,
List<String> attributeValues) {
Collection<String> attributeValues) {
AttributeType attribute = createAttributeType(mappingModel);
attributeValues.forEach(attribute::addAttributeValue);

View file

@ -27,6 +27,7 @@ import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.ProtocolMapperUtils;
import org.keycloak.provider.ProviderConfigProperty;
import java.util.Collection;
import java.util.ArrayList;
import java.util.List;
@ -48,6 +49,12 @@ public class UserAttributeStatementMapper extends AbstractSAMLProtocolMapper imp
configProperties.add(property);
AttributeStatementHelper.setConfigProperties(configProperties);
property = new ProviderConfigProperty();
property.setName(ProtocolMapperUtils.AGGREGATE_ATTRS);
property.setLabel(ProtocolMapperUtils.AGGREGATE_ATTRS_LABEL);
property.setHelpText(ProtocolMapperUtils.AGGREGATE_ATTRS_HELP_TEXT);
property.setType(ProviderConfigProperty.BOOLEAN_TYPE);
configProperties.add(property);
}
public static final String PROVIDER_ID = "saml-user-attribute-mapper";
@ -80,7 +87,8 @@ public class UserAttributeStatementMapper extends AbstractSAMLProtocolMapper imp
public void transformAttributeStatement(AttributeStatementType attributeStatement, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
UserModel user = userSession.getUser();
String attributeName = mappingModel.getConfig().get(ProtocolMapperUtils.USER_ATTRIBUTE);
List<String> attributeValues = KeycloakModelUtils.resolveAttribute(user, attributeName);
boolean aggregateAttrs = Boolean.valueOf(mappingModel.getConfig().get(ProtocolMapperUtils.AGGREGATE_ATTRS));
Collection<String> attributeValues = KeycloakModelUtils.resolveAttribute(user, attributeName, aggregateAttrs);
if (attributeValues.isEmpty()) return;
AttributeStatementHelper.addAttributes(attributeStatement, mappingModel, attributeValues);
}

View file

@ -202,6 +202,10 @@ public class SendUsernameServlet extends HttpServlet {
for (String attr : principal.getAttributes("hardcoded-attribute")) {
output += attr + ",";
}
output += "<br /> group-attribute: ";
for (String attr : principal.getAttributes("group-attribute")) {
output += attr + ",";
}
return output;
}

View file

@ -43,6 +43,9 @@ import java.net.URI;
import java.net.URL;
import java.security.KeyPair;
import java.security.PublicKey;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
@ -100,6 +103,7 @@ import org.junit.Test;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.ProtocolMappersResource;
import org.keycloak.admin.client.resource.RoleScopeResource;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.common.util.Base64;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.common.util.PemUtils;
@ -116,6 +120,7 @@ import org.keycloak.protocol.saml.mappers.AttributeStatementHelper;
import org.keycloak.protocol.saml.mappers.RoleListMapper;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ComponentRepresentation;
import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
@ -140,6 +145,7 @@ import org.keycloak.testsuite.auth.page.login.SAMLPostLoginTenant2;
import org.keycloak.testsuite.page.AbstractPage;
import org.keycloak.testsuite.saml.AbstractSamlTest;
import org.keycloak.testsuite.updaters.ClientAttributeUpdater;
import org.keycloak.testsuite.util.ProtocolMapperUtil;
import org.keycloak.testsuite.util.SamlClient;
import org.keycloak.testsuite.util.SamlClient.Binding;
import org.keycloak.testsuite.util.SamlClientBuilder;
@ -1158,6 +1164,233 @@ public class SAMLServletAdapterTest extends AbstractServletsAdapterTest {
Assert.assertThat(pageSource, not(containsString("SAML response: null")));
}
private static List<String> parseCommaSeparatedAttributes(String body, String attribute) {
int start = body.indexOf(attribute) + attribute.length();
if (start == -1) {
return Collections.emptyList();
}
int end = body.indexOf(System.getProperty("line.separator"), start);
if (end == -1) {
end = body.length();
}
String values = body.substring(start, end);
String[] parts = values.split(",");
return Arrays.asList(parts);
}
@Test
public void testUserAttributeStatementMapperUserGroupsAggregate() throws Exception {
UserResource userResource = ApiUtil.findUserByUsernameId(testRealmResource(), "bburke");
UserRepresentation user = userResource.toRepresentation();
user.setAttributes(new HashMap<>());
user.getAttributes().put("group-value", Arrays.asList("user-value1"));
userResource.update(user);
GroupRepresentation group1 = new GroupRepresentation();
group1.setName("group1");
group1.setAttributes(new HashMap<>());
group1.getAttributes().put("group-value", Arrays.asList("value1", "value2"));
testRealmResource().groups().add(group1);
group1 = testRealmResource().getGroupByPath("/group1");
userResource.joinGroup(group1.getId());
ClientResource clientResource = ApiUtil.findClientResourceByClientId(testRealmResource(), AbstractSamlTest.SAML_CLIENT_ID_EMPLOYEE_2);
ProtocolMappersResource protocolMappersResource = clientResource.getProtocolMappers();
Map<String, String> config = new LinkedHashMap<>();
config.put("attribute.nameformat", "Basic");
config.put("user.attribute", "group-value");
config.put("attribute.name", "group-attribute");
config.put("aggregate.attrs", "true");
createProtocolMapper(protocolMappersResource, "group-value", "saml", "saml-user-attribute-mapper", config);
try {
employee2ServletPage.navigateTo();
assertCurrentUrlStartsWith(testRealmSAMLPostLoginPage);
testRealmSAMLPostLoginPage.form().login("bburke", "password");
driver.navigate().to(employee2ServletPage.toString() + "/getAttributes");
waitForPageToLoad();
String body = driver.findElement(By.xpath("//body")).getText();
List<String> values = parseCommaSeparatedAttributes(body, " group-attribute: ");
Assert.assertEquals(3, values.size());
Assert.assertTrue(values.contains("user-value1"));
Assert.assertTrue(values.contains("value1"));
Assert.assertTrue(values.contains("value2"));
employee2ServletPage.logout();
checkLoggedOut(employee2ServletPage, testRealmSAMLPostLoginPage);
} finally {
// revert
user.getAttributes().remove("group-value");
userResource.update(user);
userResource.leaveGroup(group1.getId());
testRealmResource().groups().group(group1.getId()).remove();
ProtocolMapperRepresentation mapper = ProtocolMapperUtil.getMapperByNameAndProtocol(protocolMappersResource, "saml", "group-value");
protocolMappersResource.delete(mapper.getId());
}
}
@Test
public void testUserAttributeStatementMapperUserGroupsNoAggregate() throws Exception {
UserResource userResource = ApiUtil.findUserByUsernameId(testRealmResource(), "bburke");
UserRepresentation user = userResource.toRepresentation();
user.setAttributes(new HashMap<>());
user.getAttributes().put("group-value", Arrays.asList("user-value1"));
userResource.update(user);
GroupRepresentation group1 = new GroupRepresentation();
group1.setName("group1");
group1.setAttributes(new HashMap<>());
group1.getAttributes().put("group-value", Arrays.asList("value1", "value2"));
testRealmResource().groups().add(group1);
group1 = testRealmResource().getGroupByPath("/group1");
userResource.joinGroup(group1.getId());
ClientResource clientResource = ApiUtil.findClientResourceByClientId(testRealmResource(), AbstractSamlTest.SAML_CLIENT_ID_EMPLOYEE_2);
ProtocolMappersResource protocolMappersResource = clientResource.getProtocolMappers();
Map<String, String> config = new LinkedHashMap<>();
config.put("attribute.nameformat", "Basic");
config.put("user.attribute", "group-value");
config.put("attribute.name", "group-attribute");
createProtocolMapper(protocolMappersResource, "group-value", "saml", "saml-user-attribute-mapper", config);
try {
employee2ServletPage.navigateTo();
assertCurrentUrlStartsWith(testRealmSAMLPostLoginPage);
testRealmSAMLPostLoginPage.form().login("bburke", "password");
driver.navigate().to(employee2ServletPage.toString() + "/getAttributes");
waitForPageToLoad();
String body = driver.findElement(By.xpath("//body")).getText();
List<String> values = parseCommaSeparatedAttributes(body, " group-attribute: ");
Assert.assertEquals(1, values.size());
Assert.assertTrue(values.contains("user-value1"));
employee2ServletPage.logout();
checkLoggedOut(employee2ServletPage, testRealmSAMLPostLoginPage);
} finally {
// revert
user.getAttributes().remove("group-value");
userResource.update(user);
userResource.leaveGroup(group1.getId());
testRealmResource().groups().group(group1.getId()).remove();
ProtocolMapperRepresentation mapper = ProtocolMapperUtil.getMapperByNameAndProtocol(protocolMappersResource, "saml", "group-value");
protocolMappersResource.delete(mapper.getId());
}
}
@Test
public void testUserAttributeStatementMapperGroupsAggregate() throws Exception {
UserResource userResource = ApiUtil.findUserByUsernameId(testRealmResource(), "bburke");
GroupRepresentation group1 = new GroupRepresentation();
group1.setName("group1");
group1.setAttributes(new HashMap<>());
group1.getAttributes().put("group-value", Arrays.asList("value1", "value2"));
testRealmResource().groups().add(group1);
group1 = testRealmResource().getGroupByPath("/group1");
userResource.joinGroup(group1.getId());
GroupRepresentation group2 = new GroupRepresentation();
group2.setName("group2");
group2.setAttributes(new HashMap<>());
group2.getAttributes().put("group-value", Arrays.asList("value2", "value3"));
testRealmResource().groups().add(group2);
group2 = testRealmResource().getGroupByPath("/group2");
userResource.joinGroup(group2.getId());
ClientResource clientResource = ApiUtil.findClientResourceByClientId(testRealmResource(), AbstractSamlTest.SAML_CLIENT_ID_EMPLOYEE_2);
ProtocolMappersResource protocolMappersResource = clientResource.getProtocolMappers();
Map<String, String> config = new LinkedHashMap<>();
config.put("attribute.nameformat", "Basic");
config.put("user.attribute", "group-value");
config.put("attribute.name", "group-attribute");
config.put("aggregate.attrs", "true");
createProtocolMapper(protocolMappersResource, "group-value", "saml", "saml-user-attribute-mapper", config);
try {
employee2ServletPage.navigateTo();
assertCurrentUrlStartsWith(testRealmSAMLPostLoginPage);
testRealmSAMLPostLoginPage.form().login("bburke", "password");
driver.navigate().to(employee2ServletPage.toString() + "/getAttributes");
waitForPageToLoad();
String body = driver.findElement(By.xpath("//body")).getText();
List<String> values = parseCommaSeparatedAttributes(body, " group-attribute: ");
Assert.assertEquals(3, values.size());
Assert.assertTrue(values.contains("value1"));
Assert.assertTrue(values.contains("value2"));
Assert.assertTrue(values.contains("value3"));
employee2ServletPage.logout();
checkLoggedOut(employee2ServletPage, testRealmSAMLPostLoginPage);
} finally {
// revert
userResource.leaveGroup(group1.getId());
testRealmResource().groups().group(group1.getId()).remove();
userResource.leaveGroup(group2.getId());
testRealmResource().groups().group(group2.getId()).remove();
ProtocolMapperRepresentation mapper = ProtocolMapperUtil.getMapperByNameAndProtocol(protocolMappersResource, "saml", "group-value");
protocolMappersResource.delete(mapper.getId());
}
}
@Test
public void testUserAttributeStatementMapperGroupsNoAggregate() throws Exception {
UserResource userResource = ApiUtil.findUserByUsernameId(testRealmResource(), "bburke");
GroupRepresentation group1 = new GroupRepresentation();
group1.setName("group1");
group1.setAttributes(new HashMap<>());
group1.getAttributes().put("group-value", Arrays.asList("value1", "value2"));
testRealmResource().groups().add(group1);
group1 = testRealmResource().getGroupByPath("/group1");
userResource.joinGroup(group1.getId());
GroupRepresentation group2 = new GroupRepresentation();
group2.setName("group2");
group2.setAttributes(new HashMap<>());
group2.getAttributes().put("group-value", Arrays.asList("value2", "value3"));
testRealmResource().groups().add(group2);
group2 = testRealmResource().getGroupByPath("/group2");
userResource.joinGroup(group2.getId());
ClientResource clientResource = ApiUtil.findClientResourceByClientId(testRealmResource(), AbstractSamlTest.SAML_CLIENT_ID_EMPLOYEE_2);
ProtocolMappersResource protocolMappersResource = clientResource.getProtocolMappers();
Map<String, String> config = new LinkedHashMap<>();
config.put("attribute.nameformat", "Basic");
config.put("user.attribute", "group-value");
config.put("attribute.name", "group-attribute");
createProtocolMapper(protocolMappersResource, "group-value", "saml", "saml-user-attribute-mapper", config);
try {
employee2ServletPage.navigateTo();
assertCurrentUrlStartsWith(testRealmSAMLPostLoginPage);
testRealmSAMLPostLoginPage.form().login("bburke", "password");
driver.navigate().to(employee2ServletPage.toString() + "/getAttributes");
waitForPageToLoad();
String body = driver.findElement(By.xpath("//body")).getText();
List<String> values = parseCommaSeparatedAttributes(body, " group-attribute: ");
Assert.assertEquals(2, values.size());
Assert.assertTrue((values.contains("value1") && values.contains("value2"))
|| (values.contains("value2") && values.contains("value3")));
employee2ServletPage.logout();
checkLoggedOut(employee2ServletPage, testRealmSAMLPostLoginPage);
} finally {
// revert
userResource.leaveGroup(group1.getId());
testRealmResource().groups().group(group1.getId()).remove();
userResource.leaveGroup(group2.getId());
testRealmResource().groups().group(group2.getId()).remove();
ProtocolMapperRepresentation mapper = ProtocolMapperUtil.getMapperByNameAndProtocol(protocolMappersResource, "saml", "group-value");
protocolMappersResource.delete(mapper.getId());
}
}
@Test
public void testAttributes() throws Exception {
ClientResource clientResource = ApiUtil.findClientResourceByClientId(testRealmResource(), AbstractSamlTest.SAML_CLIENT_ID_EMPLOYEE_2);

View file

@ -54,6 +54,7 @@ import javax.ws.rs.core.Response;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -117,6 +118,11 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
if (mapper != null) {
protocolMappers.delete(mapper.getId());
}
mapper = ProtocolMapperUtil.getMapperByNameAndProtocol(protocolMappers, OIDCLoginProtocol.LOGIN_PROTOCOL, "group-value");
if (mapper != null) {
protocolMappers.delete(mapper.getId());
}
}
@Override
@ -734,6 +740,374 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
deleteMappers(protocolMappers);
}
@Test
public void testGroupAttributeUserOneGroupNoMultivalueNoAggregate() throws Exception {
// get the user
UserResource userResource = findUserByUsernameId(adminClient.realm("test"), "test-user@localhost");
UserRepresentation user = userResource.toRepresentation();
user.setAttributes(new HashMap<>());
user.getAttributes().put("group-value", Arrays.asList("user-value1", "user-value2"));
userResource.update(user);
// create a group1 with two values
GroupRepresentation group1 = new GroupRepresentation();
group1.setName("group1");
group1.setAttributes(new HashMap<>());
group1.getAttributes().put("group-value", Arrays.asList("value1", "value2"));
adminClient.realm("test").groups().add(group1);
group1 = adminClient.realm("test").getGroupByPath("/group1");
userResource.joinGroup(group1.getId());
// create the attribute mapper
ProtocolMappersResource protocolMappers = findClientResourceByClientId(adminClient.realm("test"), "test-app").getProtocolMappers();
protocolMappers.createMapper(createClaimMapper("group-value", "group-value", "group-value", "String", true, true, false, false)).close();
try {
// test it
OAuthClient.AccessTokenResponse response = browserLogin("password", "test-user@localhost", "password");
IDToken idToken = oauth.verifyIDToken(response.getIdToken());
assertNotNull(idToken.getOtherClaims());
assertNotNull(idToken.getOtherClaims().get("group-value"));
assertTrue(idToken.getOtherClaims().get("group-value") instanceof String);
assertTrue("user-value1".equals(idToken.getOtherClaims().get("group-value")) ||
"user-value2".equals(idToken.getOtherClaims().get("group-value")));
} finally {
// revert
user.getAttributes().remove("group-value");
userResource.update(user);
userResource.leaveGroup(group1.getId());
adminClient.realm("test").groups().group(group1.getId()).remove();
deleteMappers(protocolMappers);
}
}
@Test
public void testGroupAttributeUserOneGroupMultivalueNoAggregate() throws Exception {
// get the user
UserResource userResource = findUserByUsernameId(adminClient.realm("test"), "test-user@localhost");
UserRepresentation user = userResource.toRepresentation();
user.setAttributes(new HashMap<>());
user.getAttributes().put("group-value", Arrays.asList("user-value1", "user-value2"));
userResource.update(user);
// create a group1 with two values
GroupRepresentation group1 = new GroupRepresentation();
group1.setName("group1");
group1.setAttributes(new HashMap<>());
group1.getAttributes().put("group-value", Arrays.asList("value1", "value2"));
adminClient.realm("test").groups().add(group1);
group1 = adminClient.realm("test").getGroupByPath("/group1");
userResource.joinGroup(group1.getId());
// create the attribute mapper
ProtocolMappersResource protocolMappers = findClientResourceByClientId(adminClient.realm("test"), "test-app").getProtocolMappers();
protocolMappers.createMapper(createClaimMapper("group-value", "group-value", "group-value", "String", true, true, true, false)).close();
try {
// test it
OAuthClient.AccessTokenResponse response = browserLogin("password", "test-user@localhost", "password");
IDToken idToken = oauth.verifyIDToken(response.getIdToken());
assertNotNull(idToken.getOtherClaims());
assertNotNull(idToken.getOtherClaims().get("group-value"));
assertTrue(idToken.getOtherClaims().get("group-value") instanceof List);
assertEquals(2, ((List) idToken.getOtherClaims().get("group-value")).size());
assertTrue(((List) idToken.getOtherClaims().get("group-value")).contains("user-value1"));
assertTrue(((List) idToken.getOtherClaims().get("group-value")).contains("user-value2"));
} finally {
// revert
user.getAttributes().remove("group-value");
userResource.update(user);
userResource.leaveGroup(group1.getId());
adminClient.realm("test").groups().group(group1.getId()).remove();
deleteMappers(protocolMappers);
}
}
@Test
public void testGroupAttributeUserOneGroupMultivalueAggregate() throws Exception {
// get the user
UserResource userResource = findUserByUsernameId(adminClient.realm("test"), "test-user@localhost");
UserRepresentation user = userResource.toRepresentation();
user.setAttributes(new HashMap<>());
user.getAttributes().put("group-value", Arrays.asList("user-value1", "user-value2"));
userResource.update(user);
// create a group1 with two values
GroupRepresentation group1 = new GroupRepresentation();
group1.setName("group1");
group1.setAttributes(new HashMap<>());
group1.getAttributes().put("group-value", Arrays.asList("value1", "value2"));
adminClient.realm("test").groups().add(group1);
group1 = adminClient.realm("test").getGroupByPath("/group1");
userResource.joinGroup(group1.getId());
// create the attribute mapper
ProtocolMappersResource protocolMappers = findClientResourceByClientId(adminClient.realm("test"), "test-app").getProtocolMappers();
protocolMappers.createMapper(createClaimMapper("group-value", "group-value", "group-value", "String", true, true, true, true)).close();
try {
// test it
OAuthClient.AccessTokenResponse response = browserLogin("password", "test-user@localhost", "password");
IDToken idToken = oauth.verifyIDToken(response.getIdToken());
assertNotNull(idToken.getOtherClaims());
assertNotNull(idToken.getOtherClaims().get("group-value"));
assertTrue(idToken.getOtherClaims().get("group-value") instanceof List);
assertEquals(4, ((List) idToken.getOtherClaims().get("group-value")).size());
assertTrue(((List) idToken.getOtherClaims().get("group-value")).contains("user-value1"));
assertTrue(((List) idToken.getOtherClaims().get("group-value")).contains("user-value2"));
assertTrue(((List) idToken.getOtherClaims().get("group-value")).contains("value1"));
assertTrue(((List) idToken.getOtherClaims().get("group-value")).contains("value2"));
} finally {
// revert
user.getAttributes().remove("group-value");
userResource.update(user);
userResource.leaveGroup(group1.getId());
adminClient.realm("test").groups().group(group1.getId()).remove();
deleteMappers(protocolMappers);
}
}
@Test
public void testGroupAttributeOneGroupNoMultivalueNoAggregate() throws Exception {
// get the user
UserResource userResource = findUserByUsernameId(adminClient.realm("test"), "test-user@localhost");
// create a group1 with two values
GroupRepresentation group1 = new GroupRepresentation();
group1.setName("group1");
group1.setAttributes(new HashMap<>());
group1.getAttributes().put("group-value", Arrays.asList("value1", "value2"));
adminClient.realm("test").groups().add(group1);
group1 = adminClient.realm("test").getGroupByPath("/group1");
userResource.joinGroup(group1.getId());
// create the attribute mapper
ProtocolMappersResource protocolMappers = findClientResourceByClientId(adminClient.realm("test"), "test-app").getProtocolMappers();
protocolMappers.createMapper(createClaimMapper("group-value", "group-value", "group-value", "String", true, true, false, false)).close();
try {
// test it
OAuthClient.AccessTokenResponse response = browserLogin("password", "test-user@localhost", "password");
IDToken idToken = oauth.verifyIDToken(response.getIdToken());
assertNotNull(idToken.getOtherClaims());
assertNotNull(idToken.getOtherClaims().get("group-value"));
assertTrue(idToken.getOtherClaims().get("group-value") instanceof String);
assertTrue("value1".equals(idToken.getOtherClaims().get("group-value"))
|| "value2".equals(idToken.getOtherClaims().get("group-value")));
} finally {
// revert
userResource.leaveGroup(group1.getId());
adminClient.realm("test").groups().group(group1.getId()).remove();
deleteMappers(protocolMappers);
}
}
@Test
public void testGroupAttributeOneGroupMultiValueNoAggregate() throws Exception {
// get the user
UserResource userResource = findUserByUsernameId(adminClient.realm("test"), "test-user@localhost");
// create a group1 with two values
GroupRepresentation group1 = new GroupRepresentation();
group1.setName("group1");
group1.setAttributes(new HashMap<>());
group1.getAttributes().put("group-value", Arrays.asList("value1", "value2"));
adminClient.realm("test").groups().add(group1);
group1 = adminClient.realm("test").getGroupByPath("/group1");
userResource.joinGroup(group1.getId());
// create the attribute mapper
ProtocolMappersResource protocolMappers = findClientResourceByClientId(adminClient.realm("test"), "test-app").getProtocolMappers();
protocolMappers.createMapper(createClaimMapper("group-value", "group-value", "group-value", "String", true, true, true, false)).close();
try {
// test it
OAuthClient.AccessTokenResponse response = browserLogin("password", "test-user@localhost", "password");
IDToken idToken = oauth.verifyIDToken(response.getIdToken());
assertNotNull(idToken.getOtherClaims());
assertNotNull(idToken.getOtherClaims().get("group-value"));
assertTrue(idToken.getOtherClaims().get("group-value") instanceof List);
assertEquals(2, ((List) idToken.getOtherClaims().get("group-value")).size());
assertTrue(((List) idToken.getOtherClaims().get("group-value")).contains("value1"));
assertTrue(((List) idToken.getOtherClaims().get("group-value")).contains("value2"));
} finally {
// revert
userResource.leaveGroup(group1.getId());
adminClient.realm("test").groups().group(group1.getId()).remove();
deleteMappers(protocolMappers);
}
}
@Test
public void testGroupAttributeOneGroupMultiValueAggregate() throws Exception {
// get the user
UserResource userResource = findUserByUsernameId(adminClient.realm("test"), "test-user@localhost");
// create a group1 with two values
GroupRepresentation group1 = new GroupRepresentation();
group1.setName("group1");
group1.setAttributes(new HashMap<>());
group1.getAttributes().put("group-value", Arrays.asList("value1", "value2"));
adminClient.realm("test").groups().add(group1);
group1 = adminClient.realm("test").getGroupByPath("/group1");
userResource.joinGroup(group1.getId());
// create the attribute mapper
ProtocolMappersResource protocolMappers = findClientResourceByClientId(adminClient.realm("test"), "test-app").getProtocolMappers();
protocolMappers.createMapper(createClaimMapper("group-value", "group-value", "group-value", "String", true, true, true, true)).close();
try {
// test it
OAuthClient.AccessTokenResponse response = browserLogin("password", "test-user@localhost", "password");
IDToken idToken = oauth.verifyIDToken(response.getIdToken());
assertNotNull(idToken.getOtherClaims());
assertNotNull(idToken.getOtherClaims().get("group-value"));
assertTrue(idToken.getOtherClaims().get("group-value") instanceof List);
assertEquals(2, ((List) idToken.getOtherClaims().get("group-value")).size());
assertTrue(((List) idToken.getOtherClaims().get("group-value")).contains("value1"));
assertTrue(((List) idToken.getOtherClaims().get("group-value")).contains("value2"));
} finally {
// revert
userResource.leaveGroup(group1.getId());
adminClient.realm("test").groups().group(group1.getId()).remove();
deleteMappers(protocolMappers);
}
}
@Test
public void testGroupAttributeTwoGroupNoMultivalueNoAggregate() throws Exception {
// get the user
UserResource userResource = findUserByUsernameId(adminClient.realm("test"), "test-user@localhost");
// create two groups with two values (one is the same value)
GroupRepresentation group1 = new GroupRepresentation();
group1.setName("group1");
group1.setAttributes(new HashMap<>());
group1.getAttributes().put("group-value", Arrays.asList("value1", "value2"));
adminClient.realm("test").groups().add(group1);
group1 = adminClient.realm("test").getGroupByPath("/group1");
userResource.joinGroup(group1.getId());
GroupRepresentation group2 = new GroupRepresentation();
group2.setName("group2");
group2.setAttributes(new HashMap<>());
group2.getAttributes().put("group-value", Arrays.asList("value2", "value3"));
adminClient.realm("test").groups().add(group2);
group2 = adminClient.realm("test").getGroupByPath("/group2");
userResource.joinGroup(group2.getId());
// create the attribute mapper
ProtocolMappersResource protocolMappers = findClientResourceByClientId(adminClient.realm("test"), "test-app").getProtocolMappers();
protocolMappers.createMapper(createClaimMapper("group-value", "group-value", "group-value", "String", true, true, false, false)).close();
try {
// test it
OAuthClient.AccessTokenResponse response = browserLogin("password", "test-user@localhost", "password");
IDToken idToken = oauth.verifyIDToken(response.getIdToken());
assertNotNull(idToken.getOtherClaims());
assertNotNull(idToken.getOtherClaims().get("group-value"));
assertTrue(idToken.getOtherClaims().get("group-value") instanceof String);
assertTrue("value1".equals(idToken.getOtherClaims().get("group-value"))
|| "value2".equals(idToken.getOtherClaims().get("group-value"))
|| "value3".equals(idToken.getOtherClaims().get("group-value")));
} finally {
// revert
userResource.leaveGroup(group1.getId());
adminClient.realm("test").groups().group(group1.getId()).remove();
userResource.leaveGroup(group2.getId());
adminClient.realm("test").groups().group(group2.getId()).remove();
deleteMappers(protocolMappers);
}
}
@Test
public void testGroupAttributeTwoGroupMultiValueNoAggregate() throws Exception {
// get the user
UserResource userResource = findUserByUsernameId(adminClient.realm("test"), "test-user@localhost");
// create two groups with two values (one is the same value)
GroupRepresentation group1 = new GroupRepresentation();
group1.setName("group1");
group1.setAttributes(new HashMap<>());
group1.getAttributes().put("group-value", Arrays.asList("value1", "value2"));
adminClient.realm("test").groups().add(group1);
group1 = adminClient.realm("test").getGroupByPath("/group1");
userResource.joinGroup(group1.getId());
GroupRepresentation group2 = new GroupRepresentation();
group2.setName("group2");
group2.setAttributes(new HashMap<>());
group2.getAttributes().put("group-value", Arrays.asList("value2", "value3"));
adminClient.realm("test").groups().add(group2);
group2 = adminClient.realm("test").getGroupByPath("/group2");
userResource.joinGroup(group2.getId());
// create the attribute mapper
ProtocolMappersResource protocolMappers = findClientResourceByClientId(adminClient.realm("test"), "test-app").getProtocolMappers();
protocolMappers.createMapper(createClaimMapper("group-value", "group-value", "group-value", "String", true, true, true, false)).close();
try {
// test it
OAuthClient.AccessTokenResponse response = browserLogin("password", "test-user@localhost", "password");
IDToken idToken = oauth.verifyIDToken(response.getIdToken());
assertNotNull(idToken.getOtherClaims());
assertNotNull(idToken.getOtherClaims().get("group-value"));
assertTrue(idToken.getOtherClaims().get("group-value") instanceof List);
assertEquals(2, ((List) idToken.getOtherClaims().get("group-value")).size());
assertTrue((((List) idToken.getOtherClaims().get("group-value")).contains("value1")
&& ((List) idToken.getOtherClaims().get("group-value")).contains("value2"))
|| (((List) idToken.getOtherClaims().get("group-value")).contains("value2")
&& ((List) idToken.getOtherClaims().get("group-value")).contains("value3")));
} finally {
// revert
userResource.leaveGroup(group1.getId());
adminClient.realm("test").groups().group(group1.getId()).remove();
userResource.leaveGroup(group2.getId());
adminClient.realm("test").groups().group(group2.getId()).remove();
deleteMappers(protocolMappers);
}
}
@Test
public void testGroupAttributeTwoGroupMultiValueAggregate() throws Exception {
// get the user
UserResource userResource = findUserByUsernameId(adminClient.realm("test"), "test-user@localhost");
// create two groups with two values (one is the same value)
GroupRepresentation group1 = new GroupRepresentation();
group1.setName("group1");
group1.setAttributes(new HashMap<>());
group1.getAttributes().put("group-value", Arrays.asList("value1", "value2"));
adminClient.realm("test").groups().add(group1);
group1 = adminClient.realm("test").getGroupByPath("/group1");
userResource.joinGroup(group1.getId());
GroupRepresentation group2 = new GroupRepresentation();
group2.setName("group2");
group2.setAttributes(new HashMap<>());
group2.getAttributes().put("group-value", Arrays.asList("value2", "value3"));
adminClient.realm("test").groups().add(group2);
group2 = adminClient.realm("test").getGroupByPath("/group2");
userResource.joinGroup(group2.getId());
// create the attribute mapper
ProtocolMappersResource protocolMappers = findClientResourceByClientId(adminClient.realm("test"), "test-app").getProtocolMappers();
protocolMappers.createMapper(createClaimMapper("group-value", "group-value", "group-value", "String", true, true, true, true)).close();
try {
// test it
OAuthClient.AccessTokenResponse response = browserLogin("password", "test-user@localhost", "password");
IDToken idToken = oauth.verifyIDToken(response.getIdToken());
assertNotNull(idToken.getOtherClaims());
assertNotNull(idToken.getOtherClaims().get("group-value"));
assertTrue(idToken.getOtherClaims().get("group-value") instanceof List);
assertEquals(3, ((List) idToken.getOtherClaims().get("group-value")).size());
assertTrue(((List) idToken.getOtherClaims().get("group-value")).contains("value1"));
assertTrue(((List) idToken.getOtherClaims().get("group-value")).contains("value2"));
assertTrue(((List) idToken.getOtherClaims().get("group-value")).contains("value3"));
} finally {
// revert
userResource.leaveGroup(group1.getId());
adminClient.realm("test").groups().group(group1.getId()).remove();
userResource.leaveGroup(group2.getId());
adminClient.realm("test").groups().group(group2.getId()).remove();
deleteMappers(protocolMappers);
}
}
private void assertRoles(List<String> actualRoleList, String ...expectedRoles){
Assert.assertNames(actualRoleList, expectedRoles);
}

View file

@ -87,7 +87,17 @@ public class ProtocolMapperUtil {
String tokenClaimName, String claimType,
boolean accessToken, boolean idToken, boolean multivalued) {
return ModelToRepresentation.toRepresentation(UserAttributeMapper.createClaimMapper(name, userAttribute, tokenClaimName,
claimType, accessToken, idToken, multivalued));
claimType, accessToken, idToken, multivalued, false));
}
public static ProtocolMapperRepresentation createClaimMapper(String name,
String userAttribute,
String tokenClaimName, String claimType,
boolean accessToken, boolean idToken,
boolean multivalued, boolean aggregateAttrs) {
return ModelToRepresentation.toRepresentation(UserAttributeMapper.createClaimMapper(name, userAttribute, tokenClaimName,
claimType, accessToken, idToken, multivalued, aggregateAttrs));
}

View file

@ -206,6 +206,8 @@ userSession.modelNote.label=User Session Note
userSession.modelNote.tooltip=Name of stored user session note within the UserSessionModel.note map.
multivalued.label=Multivalued
multivalued.tooltip=Indicates if attribute supports multiple values. If true, then the list of all values of this attribute will be set as claim. If false, then just first value will be set as claim
aggregate.attrs.label=Aggregate attribute values
aggregate.attrs.tooltip=Indicates if attribute values should be aggregated with the group attributes. If using OpenID Connect mapper the multivalued option needs to be enabled too in order to get all the values. Duplicated values are discarded and the order of values is not guaranteed with this option.
selectRole.label=Select Role
selectRole.tooltip=Enter role in the textbox to the left, or click this button to browse and select the role you want.
tokenClaimName.label=Token Claim Name
@ -1537,4 +1539,4 @@ advanced-client-settings.tooltip=Expand this section to configure advanced setti
tls-client-certificate-bound-access-tokens=OAuth 2.0 Mutual TLS Certificate Bound Access Tokens Enabled
tls-client-certificate-bound-access-tokens.tooltip=This enables support for OAuth 2.0 Mutual TLS Certificate Bound Access Tokens, which means that keycloak bind an access token and a refresh token with a X.509 certificate of a token requesting client exchanged in mutual TLS between keycloak's Token Endpoint and this client. These tokens can be treated as Holder-of-Key tokens instead of bearer tokens.
subjectdn=Subject DN
subjectdn-tooltip=A regular expression for validating Subject DN in the Client Certificate. Use "(.*?)(?:$)" to match all kind of expressions.
subjectdn-tooltip=A regular expression for validating Subject DN in the Client Certificate. Use "(.*?)(?:$)" to match all kind of expressions.