All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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