package org.keycloak.federation.ldap;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import org.jboss.logging.Logger;
import org.keycloak.federation.ldap.idm.model.LDAPUser;
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStoreConfiguration;
import org.keycloak.federation.ldap.idm.store.ldap.LDAPMappingConfiguration;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.UserFederationProviderModel;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class LDAPIdentityStoreRegistry {
private static final Logger logger = Logger.getLogger(LDAPIdentityStoreRegistry.class);
private Map<String, LDAPIdentityStoreContext> ldapStores = new ConcurrentHashMap<String, LDAPIdentityStoreContext>();
public LDAPIdentityStore getLdapStore(UserFederationProviderModel model) {
LDAPIdentityStoreContext context = ldapStores.get(model.getId());
// Ldap config might have changed for the realm. In this case, we must re-initialize
Map<String, String> config = model.getConfig();
if (context == null || !config.equals(context.config)) {
logLDAPConfig(model.getId(), config);
LDAPIdentityStore store = createLdapIdentityStore(config);
context = new LDAPIdentityStoreContext(config, store);
ldapStores.put(model.getId(), context);
}
return context.store;
}
// Don't log LDAP password
private void logLDAPConfig(String fedProviderId, Map<String, String> ldapConfig) {
Map<String, String> copy = new HashMap<String, String>(ldapConfig);
copy.remove(LDAPConstants.BIND_CREDENTIAL);
logger.infof("Creating new LDAP based partition manager for the Federation provider: " + fedProviderId + ", LDAP Configuration: " + copy);
}
/**
* @param ldapConfig from realm
* @return PartitionManager instance based on LDAP store
*/
public static LDAPIdentityStore createLdapIdentityStore(Map<String,String> ldapConfig) {
Properties connectionProps = new Properties();
if (ldapConfig.containsKey(LDAPConstants.CONNECTION_POOLING)) {
connectionProps.put("com.sun.jndi.ldap.connect.pool", ldapConfig.get(LDAPConstants.CONNECTION_POOLING));
}
checkSystemProperty("com.sun.jndi.ldap.connect.pool.authentication", "none simple");
checkSystemProperty("com.sun.jndi.ldap.connect.pool.initsize", "1");
checkSystemProperty("com.sun.jndi.ldap.connect.pool.maxsize", "1000");
checkSystemProperty("com.sun.jndi.ldap.connect.pool.prefsize", "5");
checkSystemProperty("com.sun.jndi.ldap.connect.pool.timeout", "300000");
checkSystemProperty("com.sun.jndi.ldap.connect.pool.protocol", "plain");
checkSystemProperty("com.sun.jndi.ldap.connect.pool.debug", "off");
String vendor = ldapConfig.get(LDAPConstants.VENDOR);
boolean activeDirectory = vendor != null && vendor.equals(LDAPConstants.VENDOR_ACTIVE_DIRECTORY);
String ldapLoginNameMapping = ldapConfig.get(LDAPConstants.USERNAME_LDAP_ATTRIBUTE);
if (ldapLoginNameMapping == null) {
ldapLoginNameMapping = activeDirectory ? LDAPConstants.CN : LDAPConstants.UID;
}
String ldapFirstNameMapping = activeDirectory ? "givenName" : LDAPConstants.CN;
String createTimestampMapping = activeDirectory ? "whenCreated" : LDAPConstants.CREATE_TIMESTAMP;
String modifyTimestampMapping = activeDirectory ? "whenChanged" : LDAPConstants.MODIFY_TIMESTAMP;
String[] userObjectClasses = getUserObjectClasses(ldapConfig);
boolean pagination = ldapConfig.containsKey(LDAPConstants.PAGINATION) ? Boolean.parseBoolean(ldapConfig.get(LDAPConstants.PAGINATION)) : false;
boolean userAccountControlsAfterPasswordUpdate = ldapConfig.containsKey(LDAPConstants.USER_ACCOUNT_CONTROLS_AFTER_PASSWORD_UPDATE) ?
Boolean.parseBoolean(ldapConfig.get(LDAPConstants.USER_ACCOUNT_CONTROLS_AFTER_PASSWORD_UPDATE)) : false;
// Differences of unique attribute among various vendors
String uniqueIdentifierAttributeName = LDAPConstants.ENTRY_UUID;
if (vendor != null) {
switch (vendor) {
case LDAPConstants.VENDOR_RHDS:
uniqueIdentifierAttributeName = "nsuniqueid";
break;
case LDAPConstants.VENDOR_TIVOLI:
uniqueIdentifierAttributeName = "uniqueidentifier";
break;
case LDAPConstants.VENDOR_NOVELL_EDIRECTORY:
uniqueIdentifierAttributeName = "guid";
break;
case LDAPConstants.VENDOR_ACTIVE_DIRECTORY:
uniqueIdentifierAttributeName = LDAPConstants.OBJECT_GUID;
}
}
LDAPIdentityStoreConfiguration ldapStoreConfig = new LDAPIdentityStoreConfiguration()
.setConnectionProperties(connectionProps)
.setBaseDN(ldapConfig.get(LDAPConstants.BASE_DN))
.setBindDN(ldapConfig.get(LDAPConstants.BIND_DN))
.setBindCredential(ldapConfig.get(LDAPConstants.BIND_CREDENTIAL))
.setLdapURL(ldapConfig.get(LDAPConstants.CONNECTION_URL))
.setActiveDirectory(activeDirectory)
.setPagination(pagination)
.setUniqueIdentifierAttributeName(uniqueIdentifierAttributeName)
.setFactoryName("com.sun.jndi.ldap.LdapCtxFactory")
.setAuthType("simple")
.setUserAccountControlsAfterPasswordUpdate(userAccountControlsAfterPasswordUpdate);
LDAPMappingConfiguration ldapUserMappingConfig = ldapStoreConfig
.mappingConfig(LDAPUser.class)
.setBaseDN(ldapConfig.get(LDAPConstants.USER_DN_SUFFIX))
.setObjectClasses(new HashSet<String>(Arrays.asList(userObjectClasses)))
.setIdPropertyName("loginName")
.addAttributeMapping("loginName", ldapLoginNameMapping)
.addAttributeMapping("firstName", ldapFirstNameMapping)
.addAttributeMapping("lastName", LDAPConstants.SN)
.addAttributeMapping("email", LDAPConstants.EMAIL)
.addReadOnlyAttributeMapping("createdDate", createTimestampMapping)
.addReadOnlyAttributeMapping("modifyDate", modifyTimestampMapping);
if (activeDirectory && ldapLoginNameMapping.equals("sAMAccountName")) {
ldapUserMappingConfig.setBindingPropertyName("fullName");
ldapUserMappingConfig.addAttributeMapping("fullName", LDAPConstants.CN);
logger.infof("Using 'cn' attribute for DN of user and 'sAMAccountName' for username");
}
return new LDAPIdentityStore(ldapStoreConfig);
}
private static void checkSystemProperty(String name, String defaultValue) {
if (System.getProperty(name) == null) {
System.setProperty(name, defaultValue);
}
}
// Parse array of strings like [ "inetOrgPerson", "organizationalPerson" ] from the string like: "inetOrgPerson, organizationalPerson"
private static String[] getUserObjectClasses(Map<String,String> ldapConfig) {
String objClassesCfg = ldapConfig.get(LDAPConstants.USER_OBJECT_CLASSES);
String objClassesStr = (objClassesCfg != null && objClassesCfg.length() > 0) ? objClassesCfg.trim() : "inetOrgPerson, organizationalPerson";
String[] objectClasses = objClassesStr.split(",");
// Trim them
String[] userObjectClasses = new String[objectClasses.length];
for (int i=0 ; i<objectClasses.length ; i++) {
userObjectClasses[i] = objectClasses[i].trim();
}
return userObjectClasses;
}
private class LDAPIdentityStoreContext {
private LDAPIdentityStoreContext(Map<String,String> config, LDAPIdentityStore store) {
this.config = config;
this.store = store;
}
private Map<String,String> config;
private LDAPIdentityStore store;
}
}