
com.nimbusds.openid.connect.provider.userinfo.ldap.LDAPConnector Maven / Gradle / Ivy
package com.nimbusds.openid.connect.provider.userinfo.ldap;
import java.io.File;
import java.nio.charset.Charset;
import java.util.*;
import org.apache.log4j.Logger;
import org.apache.commons.io.FileUtils;
import net.minidev.json.JSONObject;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPConnectionPool;
import com.unboundid.ldap.sdk.SearchResult;
import com.nimbusds.langtag.LangTag;
import com.nimbusds.common.ldap.AttributeMapper;
import com.nimbusds.common.ldap.LDAPConnectionPoolFactory;
import com.nimbusds.oauth2.sdk.id.Subject;
import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
import com.nimbusds.openid.connect.sdk.claims.UserInfo;
import com.nimbusds.openid.connect.provider.userinfo.spi.ClaimUtils;
import com.nimbusds.openid.connect.provider.userinfo.spi.UserInfoProvider;
/**
* LDAP connector for retrieving OpenID Connect UserInfo claims.
*
* @author Vladimir Dzhuvinov
*/
public class LDAPConnector implements UserInfoProvider {
/**
* The LDAP connector configuration.
*/
private Configuration config;
/**
* The supported UserInfo claims map. Allows for mapping of complex top
* level claims, such as "address" to their sub-claims, while simple
* claims map one-to-one.
*/
private Map> claimsMap;
/**
* The UserInfo attribute mapper.
*/
private AttributeMapper attributeMapper;
/**
* The LDAP connection pool.
*/
private LDAPConnectionPool ldapConnPool;
/**
* The logger.
*/
private final Logger log = Logger.getLogger(LDAPConnector.class);
/**
* Creates a new LDAP connector for retrieving OpenID Connect UserInfo
* claims. It must be {@link #init(java.util.Properties) initialised}
* before it can be used.
*/
public LDAPConnector() {
}
@Override
public void init(final Properties props)
throws Exception {
// Load and parse the LDAP connector configuration
config = new Configuration(props);
config.log();
// Parse the LDAP / JSON attribute map
String jsonText;
try {
jsonText = FileUtils.readFileToString(new File(config.directory.attributeMap), Charset.forName("UTF-8"));
} catch (Exception e) {
throw new Exception("Couldn't open LDAP attribute map file: " + e.getMessage(), e);
}
Map attrMap;
try {
attrMap = JSONObjectUtils.parseJSONObject(jsonText);
} catch (Exception e) {
throw new Exception("Couldn't parse LDAP attribute map: " + e.getMessage(), e);
}
// Derive the supported UserInfo claims map
claimsMap = new HashMap>();
for (String key: attrMap.keySet()) {
String[] parts = key.split("\\.", 2);
List subClaims = claimsMap.get(parts[0]);
if (subClaims == null)
subClaims = new LinkedList();
subClaims.add(key);
claimsMap.put(parts[0], subClaims);
}
attributeMapper = new AttributeMapper(attrMap);
if (attributeMapper.getLDAPAttributeName("sub") == null)
throw new Exception("Missing LDAP attribute mapping for \"sub\"");
LDAPConnectionPoolFactory factory = new LDAPConnectionPoolFactory(config.server,
config.customTrustStore,
config.customKeyStore,
config.directory.user);
try {
ldapConnPool = factory.createLDAPConnectionPool();
} catch (Exception e) {
// java.security.KeyStoreException
// java.security.GeneralSecurityException
// com.unboundid.ldap.sdk.LDAPException
throw new Exception("Couldn't create LDAP connection pool: " + e.getMessage(), e);
}
}
@Override
public Set supportedClaims() {
return Collections.unmodifiableSet(claimsMap.keySet());
}
/**
* Resolves the individual requested claims from the specified
* requested claims and preferred locales.
*
* @param claims The requested claims. May contain optional
* language tags. Must not be {@code null}.
* @param claimsLocales The preferred locales, {@code null} if not
* specified.
*
* @return The resolved individual requested claims.
*/
protected List resolveRequestedClaims(final Set claims, final List claimsLocales) {
// Use set to ensure no duplicates get into the collection
Set individualClaims = new HashSet();
for (String claim: claims) {
// Check if the claim is supported and if any sub-claims
// are associated with it (e.g. for UserInfo address)
List claimsList = claimsMap.get(claim);
if (claimsList == null)
continue; // claim not supported
individualClaims.addAll(claimsList);
}
// Apply the preferred language tags if any
individualClaims = ClaimUtils.applyLangTags(individualClaims, claimsLocales);
return new ArrayList(individualClaims);
}
@Override
public UserInfo getUserInfo(final Subject subject, final Set claims, final List claimsLocales)
throws Exception {
// Compose search filter
String filter = config.directory.filter.apply(subject.getValue());
// Resolve the individual requested claims
List claimsToRequest = resolveRequestedClaims(claims, claimsLocales);
// Map OIDC claim names to LDAP attribute names
List ldapAttrs = attributeMapper.getLDAPAttributeNames(claimsToRequest);
// Do LDAP search
SearchResult searchResult;
try {
searchResult = ldapConnPool.search(config.directory.baseDN.toString(),
config.directory.scope,
filter,
ldapAttrs.toArray(new String[0]));
} catch (Exception e) {
// LDAPException
throw new Exception("Couldn't get UserInfo for subject \"" + subject + "\": " + e.getMessage(), e);
}
// Get matches count
final int entryCount = searchResult.getEntryCount();
log.debug("Found " + entryCount + " entries for subject \"" + subject + "\"");
// Nothing found?
if (entryCount == 0)
return null; // Nothing found
// More than one entry?
if (entryCount > 1)
throw new Exception("Found " + entryCount + " entries for subject \"" + subject + "\"");
// Process user entry
Entry entry = searchResult.getSearchEntries().get(0);
Map entryObject = attributeMapper.transform(entry);
entryObject.put("sub", subject.getValue()); // append sub
try {
return new UserInfo(new JSONObject(entryObject));
} catch (IllegalArgumentException e) {
throw new Exception("Couldn't create UserInfo object: " + e.getMessage(), e);
}
}
@Override
public void shutdown()
throws Exception {
ldapConnPool.close();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy