Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.apache.openaz.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.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.openaz.xacml.api.Attribute; import org.apache.openaz.xacml.api.pip.PIPException; import org.apache.openaz.xacml.api.pip.PIPFinder; import org.apache.openaz.xacml.api.pip.PIPRequest; import org.apache.openaz.xacml.api.pip.PIPResponse; import org.apache.openaz.xacml.std.pip.StdMutablePIPResponse; import org.apache.openaz.xacml.std.pip.StdPIPResponse; import org.apache.openaz.xacml.std.pip.engines.StdConfigurableEngine; import com.google.common.base.Splitter; import com.google.common.cache.Cache; /** * LDAPEngine extends {@link org.apache.openaz.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. */ 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 Log logger = LogFactory.getLog(this.getClass()); private Hashtable<Object, Object> ldapEnvironment = new Hashtable<Object, Object>(); private List<LDAPResolver> ldapResolvers = new ArrayList<LDAPResolver>(); 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(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<String, PIPResponse> 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<SearchResult> namingEnumeration = dirContext.search(stringBase, stringFilter, searchControls); if (namingEnumeration != null && namingEnumeration.hasMore()) { while (namingEnumeration.hasMore()) { List<Attribute> 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<PIPRequest> attributesRequired() { Set<PIPRequest> requiredAttributes = new HashSet<PIPRequest>(); for (LDAPResolver resolver : this.ldapResolvers) { resolver.attributesRequired(requiredAttributes); } return requiredAttributes; } @Override public Collection<PIPRequest> attributesProvided() { Set<PIPRequest> providedAttributes = new HashSet<PIPRequest>(); for (LDAPResolver resolver : this.ldapResolvers) { resolver.attributesProvided(providedAttributes); } return providedAttributes; } }