View Javadoc

1   /*
2    * Licensed to the University Corporation for Advanced Internet Development, 
3    * Inc. (UCAID) under one or more contributor license agreements.  See the 
4    * NOTICE file distributed with this work for additional information regarding
5    * copyright ownership. The UCAID licenses this file to You under the Apache 
6    * License, Version 2.0 (the "License"); you may not use this file except in 
7    * compliance with the License.  You may obtain a copy of the License at
8    *
9    *    http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package edu.internet2.middleware.shibboleth.common.config.attribute.resolver.dataConnector;
19  
20  import java.util.HashMap;
21  import java.util.List;
22  import java.util.Map;
23  
24  import javax.xml.namespace.QName;
25  
26  import org.opensaml.xml.util.DatatypeHelper;
27  import org.opensaml.xml.util.XMLHelper;
28  import org.slf4j.Logger;
29  import org.slf4j.LoggerFactory;
30  import org.springframework.beans.factory.config.RuntimeBeanReference;
31  import org.springframework.beans.factory.support.BeanDefinitionBuilder;
32  import org.springframework.beans.factory.xml.ParserContext;
33  import org.w3c.dom.Element;
34  
35  import edu.internet2.middleware.shibboleth.common.attribute.resolver.provider.dataConnector.LdapPoolEmptyStrategy;
36  import edu.internet2.middleware.shibboleth.common.attribute.resolver.provider.dataConnector.LdapPoolVTStrategy;
37  import edu.internet2.middleware.shibboleth.common.attribute.resolver.provider.dataConnector.LdapDataConnector.AUTHENTICATION_TYPE;
38  import edu.internet2.middleware.shibboleth.common.config.SpringConfigurationUtils;
39  import edu.vt.middleware.ldap.SearchFilter;
40  import edu.vt.middleware.ldap.LdapConfig.SearchScope;
41  import edu.vt.middleware.ldap.handler.ConnectionHandler.ConnectionStrategy;
42  import edu.vt.middleware.ldap.pool.CompareLdapValidator;
43  import edu.vt.middleware.ldap.pool.LdapPoolConfig;
44  import edu.vt.middleware.ldap.pool.LdapValidator;
45  
46  /** Spring bean definition parser for configuring an LDAP data connector. */
47  public class LdapDataConnectorBeanDefinitionParser extends BaseDataConnectorBeanDefinitionParser {
48  
49      /** LDAP data connector type name. */
50      public static final QName TYPE_NAME = new QName(DataConnectorNamespaceHandler.NAMESPACE, "LDAPDirectory");
51  
52      /** Class logger. */
53      private final Logger log = LoggerFactory.getLogger(LdapDataConnectorBeanDefinitionParser.class);
54  
55      /** {@inheritDoc} */
56      protected Class<?> getBeanClass(Element element) {
57          return LdapDataConnectorFactoryBean.class;
58      }
59  
60      /** {@inheritDoc} */
61      protected void doParse(String pluginId, Element pluginConfig, Map<QName, List<Element>> pluginConfigChildren,
62              BeanDefinitionBuilder pluginBuilder, ParserContext parserContext) {
63          super.doParse(pluginId, pluginConfig, pluginConfigChildren, pluginBuilder, parserContext);
64  
65          processBasicConnectionConfig(pluginId, pluginConfig, pluginConfigChildren, pluginBuilder, parserContext);
66          processSecurityConfig(pluginId, pluginConfig, pluginConfigChildren, pluginBuilder, parserContext);
67          processResultHandlingConfig(pluginId, pluginConfig, pluginConfigChildren, pluginBuilder, parserContext);
68  
69          Map<String, String> ldapProperties = processLDAPProperties(pluginConfigChildren.get(new QName(
70                  DataConnectorNamespaceHandler.NAMESPACE, "LDAPProperty")));
71          if (ldapProperties != null) {
72              log.debug("Data connector {} LDAP properties: {}", pluginId, ldapProperties);
73              pluginBuilder.addPropertyValue("ldapProperties", ldapProperties);
74          }
75  
76          processPoolingConfig(pluginId, pluginConfig, pluginConfigChildren, pluginBuilder, parserContext);
77          
78          processCacheConfig(pluginId, pluginConfig, pluginBuilder);
79      }
80  
81      /**
82       * Process the basic LDAP connection configuration for the LDAP data connector.
83       * 
84       * @param pluginId ID of the LDAP plugin
85       * @param pluginConfig LDAP plugin configuration element
86       * @param pluginConfigChildren child elements of the plugin
87       * @param pluginBuilder plugin builder
88       * @param parserContext current parsing context
89       */
90      protected void processBasicConnectionConfig(String pluginId, Element pluginConfig,
91              Map<QName, List<Element>> pluginConfigChildren, BeanDefinitionBuilder pluginBuilder,
92              ParserContext parserContext) {
93  
94          String ldapURL = pluginConfig.getAttributeNS(null, "ldapURL");
95          log.debug("Data connector {} LDAP URL: {}", pluginId, ldapURL);
96          pluginBuilder.addPropertyValue("ldapUrl", ldapURL);
97  
98          ConnectionStrategy connStrategy = ConnectionStrategy.ACTIVE_PASSIVE;
99          if (pluginConfig.hasAttributeNS(null, "connectionStrategy")) {
100             connStrategy = ConnectionStrategy.valueOf(pluginConfig.getAttributeNS(null, "connectionStrategy"));
101         }
102         log.debug("Data connector {} connection strategy: {}", pluginId, connStrategy);
103         pluginBuilder.addPropertyValue("connectionStrategy", connStrategy);
104 
105         if (pluginConfig.hasAttributeNS(null, "baseDN")) {
106             String baseDN = pluginConfig.getAttributeNS(null, "baseDN");
107             log.debug("Data connector {} base DN: {}", pluginId, baseDN);
108             pluginBuilder.addPropertyValue("baseDN", baseDN);
109         }
110 
111         AUTHENTICATION_TYPE authnType = AUTHENTICATION_TYPE.SIMPLE;
112         if (pluginConfig.hasAttributeNS(null, "authenticationType")) {
113             authnType = AUTHENTICATION_TYPE.valueOf(pluginConfig.getAttributeNS(null, "authenticationType"));
114         }
115         log.debug("Data connector {} authentication type: {}", pluginId, authnType);
116         pluginBuilder.addPropertyValue("authenticationType", authnType);
117 
118         String principal = pluginConfig.getAttributeNS(null, "principal");
119         log.debug("Data connector {} principal: {}", pluginId, principal);
120         pluginBuilder.addPropertyValue("principal", principal);
121 
122         String credential = pluginConfig.getAttributeNS(null, "principalCredential");
123         pluginBuilder.addPropertyValue("principalCredential", credential);
124 
125         String templateEngineRef = pluginConfig.getAttributeNS(null, "templateEngine");
126         pluginBuilder.addPropertyReference("templateEngine", templateEngineRef);
127 
128         String filterTemplate = pluginConfigChildren.get(
129                 new QName(DataConnectorNamespaceHandler.NAMESPACE, "FilterTemplate")).get(0).getTextContent();
130         filterTemplate = DatatypeHelper.safeTrimOrNullString(filterTemplate);
131         log.debug("Data connector {} LDAP filter template: {}", pluginId, filterTemplate);
132         pluginBuilder.addPropertyValue("filterTemplate", filterTemplate);
133 
134         SearchScope searchScope = SearchScope.SUBTREE;
135         if (pluginConfig.hasAttributeNS(null, "searchScope")) {
136             searchScope = SearchScope.valueOf(pluginConfig.getAttributeNS(null, "searchScope"));
137         }
138         log.debug("Data connector {} search scope: {}", pluginId, searchScope);
139         pluginBuilder.addPropertyValue("searchScope", searchScope);
140 
141         QName returnAttributesName = new QName(DataConnectorNamespaceHandler.NAMESPACE, "ReturnAttributes");
142         if (pluginConfigChildren.containsKey(returnAttributesName)) {
143             List<String> returnAttributes = XMLHelper.getElementContentAsList(pluginConfigChildren.get(
144                     returnAttributesName).get(0));
145             log.debug("Data connector {} return attributes: {}", pluginId, returnAttributes);
146             pluginBuilder.addPropertyValue("returnAttributes", returnAttributes);
147         }
148     }
149 
150     /**
151      * Process the LDAP connection security configuration for the LDAP data connector.
152      * 
153      * @param pluginId ID of the LDAP plugin
154      * @param pluginConfig LDAP plugin configuration element
155      * @param pluginConfigChildren child elements of the plugin
156      * @param pluginBuilder plugin builder
157      * @param parserContext current parsing context
158      */
159     protected void processSecurityConfig(String pluginId, Element pluginConfig,
160             Map<QName, List<Element>> pluginConfigChildren, BeanDefinitionBuilder pluginBuilder,
161             ParserContext parserContext) {
162         RuntimeBeanReference trustCredential = processCredential(pluginConfigChildren.get(new QName(
163                 DataConnectorNamespaceHandler.NAMESPACE, "StartTLSTrustCredential")), parserContext);
164         if (trustCredential != null) {
165             log.debug("Data connector {} using provided SSL/TLS trust material", pluginId);
166             pluginBuilder.addPropertyValue("trustCredential", trustCredential);
167         }
168 
169         RuntimeBeanReference connectionCredential = processCredential(pluginConfigChildren.get(new QName(
170                 DataConnectorNamespaceHandler.NAMESPACE, "StartTLSAuthenticationCredential")), parserContext);
171         if (connectionCredential != null) {
172             log.debug("Data connector {} using provided SSL/TLS client authentication material", pluginId);
173             pluginBuilder.addPropertyValue("connectionCredential", connectionCredential);
174         }
175 
176         boolean useStartTLS = false;
177         if (pluginConfig.hasAttributeNS(null, "useStartTLS")) {
178             useStartTLS = XMLHelper.getAttributeValueAsBoolean(pluginConfig.getAttributeNodeNS(null, "useStartTLS"));
179         }
180         log.debug("Data connector {} use startTLS: {}", pluginId, useStartTLS);
181         pluginBuilder.addPropertyValue("useStartTLS", useStartTLS);
182     }
183 
184     /**
185      * Process the LDAP result handling configuration for the LDAP data connector.
186      * 
187      * @param pluginId ID of the LDAP plugin
188      * @param pluginConfig LDAP plugin configuration element
189      * @param pluginConfigChildren child elements of the plugin
190      * @param pluginBuilder plugin builder
191      * @param parserContext current parsing context
192      */
193     protected void processResultHandlingConfig(String pluginId, Element pluginConfig,
194             Map<QName, List<Element>> pluginConfigChildren, BeanDefinitionBuilder pluginBuilder,
195             ParserContext parserContext) {
196         int searchTimeLimit = 3000;
197         if (pluginConfig.hasAttributeNS(null, "searchTimeLimit")) {
198             searchTimeLimit = (int) SpringConfigurationUtils.parseDurationToMillis(
199                     "'searchTimeLimit' on data connector " + pluginId, pluginConfig.getAttributeNS(null,
200                             "searchTimeLimit"), 0);
201         }
202         log.debug("Data connector {} search timeout: {}ms", pluginId, searchTimeLimit);
203         pluginBuilder.addPropertyValue("searchTimeLimit", searchTimeLimit);
204 
205         int maxResultSize = 1;
206         if (pluginConfig.hasAttributeNS(null, "maxResultSize")) {
207             maxResultSize = Integer.parseInt(pluginConfig.getAttributeNS(null, "maxResultSize"));
208         }
209         log.debug("Data connector {} max search result size: {}", pluginId, maxResultSize);
210         pluginBuilder.addPropertyValue("maxResultSize", maxResultSize);
211 
212         boolean mergeResults = false;
213         if (pluginConfig.hasAttributeNS(null, "mergeResults")) {
214             mergeResults = XMLHelper.getAttributeValueAsBoolean(pluginConfig.getAttributeNodeNS(null, "mergeResults"));
215         }
216         log.debug("Data connector {} merge results: {}", pluginId, mergeResults);
217         pluginBuilder.addPropertyValue("mergeResults", mergeResults);
218 
219         boolean noResultsIsError = false;
220         if (pluginConfig.hasAttributeNS(null, "noResultIsError")) {
221             noResultsIsError = XMLHelper.getAttributeValueAsBoolean(pluginConfig.getAttributeNodeNS(null,
222                     "noResultIsError"));
223         }
224         log.debug("Data connector {} no results is error: {}", pluginId, noResultsIsError);
225         pluginBuilder.addPropertyValue("noResultsIsError", noResultsIsError);
226         
227         boolean lowercaseAttributeNames = false;
228         if (pluginConfig.hasAttributeNS(null, "lowercaseAttributeNames")) {
229             lowercaseAttributeNames = XMLHelper.getAttributeValueAsBoolean(pluginConfig.getAttributeNodeNS(null,
230                     "lowercaseAttributeNames"));
231         }
232         log.debug("Data connector {} will lower case attribute IDs: {}", pluginId, lowercaseAttributeNames);
233         pluginBuilder.addPropertyValue("lowercaseAttributeNames", lowercaseAttributeNames);
234     }
235 
236     /**
237      * Process the pooling configuration for the LDAP data connector.
238      * 
239      * @param pluginId ID of the LDAP plugin
240      * @param pluginConfig LDAP plugin configuration element
241      * @param pluginConfigChildren child elements of the plugin
242      * @param pluginBuilder plugin builder
243      * @param parserContext current parsing context
244      */
245     protected void processPoolingConfig(String pluginId, Element pluginConfig,
246             Map<QName, List<Element>> pluginConfigChildren, BeanDefinitionBuilder pluginBuilder,
247             ParserContext parserContext) {
248 
249         List<Element> poolConfigElems = pluginConfigChildren.get(new QName(DataConnectorNamespaceHandler.NAMESPACE,
250                 "ConnectionPool"));
251         if (poolConfigElems == null || poolConfigElems.size() == 0) {
252             log.debug("Data connector {} is pooling connections: {}", pluginId, false);
253             pluginBuilder.addPropertyValue("poolStrategy", new LdapPoolEmptyStrategy());
254             return;
255         }
256 
257         Element poolConfigElem = poolConfigElems.get(0);
258 
259         LdapPoolConfig ldapPoolConfig = new LdapPoolConfig();
260         LdapPoolVTStrategy ldapPoolStrategy = new LdapPoolVTStrategy();
261         ldapPoolStrategy.setLdapPoolConfig(ldapPoolConfig);
262         log.debug("Data connector {} is pooling connections: {}", pluginId, true);
263         pluginBuilder.addPropertyValue("poolStrategy", ldapPoolStrategy);
264 
265         int poolMinSize = 0;
266         if (pluginConfig.hasAttributeNS(null, "poolInitialSize")) {
267             poolMinSize = Integer.parseInt(pluginConfig.getAttributeNS(null, "poolInitialSize"));
268             log
269                     .warn("Data connector {} using deprecated attribute poolInitialSize on <DataConnector> use minPoolSize on child <PoolConfig> instead");
270         } else if (poolConfigElem != null && poolConfigElem.hasAttributeNS(null, "minPoolSize")) {
271             poolMinSize = Integer.parseInt(poolConfigElem.getAttributeNS(null, "minPoolSize"));
272         }
273         log.debug("Data connector {} pool minimum connections: {}", pluginId, poolMinSize);
274         ldapPoolConfig.setMinPoolSize(poolMinSize);
275 
276         int poolMaxSize = 3;
277         if (pluginConfig.hasAttributeNS(null, "poolMaxIdleSize")) {
278             poolMaxSize = Integer.parseInt(pluginConfig.getAttributeNS(null, "poolMaxIdleSize"));
279             log
280                     .warn("Data connector {} using deprecated attribute poolMaxIdleSize on <DataConnector> use maxPoolSize on child <PoolConfig> instead");
281         } else if (poolConfigElem != null && poolConfigElem.hasAttributeNS(null, "maxPoolSize")) {
282             poolMaxSize = Integer.parseInt(poolConfigElem.getAttributeNS(null, "maxPoolSize"));
283         }
284         log.debug("Data connector {} pool maximum connections: {}", pluginId, poolMaxSize);
285         ldapPoolConfig.setMaxPoolSize(poolMaxSize);
286 
287         boolean blockWhenEmpty = true;
288         if (poolConfigElem != null && poolConfigElem.hasAttributeNS(null, "blockWhenEmpty")) {
289             blockWhenEmpty = XMLHelper.getAttributeValueAsBoolean(poolConfigElem.getAttributeNodeNS(null,
290                     "blockWhenEmpty"));
291         }
292         log.debug("Data connector {} pool block when empty: {}", pluginId, blockWhenEmpty);
293         ldapPoolStrategy.setBlockWhenEmpty(blockWhenEmpty);
294 
295         boolean poolValidatePeriodically = false;
296         if (poolConfigElem != null && poolConfigElem.hasAttributeNS(null, "validatePeriodically")) {
297             poolValidatePeriodically = XMLHelper.getAttributeValueAsBoolean(poolConfigElem.getAttributeNodeNS(null,
298                     "validatePeriodically"));
299         }
300         log.debug("Data connector {} pool validate periodically: {}", pluginId, poolValidatePeriodically);
301         ldapPoolConfig.setValidatePeriodically(poolValidatePeriodically);
302 
303         int poolValidateTimerPeriod = 1800000;
304         if (poolConfigElem != null && poolConfigElem.hasAttributeNS(null, "validateTimerPeriod")) {
305             poolValidateTimerPeriod = (int) SpringConfigurationUtils.parseDurationToMillis("validateTimerPeriod",
306                     poolConfigElem.getAttributeNS(null, "validateTimerPeriod"), 0);
307         }
308         log.debug("Data connector {} pool validate timer period: {}ms", pluginId, poolValidateTimerPeriod);
309         ldapPoolConfig.setValidateTimerPeriod(poolValidateTimerPeriod);
310 
311         String poolValidateDn = "";
312         if (poolConfigElem != null && poolConfigElem.hasAttributeNS(null, "validateDN")) {
313             poolValidateDn = poolConfigElem.getAttributeNS(null, "validateDN");
314         }
315         String poolValidateFilter = "(objectClass=*)";
316         if (poolConfigElem != null && poolConfigElem.hasAttributeNS(null, "validateFilter")) {
317             poolValidateFilter = poolConfigElem.getAttributeNS(null, "validateFilter");
318         }
319         LdapValidator poolValidator = new CompareLdapValidator(poolValidateDn, new SearchFilter(poolValidateFilter));
320         log.debug("Data connector {} pool validation filter: {}", pluginId, poolValidateFilter);
321         pluginBuilder.addPropertyValue("poolValidator", poolValidator);
322 
323         int poolExpirationTime = 600000;
324         if (poolConfigElem != null && poolConfigElem.hasAttributeNS(null, "expirationTime")) {
325             poolExpirationTime = (int) SpringConfigurationUtils.parseDurationToMillis("expirationTime", poolConfigElem
326                     .getAttributeNS(null, "expirationTime"), 0);
327         }
328         log.debug("Data connector {} pool expiration time: {}ms", pluginId, poolExpirationTime);
329         ldapPoolConfig.setExpirationTime(poolExpirationTime);
330     }
331     
332     /**
333      * Processes the cache configuration directives.
334      * 
335      * @param pluginId ID of the plugin
336      * @param pluginConfig configuration element for the plugin
337      * @param pluginBuilder builder for the plugin
338      */
339     protected void processCacheConfig(String pluginId, Element pluginConfig, BeanDefinitionBuilder pluginBuilder) {
340         boolean cacheResults = false;
341         String cacheManagerId = "shibboleth.CacheManager";
342         long cacheElementTtl = 4 * 60 * 60 * 1000;
343         int maximumCachedElements = 500;
344 
345         List<Element> cacheConfigs = XMLHelper.getChildElementsByTagNameNS(pluginConfig,
346                 DataConnectorNamespaceHandler.NAMESPACE, "ResultCache");
347         if (cacheConfigs != null && !cacheConfigs.isEmpty()) {
348             Element cacheConfig = cacheConfigs.get(0);
349             
350             cacheResults = true;
351             
352             if (cacheConfig.hasAttributeNS(null, "cacheManagerRef")) {
353                 cacheManagerId = DatatypeHelper.safeTrim(cacheConfig.getAttributeNS(null, "cacheManagerRef"));
354             }
355 
356             if (cacheConfig.hasAttributeNS(null, "elementTimeToLive")) {
357                 cacheElementTtl = SpringConfigurationUtils.parseDurationToMillis("elementTimeToLive on data connector "
358                         + pluginId, cacheConfig.getAttributeNS(null, "elementTimeToLive"), 0);
359             }
360 
361             if (cacheConfig.hasAttributeNS(null, "maximumCachedElements")) {
362                 maximumCachedElements = Integer.parseInt(DatatypeHelper.safeTrim(cacheConfig.getAttributeNS(null,
363                         "maximumCachedElements")));
364             }
365         }
366 
367         if (pluginConfig.hasAttributeNS(null, "cacheResults")) {
368             log.warn("Data connection {}: use of 'cacheResults' attribute is deprecated.  Use <ResultCache> instead.",
369                     pluginId);
370             cacheResults = XMLHelper.getAttributeValueAsBoolean(pluginConfig.getAttributeNodeNS(null, "cacheResults"));
371         }
372 
373         if (cacheResults) {
374             log.debug("Data connector {} is caching results: {}", pluginId, cacheResults);
375             
376             pluginBuilder.addPropertyReference("cacheManager", cacheManagerId);
377             
378             log.debug("Data connector {} cache element time to live: {}ms", pluginId, cacheElementTtl);
379             pluginBuilder.addPropertyValue("cacheElementTimeToLive", cacheElementTtl);
380             
381             log.debug("Data connector {} maximum number of caches elements: {}", pluginId, maximumCachedElements);
382             pluginBuilder.addPropertyValue("maximumCachedElements", maximumCachedElements);
383         }
384 
385     }
386 
387     /**
388      * Processes the LDAP properties provided in the configuration.
389      * 
390      * @param propertyElems LDAP properties provided in the configuration
391      * 
392      * @return LDAP properties provided in the configuration
393      */
394     protected Map<String, String> processLDAPProperties(List<Element> propertyElems) {
395         if (propertyElems == null || propertyElems.size() == 0) {
396             return null;
397         }
398 
399         HashMap<String, String> properties = new HashMap<String, String>(5);
400 
401         String propName;
402         String propValue;
403         for (Element propertyElem : propertyElems) {
404             propName = DatatypeHelper.safeTrimOrNullString(propertyElem.getAttributeNS(null, "name"));
405             propValue = DatatypeHelper.safeTrimOrNullString(propertyElem.getAttributeNS(null, "value"));
406             properties.put(propName, propValue);
407         }
408 
409         return properties;
410     }
411 
412     /**
413      * Processes a credential element.
414      * 
415      * @param credentials list containing the element to process.
416      * @param parserContext current parser context
417      * 
418      * @return the bean definition for the credential
419      */
420     protected RuntimeBeanReference processCredential(List<Element> credentials, ParserContext parserContext) {
421         if (credentials == null) {
422             return null;
423         }
424 
425         Element credentialElem = credentials.get(0);
426         return SpringConfigurationUtils.parseCustomElement(credentialElem, parserContext);
427     }
428 }