diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPOperationManager.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPOperationManager.java
index 502fa75521..d3d8317255 100644
--- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPOperationManager.java
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPOperationManager.java
@@ -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("]");
diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/HardcodedLDAPAttributeMapper.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/HardcodedLDAPAttributeMapper.java
new file mode 100644
index 0000000000..f4f7e3d94c
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/HardcodedLDAPAttributeMapper.java
@@ -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 Marek Posolda
+ */
+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) {
+
+ }
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/HardcodedLDAPAttributeMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/HardcodedLDAPAttributeMapperFactory.java
new file mode 100644
index 0000000000..d8e8720750
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/HardcodedLDAPAttributeMapperFactory.java
@@ -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 Marek Posolda
+ */
+public class HardcodedLDAPAttributeMapperFactory extends AbstractLDAPStorageMapperFactory {
+
+ public static final String PROVIDER_ID = "hardcoded-ldap-attribute-mapper";
+
+ protected static final List configProperties = new ArrayList();
+
+ 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 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);
+ }
+
+
+}
diff --git a/federation/ldap/src/main/resources/META-INF/services/org.keycloak.storage.ldap.mappers.LDAPStorageMapperFactory b/federation/ldap/src/main/resources/META-INF/services/org.keycloak.storage.ldap.mappers.LDAPStorageMapperFactory
index 3781e87bf3..e43fdbe4ca 100644
--- a/federation/ldap/src/main/resources/META-INF/services/org.keycloak.storage.ldap.mappers.LDAPStorageMapperFactory
+++ b/federation/ldap/src/main/resources/META-INF/services/org.keycloak.storage.ldap.mappers.LDAPStorageMapperFactory
@@ -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
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPProvidersIntegrationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPProvidersIntegrationTest.java
index c148257580..0982d5d5e4 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPProvidersIntegrationTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPProvidersIntegrationTest.java
@@ -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();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPTestUtils.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPTestUtils.java
index 49d81e7443..f41a7bab74 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPTestUtils.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPTestUtils.java
@@ -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) {