View Javadoc

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