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

com.att.research.xacml.std.pip.engines.ldap.LDAPEngine Maven / Gradle / Ivy

There is a newer version: 2.2.0
Show newest version
/*
 *
 *          Copyright (c) 2013,2019  AT&T Knowledge Ventures
 *                     SPDX-License-Identifier: MIT
 */

package com.att.research.xacml.std.pip.engines.ldap;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Properties;
import java.util.Set;

import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.att.research.xacml.api.Attribute;
import com.att.research.xacml.api.pip.PIPException;
import com.att.research.xacml.api.pip.PIPFinder;
import com.att.research.xacml.api.pip.PIPRequest;
import com.att.research.xacml.api.pip.PIPResponse;
import com.att.research.xacml.std.pip.StdMutablePIPResponse;
import com.att.research.xacml.std.pip.StdPIPResponse;
import com.att.research.xacml.std.pip.engines.StdConfigurableEngine;
import com.google.common.base.Splitter;
import com.google.common.cache.Cache;

/**
 * LDAPEngine extends {@link com.att.research.xacml.std.pip.engines.StdConfigurableEngine} to implement a generic PIP for accessing
 * data from and LDAP server, including a configurable cache to avoid repeat queries.
 * 
 * @author car
 * @version $Revision$
 */
public class LDAPEngine extends StdConfigurableEngine {
	public static final String PROP_RESOLVERS			= "resolvers";
	public static final String PROP_RESOLVER			= "resolver";
	public static final String PROP_LDAP_SCOPE			= "scope";
	
	private static final String LDAP_SCOPE_SUBTREE		= "subtree";
	private static final String LDAP_SCOPE_OBJECT		= "object";
	private static final String LDAP_SCOPE_ONELEVEL		= "onelevel";
	private static final String DEFAULT_CONTEXT_FACTORY	= "com.sun.jndi.ldap.LdapCtxFactory";
	private static final String DEFAULT_SCOPE			= LDAP_SCOPE_SUBTREE;

	private Logger logger								= LoggerFactory.getLogger(this.getClass());	
	private Hashtable ldapEnvironment	= new Hashtable();
	private List ldapResolvers 			= new ArrayList();
	private int ldapScope;
	
	/*
	 * In addition, we pull the following standard LDAP properties from the configuration
	 * 	Context.AUTHORITATIVE: boolean
	 *  Context.BATCHSIZE: integer
	 *  Context.DNSURL: String
	 *  Context.INITIAL_CONTEXT_FACTORY: String
	 *  Context.LANGUAGE: String
	 *  Context.OBJECT_FACTORIES: String
	 *  Context.PROVIDER_URL: String
	 *  Context.REFERRAL: String
	 *  Context.SECURITY_AUTHENTICATION: String
	 *  Context.SECURITY_CREDENTIALS: String
	 *  Context.SECURITY_PRINCIPAL: String
	 *  Context.SECURITY_PROTOCOL: String
	 *  Context.STATE_FACTORIES: String
	 *  Context.URL_PKG_PREFIXES: String
	 */

	public LDAPEngine() {
	}
	
	private boolean configureStringProperty(String propertyPrefix, String property, Properties properties, String defaultValue) {
		String propertyValue	= properties.getProperty(propertyPrefix + property, defaultValue);
		if (propertyValue != null) {
			this.ldapEnvironment.put(property, propertyValue);
			return true;
		} else {
			return false;
		}
	}
	
	private boolean configureIntegerProperty(String propertyPrefix, String property, Properties properties, Integer defaultValue) {
		String propertyValue	= properties.getProperty(propertyPrefix + property);
		if (propertyValue == null) {
			if (defaultValue != null) {
				this.ldapEnvironment.put(property, defaultValue);
				return true;
			} else {
				return false;
			}
		} else {
			try {
				this.ldapEnvironment.put(property, Integer.parseInt(propertyValue));
				return true;
			} catch (NumberFormatException ex) {
				this.logger.error("Invalid Integer '" + propertyValue + "' for '" + property + "' property");
				return false;
			}
		}
	}
	
	@Override
	public void configure(String id, Properties properties) throws PIPException {
		/*
		 * Handle the standard properties
		 */
		super.configure(id, properties);
		String propertyPrefix	= id + ".";
		
		/*
		 * Configure the LDAP environment: I think the only required property is the provider_url
		 */
		if (!this.configureStringProperty(propertyPrefix, Context.PROVIDER_URL, properties, null)) {
			throw new PIPException("Invalid configuration for " + this.getClass().getName() + ": No " + propertyPrefix + Context.PROVIDER_URL);			
		}
		this.configureStringProperty(propertyPrefix, Context.AUTHORITATIVE, properties, null);
		this.configureIntegerProperty(propertyPrefix, Context.BATCHSIZE, properties, null);
		this.configureStringProperty(propertyPrefix, Context.DNS_URL, properties, null);
		this.configureStringProperty(propertyPrefix, Context.INITIAL_CONTEXT_FACTORY, properties, DEFAULT_CONTEXT_FACTORY);
		this.configureStringProperty(propertyPrefix, Context.LANGUAGE, properties, null);
		this.configureStringProperty(propertyPrefix, Context.OBJECT_FACTORIES, properties, null);
		this.configureStringProperty(propertyPrefix, Context.REFERRAL, properties, null);
		this.configureStringProperty(propertyPrefix, Context.SECURITY_AUTHENTICATION, properties, null);
		this.configureStringProperty(propertyPrefix, Context.SECURITY_CREDENTIALS, properties, null);
		this.configureStringProperty(propertyPrefix, Context.SECURITY_PRINCIPAL, properties, null);
		this.configureStringProperty(propertyPrefix, Context.SECURITY_PROTOCOL, properties, null);
		this.configureStringProperty(propertyPrefix, Context.STATE_FACTORIES, properties, null);
		this.configureStringProperty(propertyPrefix, Context.URL_PKG_PREFIXES, properties, null);
		
		String ldapScopeValue	= properties.getProperty(propertyPrefix + PROP_LDAP_SCOPE, DEFAULT_SCOPE);
		if (LDAP_SCOPE_SUBTREE.equals(ldapScopeValue)) {
			this.ldapScope	= SearchControls.SUBTREE_SCOPE;
		} else if (LDAP_SCOPE_OBJECT.equals(ldapScopeValue)) {
			this.ldapScope	= SearchControls.OBJECT_SCOPE;
		} else if (LDAP_SCOPE_ONELEVEL.equals(ldapScopeValue)) {
			this.ldapScope	= SearchControls.ONELEVEL_SCOPE;
		} else {
			this.logger.warn("Invalid LDAP Scope value '" + ldapScopeValue + "'; using " + DEFAULT_SCOPE);
			this.ldapScope	= SearchControls.SUBTREE_SCOPE;
		}
		
		/*
		 * Get list of resolvers defined for this LDAP Engine
		 */
		String resolversList = properties.getProperty(propertyPrefix + PROP_RESOLVERS);
		if (resolversList == null || resolversList.isEmpty()) {
			throw new PIPException("Invalid configuration for " + this.getClass().getName() + ": No " + propertyPrefix + PROP_RESOLVERS);
		}

		/*
		 * Iterate the resolvers
		 */
		for (String resolver : Splitter.on(',').trimResults().omitEmptyStrings().split(resolversList)) {
			/*
			 * Get the LDAPResolver for this LDAPEngine
			 */
			String resolverClassName	= properties.getProperty(propertyPrefix + PROP_RESOLVER + "." + resolver + ".classname");
			if (resolverClassName == null) {
				throw new PIPException("Invalid configuration for " + this.getClass().getName() + ": No " + propertyPrefix + PROP_RESOLVER + "." + resolver + ".classname");
			}
			
			LDAPResolver ldapResolverNew	= null;
			try {
				Class classResolver	= Class.forName(resolverClassName);
				if (!LDAPResolver.class.isAssignableFrom(classResolver)) {
					this.logger.error("LDAPResolver class " + resolverClassName + " does not implement " + LDAPResolver.class.getCanonicalName());
					throw new PIPException("LDAPResolver class " + resolverClassName + " does not implement " + LDAPResolver.class.getCanonicalName());
				}
				ldapResolverNew	= LDAPResolver.class.cast(classResolver.newInstance());
			} catch (Exception ex) {
				this.logger.error("Exception instantiating LDAPResolver for class '" + resolverClassName + "': " + ex.getMessage(), ex);
				throw new PIPException("Exception instantiating LDAPResolver for class '" + resolverClassName + "'", ex);
			}
			assert(ldapResolverNew != null);
			ldapResolverNew.configure(propertyPrefix + PROP_RESOLVER + "." + resolver, properties, this.getIssuer());
			
			this.ldapResolvers.add(ldapResolverNew);
		}			
		
	}
	
	@Override
	public PIPResponse getAttributes(PIPRequest pipRequest, PIPFinder pipFinder) throws PIPException {
		/*
		 * Make sure we have at least one resolver.
		 */
		if (this.ldapResolvers.size() == 0) {
			throw new IllegalStateException(this.getClass().getCanonicalName() + " is not configured");
		}
		
		StdMutablePIPResponse mutablePIPResponse	= new StdMutablePIPResponse();
		for (LDAPResolver ldapResolver : this.ldapResolvers) {
			this.getAttributes(pipRequest, pipFinder, mutablePIPResponse, ldapResolver);
		}
		if (mutablePIPResponse.getAttributes().size() == 0) {
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("returning empty response");
			}
			return StdPIPResponse.PIP_RESPONSE_EMPTY;
		} else {
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("Returning " + mutablePIPResponse.getAttributes().size() + " attributes");
				this.logger.debug("Attributes: {}", mutablePIPResponse.getAttributes());
			}
			return new StdPIPResponse(mutablePIPResponse);
		}
	}
	
	public void getAttributes(PIPRequest pipRequest, PIPFinder pipFinder, StdMutablePIPResponse mutablePIPResponse, LDAPResolver ldapResolver) throws PIPException {
		/*
		 * Check with the resolver to get the base string
		 */
		String stringBase	= ldapResolver.getBase(this, pipRequest, pipFinder);
		if (stringBase == null) {
			this.logger.warn(this.getName() + " does not handle " + pipRequest.toString());
			return;
		}
		
		/*
		 * Get the filter string
		 */
		String stringFilter	= ldapResolver.getFilterString(this, pipRequest, pipFinder);
		
		/*
		 * Check the cache
		 */
		Cache cache = this.getCache();
		String cacheKey		= stringBase + "::" + (stringFilter == null ? "" : stringFilter);
		if (cache != null) {
			PIPResponse pipResponse	= cache.getIfPresent(cacheKey);
			if (pipResponse != null) {
				if (this.logger.isDebugEnabled()) {
					this.logger.debug("Returning cached response: " + pipResponse);
				}
				mutablePIPResponse.addAttributes(pipResponse.getAttributes());
				return;
			}
		}		
		/*
		 * Not in the cache, so set up the LDAP query session
		 */
		DirContext dirContext	= null;
		PIPResponse pipResponse = null;
		try {
			/*
			 * Create the DirContext
			 */
			dirContext	= new InitialDirContext(this.ldapEnvironment);
			
			/*
			 * Set up the search controls
			 */
			SearchControls searchControls	= new SearchControls();
			searchControls.setSearchScope(this.ldapScope);
			
			/*
			 * Do the search
			 */
			NamingEnumeration namingEnumeration	= dirContext.search(stringBase, stringFilter, searchControls);
			if (namingEnumeration != null && namingEnumeration.hasMore()) {
				while (namingEnumeration.hasMore()) {
					List listAttributes	= ldapResolver.decodeResult(namingEnumeration.next());
					if (listAttributes != null && listAttributes.size() > 0) {
						mutablePIPResponse.addAttributes(listAttributes);
					}
				}
			}
			/*
			 * Put in the cache
			 */
			if (cache != null) {
				cache.put(cacheKey, pipResponse);
			}
		} catch (NamingException ex) {
			this.logger.error("NamingException creating the DirContext: " + ex.getMessage(), ex);
		} finally {
			if (dirContext != null) {
				try {
					dirContext.close();
				} catch (Exception ex) {
					this.logger.warn("Exception closing DirContext: " + ex.getMessage(), ex);
				}
			}
		}
	}

	@Override
	public Collection attributesRequired() {
		Set requiredAttributes = new HashSet();
		for (LDAPResolver resolver : this.ldapResolvers) {
			resolver.attributesRequired(requiredAttributes);
		}
		return requiredAttributes;
	}

	@Override
	public Collection attributesProvided() {
		Set providedAttributes = new HashSet();
		for (LDAPResolver resolver : this.ldapResolvers) {
			resolver.attributesProvided(providedAttributes);
		}
		return providedAttributes;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy