KEYCLOAK-11770 add an hardcoded attribute mapper (#6396)
Signed-off-by: Cédric Couralet <cedric.couralet@insee.fr>
This commit is contained in:
parent
56aa14ffab
commit
bde94f2f08
6 changed files with 390 additions and 30 deletions
|
@ -17,12 +17,26 @@
|
|||
|
||||
package org.keycloak.storage.ldap;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.naming.directory.SearchControls;
|
||||
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.component.ComponentValidationException;
|
||||
import org.keycloak.models.LDAPConstants;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.reflection.Property;
|
||||
import org.keycloak.models.utils.reflection.PropertyCriteria;
|
||||
import org.keycloak.models.utils.reflection.PropertyQueries;
|
||||
import org.keycloak.storage.ldap.idm.model.LDAPDn;
|
||||
import org.keycloak.storage.ldap.idm.model.LDAPObject;
|
||||
import org.keycloak.storage.ldap.idm.query.Condition;
|
||||
|
@ -32,14 +46,6 @@ import org.keycloak.storage.ldap.idm.store.ldap.LDAPIdentityStore;
|
|||
import org.keycloak.storage.ldap.mappers.LDAPStorageMapper;
|
||||
import org.keycloak.storage.ldap.mappers.membership.MembershipType;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import javax.naming.directory.SearchControls;
|
||||
|
||||
/**
|
||||
* Allow to directly call some operations against LDAPIdentityStore.
|
||||
*
|
||||
|
@ -299,4 +305,34 @@ public class LDAPUtils {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a map of the user model properties from the getter methods
|
||||
* Map key are the attributes names in lower case
|
||||
*/
|
||||
public static Map<String, Property<Object>> getUserModelProperties(){
|
||||
|
||||
Map<String, Property<Object>> userModelProps = PropertyQueries.createQuery(UserModel.class)
|
||||
.addCriteria(new PropertyCriteria() {
|
||||
|
||||
@Override
|
||||
public boolean methodMatches(Method m) {
|
||||
if ((m.getName().startsWith("get") || m.getName().startsWith("is"))
|
||||
&& m.getParameterTypes().length > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}).getResultList();
|
||||
|
||||
// Convert to be keyed by lower-cased attribute names
|
||||
Map<String, Property<Object>> userModelProperties = new HashMap<>();
|
||||
for (Map.Entry<String, Property<Object>> entry : userModelProps.entrySet()) {
|
||||
userModelProperties.put(entry.getKey().toLowerCase(), entry.getValue());
|
||||
}
|
||||
|
||||
return userModelProperties;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* Copyright 2017 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.storage.ldap.mappers;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.UserModelDelegate;
|
||||
import org.keycloak.models.utils.reflection.Property;
|
||||
import org.keycloak.storage.ldap.LDAPStorageProvider;
|
||||
import org.keycloak.storage.ldap.LDAPUtils;
|
||||
import org.keycloak.storage.ldap.idm.model.LDAPObject;
|
||||
import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery;
|
||||
|
||||
|
||||
public class HardcodedAttributeMapper extends AbstractLDAPStorageMapper {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(HardcodedAttributeMapper.class);
|
||||
|
||||
public HardcodedAttributeMapper(ComponentModel mapperModel, LDAPStorageProvider ldapProvider) {
|
||||
super(mapperModel, ldapProvider);
|
||||
}
|
||||
|
||||
private static final Map<String, Property<Object>> userModelProperties = LDAPUtils.getUserModelProperties();
|
||||
|
||||
public static final String USER_MODEL_ATTRIBUTE = "user.model.attribute";
|
||||
public static final String ATTRIBUTE_VALUE = "attribute.value";
|
||||
|
||||
@Override
|
||||
public void onImportUserFromLDAP(LDAPObject ldapUser, UserModel user, RealmModel realm, boolean isCreate) {
|
||||
String userModelAttrName = getUserModelAttribute();
|
||||
|
||||
String attributeValue = getAttributeValue();
|
||||
Property<Object> userModelProperty = userModelProperties.get(userModelAttrName.toLowerCase());
|
||||
|
||||
if (userModelProperty != null) {
|
||||
setPropertyOnUserModel(userModelProperty, user, attributeValue);
|
||||
} else {
|
||||
user.setAttribute(userModelAttrName, Arrays.asList(attributeValue));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRegisterUserToLDAP(LDAPObject ldapUser, UserModel localUser, RealmModel realm) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel proxy(final LDAPObject ldapUser, UserModel delegate, RealmModel realm) {
|
||||
String userModelAttrName = getUserModelAttribute();
|
||||
String attributeValue = getAttributeValue();
|
||||
|
||||
delegate = new UserModelDelegate(delegate) {
|
||||
|
||||
@Override
|
||||
public List<String> getAttribute(String name) {
|
||||
if(userModelAttrName.equals(name)){
|
||||
return Arrays.asList(attributeValue);
|
||||
}
|
||||
return super.getAttribute(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmailVerified() {
|
||||
if(userModelAttrName.equals("emailVerified")){
|
||||
return Boolean.valueOf(attributeValue);
|
||||
}
|
||||
return super.isEmailVerified();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
if(userModelAttrName.equals("enabled")){
|
||||
return Boolean.valueOf(attributeValue);
|
||||
}
|
||||
return super.isEnabled();
|
||||
}
|
||||
|
||||
};
|
||||
return delegate;
|
||||
}
|
||||
|
||||
private String getUserModelAttribute() {
|
||||
return mapperModel.getConfig().getFirst(USER_MODEL_ATTRIBUTE);
|
||||
}
|
||||
|
||||
String getAttributeValue() {
|
||||
return mapperModel.getConfig().getFirst(ATTRIBUTE_VALUE);
|
||||
}
|
||||
|
||||
protected void setPropertyOnUserModel(Property<Object> userModelProperty, UserModel user, String ldapAttrValue) {
|
||||
if (ldapAttrValue == null) {
|
||||
userModelProperty.setValue(user, null);
|
||||
} else {
|
||||
Class<Object> clazz = userModelProperty.getJavaClass();
|
||||
|
||||
if (String.class.equals(clazz)) {
|
||||
userModelProperty.setValue(user, ldapAttrValue);
|
||||
} else if (Boolean.class.equals(clazz) || boolean.class.equals(clazz)) {
|
||||
Boolean boolVal = Boolean.valueOf(ldapAttrValue);
|
||||
userModelProperty.setValue(user, boolVal);
|
||||
} else {
|
||||
logger.warnf("Don't know how to set the property '%s' on user '%s' . Value of LDAP attribute is '%s' ", userModelProperty.getName(), user.getUsername(), ldapAttrValue.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeLDAPQuery(LDAPQuery query) {
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright 2017 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.storage.ldap.mappers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.component.ComponentValidationException;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.provider.ConfigurationValidationHelper;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
import org.keycloak.storage.ldap.LDAPStorageProvider;
|
||||
|
||||
public class HardcodedAttributeMapperFactory extends AbstractLDAPStorageMapperFactory {
|
||||
|
||||
public static final String PROVIDER_ID = "hardcoded-attribute-mapper";
|
||||
|
||||
|
||||
protected static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
|
||||
|
||||
static {
|
||||
ProviderConfigProperty attrName = createConfigProperty(HardcodedAttributeMapper.USER_MODEL_ATTRIBUTE, "User Model Attribute Name",
|
||||
"Name of the model attribute, which will be added when importing user from ldap",
|
||||
ProviderConfigProperty.STRING_TYPE, null);
|
||||
|
||||
ProviderConfigProperty attrValue = createConfigProperty(HardcodedAttributeMapper.ATTRIBUTE_VALUE, "Attribute Value",
|
||||
"Value of the model attribute, which will be added when importing user from ldap.",
|
||||
ProviderConfigProperty.STRING_TYPE, null);
|
||||
|
||||
configProperties.add(attrName);
|
||||
configProperties.add(attrValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpText() {
|
||||
return "This mapper will hardcode any model user attribute and some property (like emailVerified or enabled) when importing user from ldap.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return configProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel config) throws ComponentValidationException {
|
||||
ConfigurationValidationHelper.check(config)
|
||||
.checkRequired(HardcodedAttributeMapper.USER_MODEL_ATTRIBUTE, "Attribute Name")
|
||||
.checkRequired(HardcodedAttributeMapper.ATTRIBUTE_VALUE, "Attribute Value");
|
||||
if(config.get(HardcodedAttributeMapper.USER_MODEL_ATTRIBUTE).equalsIgnoreCase("username") || config.get(HardcodedAttributeMapper.USER_MODEL_ATTRIBUTE).equalsIgnoreCase("email")){
|
||||
throw new ComponentValidationException("Attribute Name cannot be set to username or email");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractLDAPStorageMapper createMapper(ComponentModel mapperModel, LDAPStorageProvider federationProvider) {
|
||||
return new HardcodedAttributeMapper(mapperModel, federationProvider);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -31,9 +31,11 @@ import org.keycloak.models.utils.reflection.PropertyCriteria;
|
|||
import org.keycloak.models.utils.reflection.PropertyQueries;
|
||||
import org.keycloak.storage.UserStorageProvider;
|
||||
import org.keycloak.storage.ldap.LDAPStorageProvider;
|
||||
import org.keycloak.storage.ldap.LDAPUtils;
|
||||
import org.keycloak.storage.ldap.idm.model.LDAPObject;
|
||||
import org.keycloak.storage.ldap.idm.query.Condition;
|
||||
import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery;
|
||||
import org.keycloak.storage.ldap.idm.store.ldap.LDAPUtil;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
|
@ -53,28 +55,7 @@ public class UserAttributeLDAPStorageMapper extends AbstractLDAPStorageMapper {
|
|||
|
||||
private static final Logger logger = Logger.getLogger(UserAttributeLDAPStorageMapper.class);
|
||||
|
||||
private static final Map<String, Property<Object>> userModelProperties;
|
||||
|
||||
static {
|
||||
Map<String, Property<Object>> userModelProps = PropertyQueries.createQuery(UserModel.class).addCriteria(new PropertyCriteria() {
|
||||
|
||||
@Override
|
||||
public boolean methodMatches(Method m) {
|
||||
if ((m.getName().startsWith("get") || m.getName().startsWith("is")) && m.getParameterTypes().length > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}).getResultList();
|
||||
|
||||
// Convert to be keyed by lower-cased attribute names
|
||||
userModelProperties = new HashMap<>();
|
||||
for (Map.Entry<String, Property<Object>> entry : userModelProps.entrySet()) {
|
||||
userModelProperties.put(entry.getKey().toLowerCase(), entry.getValue());
|
||||
}
|
||||
}
|
||||
private static final Map<String, Property<Object>> userModelProperties = LDAPUtils.getUserModelProperties();
|
||||
|
||||
public static final String USER_MODEL_ATTRIBUTE = "user.model.attribute";
|
||||
public static final String LDAP_ATTRIBUTE = "ldap.attribute";
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapperFactory
|
||||
org.keycloak.storage.ldap.mappers.HardcodedLDAPRoleStorageMapperFactory
|
||||
org.keycloak.storage.ldap.mappers.HardcodedLDAPGroupStorageMapperFactory
|
||||
org.keycloak.storage.ldap.mappers.HardcodedAttributeMapperFactory
|
||||
org.keycloak.storage.ldap.mappers.HardcodedLDAPAttributeMapperFactory
|
||||
org.keycloak.storage.ldap.mappers.membership.group.GroupLDAPStorageMapperFactory
|
||||
org.keycloak.storage.ldap.mappers.membership.role.RoleLDAPStorageMapperFactory
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* Copyright 2017 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.federation.ldap;
|
||||
|
||||
import static org.keycloak.testsuite.arquillian.DeploymentTargetModifier.AUTH_SERVER_CURRENT;
|
||||
|
||||
import org.hamcrest.BaseMatcher;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.jboss.arquillian.container.test.api.Deployment;
|
||||
import org.jboss.arquillian.container.test.api.TargetsContainer;
|
||||
import org.jboss.shrinkwrap.api.spec.WebArchive;
|
||||
import org.junit.Assert;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.internal.matchers.ThrowableCauseMatcher;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.runners.MethodSorters;
|
||||
import org.keycloak.admin.client.resource.UserResource;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.component.ComponentValidationException;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.storage.ldap.LDAPStorageProvider;
|
||||
import org.keycloak.storage.ldap.mappers.HardcodedAttributeMapper;
|
||||
import org.keycloak.storage.ldap.mappers.HardcodedAttributeMapperFactory;
|
||||
import org.keycloak.storage.ldap.mappers.LDAPStorageMapper;
|
||||
import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
|
||||
import org.keycloak.testsuite.runonserver.RunOnServerException;
|
||||
import org.keycloak.testsuite.util.LDAPRule;
|
||||
import org.keycloak.testsuite.util.LDAPTestUtils;
|
||||
|
||||
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
public class LDAPHardcodedAttributeTest extends AbstractLDAPTest {
|
||||
|
||||
@ClassRule
|
||||
public static LDAPRule ldapRule = new LDAPRule();
|
||||
|
||||
@Rule
|
||||
public ExpectedException exceptionRule = ExpectedException.none();
|
||||
|
||||
@Deployment
|
||||
@TargetsContainer(AUTH_SERVER_CURRENT)
|
||||
public static WebArchive deploy() {
|
||||
return RunOnServerDeployment.create(UserResource.class, AbstractLDAPTest.class).addPackages(true,
|
||||
"org.keycloak.testsuite", "org.keycloak.testsuite.federation.ldap");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LDAPRule getLDAPRule() {
|
||||
return ldapRule;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void afterImportTestRealm() {
|
||||
testingClient.server().run(session -> {
|
||||
LDAPTestContext ctx = LDAPTestContext.init(session);
|
||||
RealmModel appRealm = ctx.getRealm();
|
||||
|
||||
ComponentModel localeMapperModel = KeycloakModelUtils.createComponentModel("localeMapper", ctx.getLdapModel().getId(), HardcodedAttributeMapperFactory.PROVIDER_ID, LDAPStorageMapper.class.getName(),
|
||||
HardcodedAttributeMapper.USER_MODEL_ATTRIBUTE, "locale",
|
||||
HardcodedAttributeMapper.ATTRIBUTE_VALUE, "en");
|
||||
ComponentModel emailVerifiedMapperModel = KeycloakModelUtils.createComponentModel("emailVerifiedMapper", ctx.getLdapModel().getId(), HardcodedAttributeMapperFactory.PROVIDER_ID, LDAPStorageMapper.class.getName(),
|
||||
HardcodedAttributeMapper.USER_MODEL_ATTRIBUTE, "emailVerified",
|
||||
HardcodedAttributeMapper.ATTRIBUTE_VALUE, "true");
|
||||
|
||||
appRealm.addComponentModel(localeMapperModel);
|
||||
appRealm.addComponentModel(emailVerifiedMapperModel);
|
||||
|
||||
// Delete all LDAP users and add some new for testing
|
||||
LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ctx.getLdapModel());
|
||||
LDAPTestUtils.removeAllLDAPUsers(ldapFedProvider, appRealm);
|
||||
|
||||
LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnkeycloak", "John", "Doe",
|
||||
"john@email.org", null, "1234");
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testHarcodedMapper(){
|
||||
testingClient.server().run(session -> {
|
||||
LDAPTestContext ctx = LDAPTestContext.init(session);
|
||||
RealmModel appRealm = ctx.getRealm();
|
||||
|
||||
UserModel user = session.users().getUserByUsername("johnkeycloak", appRealm);
|
||||
Assert.assertNotNull(user);
|
||||
Assert.assertTrue(user.isEmailVerified());
|
||||
Assert.assertEquals("en", user.getFirstAttribute("locale"));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConfigInvalid(){
|
||||
exceptionRule.expect(RunOnServerException.class);
|
||||
exceptionRule.expectMessage("Attribute Name cannot be set to username or email");
|
||||
testingClient.server().run(session -> {
|
||||
LDAPTestContext ctx = LDAPTestContext.init(session);
|
||||
RealmModel appRealm = ctx.getRealm();
|
||||
|
||||
ComponentModel usernameMapperModel = KeycloakModelUtils.createComponentModel("usernameMapper", ctx.getLdapModel().getId(), HardcodedAttributeMapperFactory.PROVIDER_ID, LDAPStorageMapper.class.getName(),
|
||||
HardcodedAttributeMapper.USER_MODEL_ATTRIBUTE, "username",
|
||||
HardcodedAttributeMapper.ATTRIBUTE_VALUE, "username");
|
||||
appRealm.addComponentModel(usernameMapperModel);
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue