KEYCLOAK-4433 Added HardcodedLDAPAttributeMapper
This commit is contained in:
parent
49e8918462
commit
098d8e915d
6 changed files with 276 additions and 5 deletions
|
@ -487,7 +487,13 @@ public class LDAPOperationManager {
|
||||||
while (all.hasMore()) {
|
while (all.hasMore()) {
|
||||||
Attribute attribute = all.next();
|
Attribute attribute = all.next();
|
||||||
|
|
||||||
logger.tracef(" %s = %s", attribute.getID(), attribute.get());
|
String attrName = attribute.getID().toUpperCase();
|
||||||
|
Object attrVal = attribute.get();
|
||||||
|
if (attrName.contains("PASSWORD") || attrName.contains("UNICODEPWD")) {
|
||||||
|
attrVal = "********************";
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.tracef(" %s = %s", attribute.getID(), attrVal);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.tracef("]");
|
logger.tracef("]");
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
/*
|
||||||
|
* 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.storage.ldap.mappers;
|
||||||
|
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.component.ComponentModel;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.storage.ldap.LDAPStorageProvider;
|
||||||
|
import org.keycloak.storage.ldap.idm.model.LDAPObject;
|
||||||
|
import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class HardcodedLDAPAttributeMapper extends AbstractLDAPStorageMapper {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(HardcodedLDAPAttributeMapper.class);
|
||||||
|
|
||||||
|
public static final String LDAP_ATTRIBUTE_NAME = "ldap.attribute.name";
|
||||||
|
|
||||||
|
public static final String LDAP_ATTRIBUTE_VALUE = "ldap.attribute.value";
|
||||||
|
|
||||||
|
public static Pattern substitution = Pattern.compile("\\$\\{([^}]+)\\}");
|
||||||
|
|
||||||
|
public HardcodedLDAPAttributeMapper(ComponentModel mapperModel, LDAPStorageProvider ldapProvider) {
|
||||||
|
super(mapperModel, ldapProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRegisterUserToLDAP(LDAPObject ldapUser, UserModel localUser, RealmModel realm) {
|
||||||
|
String ldapAttrName = mapperModel.get(LDAP_ATTRIBUTE_NAME);
|
||||||
|
String ldapAttrValue = mapperModel.get(LDAP_ATTRIBUTE_VALUE);
|
||||||
|
|
||||||
|
String computedValue = computeAttributeValue(ldapAttrName, ldapAttrValue, ldapUser, localUser, realm);
|
||||||
|
|
||||||
|
ldapUser.setAttribute(ldapAttrName, Collections.singleton(computedValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected String computeAttributeValue(String ldapAttrName, String ldapAttrValue, LDAPObject ldapUser, UserModel localUser, RealmModel realm) {
|
||||||
|
Matcher m = substitution.matcher(ldapAttrValue);
|
||||||
|
StringBuffer sb = new StringBuffer();
|
||||||
|
|
||||||
|
while (m.find()) {
|
||||||
|
String token = m.group(1);
|
||||||
|
if (token.equals("RANDOM")) {
|
||||||
|
String randomVal = getRandomValue();
|
||||||
|
m.appendReplacement(sb, randomVal);
|
||||||
|
} else {
|
||||||
|
m.appendReplacement(sb, token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m.appendTail(sb);
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static final String CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVW1234567890";
|
||||||
|
|
||||||
|
// Generate random character of length 30. Allowed chars are from range 33-126
|
||||||
|
protected String getRandomValue() {
|
||||||
|
SecureRandom r = new SecureRandom();
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < 30; i++) {
|
||||||
|
char c = CHARS.charAt(r.nextInt(CHARS.length()));
|
||||||
|
sb.append(c);
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onImportUserFromLDAP(LDAPObject ldapUser, UserModel user, RealmModel realm, boolean isCreate) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserModel proxy(LDAPObject ldapUser, UserModel delegate, RealmModel realm) {
|
||||||
|
return delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeLDAPQuery(LDAPQuery query) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
/*
|
||||||
|
* 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.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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class HardcodedLDAPAttributeMapperFactory extends AbstractLDAPStorageMapperFactory {
|
||||||
|
|
||||||
|
public static final String PROVIDER_ID = "hardcoded-ldap-attribute-mapper";
|
||||||
|
|
||||||
|
protected static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
ProviderConfigProperty attrName = createConfigProperty(HardcodedLDAPAttributeMapper.LDAP_ATTRIBUTE_NAME, "LDAP Attribute Name",
|
||||||
|
"Name of the LDAP attribute, which will be added to the new user during registration",
|
||||||
|
ProviderConfigProperty.STRING_TYPE, null);
|
||||||
|
|
||||||
|
ProviderConfigProperty attrValue = createConfigProperty(HardcodedLDAPAttributeMapper.LDAP_ATTRIBUTE_VALUE, "LDAP Attribute Value",
|
||||||
|
"Value of the LDAP attribute, which will be added to the new user during registration. You can either hardcode any value like 'foo' but you can also use some special tokens. "
|
||||||
|
+ "Only supported token right now is '${RANDOM}' , which will be replaced with some randomly generated String.",
|
||||||
|
ProviderConfigProperty.STRING_TYPE, null);
|
||||||
|
|
||||||
|
configProperties.add(attrName);
|
||||||
|
configProperties.add(attrValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHelpText() {
|
||||||
|
return "This mapper is supported just if syncRegistrations is enabled. When new user is registered in Keycloak, he will be written to the LDAP with the hardcoded value of some specified attribute.";
|
||||||
|
}
|
||||||
|
|
||||||
|
@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(HardcodedLDAPAttributeMapper.LDAP_ATTRIBUTE_NAME, "LDAP Attribute Name")
|
||||||
|
.checkRequired(HardcodedLDAPAttributeMapper.LDAP_ATTRIBUTE_VALUE, "LDAP Attribute Value");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected AbstractLDAPStorageMapper createMapper(ComponentModel mapperModel, LDAPStorageProvider federationProvider) {
|
||||||
|
return new HardcodedLDAPAttributeMapper(mapperModel, federationProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapperFactory
|
org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapperFactory
|
||||||
org.keycloak.storage.ldap.mappers.HardcodedLDAPRoleStorageMapperFactory
|
org.keycloak.storage.ldap.mappers.HardcodedLDAPRoleStorageMapperFactory
|
||||||
|
org.keycloak.storage.ldap.mappers.HardcodedLDAPAttributeMapperFactory
|
||||||
org.keycloak.storage.ldap.mappers.membership.group.GroupLDAPStorageMapperFactory
|
org.keycloak.storage.ldap.mappers.membership.group.GroupLDAPStorageMapperFactory
|
||||||
org.keycloak.storage.ldap.mappers.membership.role.RoleLDAPStorageMapperFactory
|
org.keycloak.storage.ldap.mappers.membership.role.RoleLDAPStorageMapperFactory
|
||||||
org.keycloak.storage.ldap.mappers.msad.MSADUserAccountControlStorageMapperFactory
|
org.keycloak.storage.ldap.mappers.msad.MSADUserAccountControlStorageMapperFactory
|
||||||
|
|
|
@ -19,9 +19,7 @@ package org.keycloak.testsuite.federation.storage.ldap;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Assume;
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.BeforeClass;
|
|
||||||
import org.junit.ClassRule;
|
import org.junit.ClassRule;
|
||||||
import org.junit.FixMethodOrder;
|
import org.junit.FixMethodOrder;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
|
@ -31,7 +29,6 @@ import org.junit.rules.TestRule;
|
||||||
import org.junit.runners.MethodSorters;
|
import org.junit.runners.MethodSorters;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.admin.client.Keycloak;
|
import org.keycloak.admin.client.Keycloak;
|
||||||
import org.keycloak.common.Profile;
|
|
||||||
import org.keycloak.common.util.MultivaluedHashMap;
|
import org.keycloak.common.util.MultivaluedHashMap;
|
||||||
import org.keycloak.component.ComponentModel;
|
import org.keycloak.component.ComponentModel;
|
||||||
import org.keycloak.credential.CredentialModel;
|
import org.keycloak.credential.CredentialModel;
|
||||||
|
@ -55,6 +52,8 @@ import org.keycloak.representations.AccessToken;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
import org.keycloak.services.managers.RealmManager;
|
||||||
import org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapper;
|
import org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapper;
|
||||||
import org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapperFactory;
|
import org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapperFactory;
|
||||||
|
import org.keycloak.storage.ldap.mappers.HardcodedLDAPAttributeMapper;
|
||||||
|
import org.keycloak.storage.ldap.mappers.HardcodedLDAPAttributeMapperFactory;
|
||||||
import org.keycloak.storage.ldap.mappers.HardcodedLDAPRoleStorageMapper;
|
import org.keycloak.storage.ldap.mappers.HardcodedLDAPRoleStorageMapper;
|
||||||
import org.keycloak.storage.ldap.mappers.HardcodedLDAPRoleStorageMapperFactory;
|
import org.keycloak.storage.ldap.mappers.HardcodedLDAPRoleStorageMapperFactory;
|
||||||
import org.keycloak.storage.ldap.mappers.LDAPStorageMapper;
|
import org.keycloak.storage.ldap.mappers.LDAPStorageMapper;
|
||||||
|
@ -689,6 +688,79 @@ public class LDAPProvidersIntegrationTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHardcodedAttributeMapperTest() throws Exception {
|
||||||
|
// Create hardcoded mapper for "description"
|
||||||
|
KeycloakSession session = keycloakRule.startSession();
|
||||||
|
ComponentModel hardcodedMapperModel = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
RealmModel appRealm = new RealmManager(session).getRealmByName("test");
|
||||||
|
|
||||||
|
hardcodedMapperModel = KeycloakModelUtils.createComponentModel("hardcodedAttr-description", ldapModel.getId(), HardcodedLDAPAttributeMapperFactory.PROVIDER_ID, LDAPStorageMapper.class.getName(),
|
||||||
|
HardcodedLDAPAttributeMapper.LDAP_ATTRIBUTE_NAME, "description",
|
||||||
|
HardcodedLDAPAttributeMapper.LDAP_ATTRIBUTE_VALUE, "some-${RANDOM}");
|
||||||
|
appRealm.addComponentModel(hardcodedMapperModel);
|
||||||
|
} finally {
|
||||||
|
keycloakRule.stopSession(session, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register new user
|
||||||
|
loginPage.open();
|
||||||
|
loginPage.clickRegister();
|
||||||
|
registerPage.assertCurrent();
|
||||||
|
|
||||||
|
registerPage.register("firstName", "lastName", "email34@check.cz", "register123", "Password1", "Password1");
|
||||||
|
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
session = keycloakRule.startSession();
|
||||||
|
ComponentModel userAttrMapper = null;
|
||||||
|
try {
|
||||||
|
RealmModel appRealm = new RealmManager(session).getRealmByName("test");
|
||||||
|
|
||||||
|
// See that user don't yet have any description
|
||||||
|
UserModel user = LDAPTestUtils.assertUserImported(session.users(), appRealm, "register123", "firstName", "lastName", "email34@check.cz", null);
|
||||||
|
Assert.assertNull(user.getFirstAttribute("desc"));
|
||||||
|
Assert.assertNull(user.getFirstAttribute("description"));
|
||||||
|
|
||||||
|
// Remove hardcoded mapper for "description" and create regular userAttribute mapper for description
|
||||||
|
appRealm.removeComponent(hardcodedMapperModel);
|
||||||
|
|
||||||
|
userAttrMapper = LDAPTestUtils.addUserAttributeMapper(appRealm, ldapModel, "desc-attribute-mapper", "desc", "description");
|
||||||
|
userAttrMapper.put(UserAttributeLDAPStorageMapper.ALWAYS_READ_VALUE_FROM_LDAP, "true");
|
||||||
|
appRealm.updateComponent(userAttrMapper);
|
||||||
|
} finally {
|
||||||
|
keycloakRule.stopSession(session, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Check that user has description on him now
|
||||||
|
session = keycloakRule.startSession();
|
||||||
|
try {
|
||||||
|
RealmModel appRealm = new RealmManager(session).getRealmByName("test");
|
||||||
|
|
||||||
|
session.userCache().evict(appRealm, session.users().getUserByUsername("register123", appRealm));
|
||||||
|
|
||||||
|
// See that user don't yet have any description
|
||||||
|
UserModel user = session.users().getUserByUsername("register123", appRealm);
|
||||||
|
Assert.assertNull(user.getFirstAttribute("description"));
|
||||||
|
Assert.assertNotNull(user.getFirstAttribute("desc"));
|
||||||
|
String desc = user.getFirstAttribute("desc");
|
||||||
|
Assert.assertTrue(desc.startsWith("some-"));
|
||||||
|
Assert.assertEquals(35, desc.length());
|
||||||
|
|
||||||
|
// Remove mapper for "description"
|
||||||
|
appRealm.removeComponent(userAttrMapper);
|
||||||
|
} finally {
|
||||||
|
keycloakRule.stopSession(session, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testHardcodedRoleMapper() {
|
public void testHardcodedRoleMapper() {
|
||||||
KeycloakSession session = keycloakRule.startSession();
|
KeycloakSession session = keycloakRule.startSession();
|
||||||
|
|
|
@ -141,13 +141,14 @@ public class LDAPTestUtils {
|
||||||
return (LDAPStorageProvider)keycloakSession.getProvider(UserStorageProvider.class, ldapFedModel);
|
return (LDAPStorageProvider)keycloakSession.getProvider(UserStorageProvider.class, ldapFedModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void assertUserImported(UserProvider userProvider, RealmModel realm, String username, String expectedFirstName, String expectedLastName, String expectedEmail, String expectedPostalCode) {
|
public static UserModel assertUserImported(UserProvider userProvider, RealmModel realm, String username, String expectedFirstName, String expectedLastName, String expectedEmail, String expectedPostalCode) {
|
||||||
UserModel user = userProvider.getUserByUsername(username, realm);
|
UserModel user = userProvider.getUserByUsername(username, realm);
|
||||||
Assert.assertNotNull(user);
|
Assert.assertNotNull(user);
|
||||||
Assert.assertEquals(expectedFirstName, user.getFirstName());
|
Assert.assertEquals(expectedFirstName, user.getFirstName());
|
||||||
Assert.assertEquals(expectedLastName, user.getLastName());
|
Assert.assertEquals(expectedLastName, user.getLastName());
|
||||||
Assert.assertEquals(expectedEmail, user.getEmail());
|
Assert.assertEquals(expectedEmail, user.getEmail());
|
||||||
Assert.assertEquals(expectedPostalCode, user.getFirstAttribute("postal_code"));
|
Assert.assertEquals(expectedPostalCode, user.getFirstAttribute("postal_code"));
|
||||||
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void assertLoaded(UserModel user, String username, String expectedFirstName, String expectedLastName, String expectedEmail, String expectedPostalCode) {
|
public static void assertLoaded(UserModel user, String username, String expectedFirstName, String expectedLastName, String expectedEmail, String expectedPostalCode) {
|
||||||
|
|
Loading…
Reference in a new issue