KEYCLOAK-4433 Added HardcodedLDAPAttributeMapper

This commit is contained in:
mposolda 2017-02-20 21:48:20 +01:00
parent 49e8918462
commit 098d8e915d
6 changed files with 276 additions and 5 deletions

View file

@ -487,7 +487,13 @@ public class LDAPOperationManager {
while (all.hasMore()) {
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("]");

View file

@ -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) {
}
}

View file

@ -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);
}
}

View file

@ -17,6 +17,7 @@
org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapperFactory
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.role.RoleLDAPStorageMapperFactory
org.keycloak.storage.ldap.mappers.msad.MSADUserAccountControlStorageMapperFactory

View file

@ -19,9 +19,7 @@ package org.keycloak.testsuite.federation.storage.ldap;
import org.jboss.logging.Logger;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.FixMethodOrder;
import org.junit.Rule;
@ -31,7 +29,6 @@ import org.junit.rules.TestRule;
import org.junit.runners.MethodSorters;
import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.common.Profile;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.component.ComponentModel;
import org.keycloak.credential.CredentialModel;
@ -55,6 +52,8 @@ import org.keycloak.representations.AccessToken;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapper;
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.HardcodedLDAPRoleStorageMapperFactory;
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
public void testHardcodedRoleMapper() {
KeycloakSession session = keycloakRule.startSession();

View file

@ -141,13 +141,14 @@ public class LDAPTestUtils {
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);
Assert.assertNotNull(user);
Assert.assertEquals(expectedFirstName, user.getFirstName());
Assert.assertEquals(expectedLastName, user.getLastName());
Assert.assertEquals(expectedEmail, user.getEmail());
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) {