001    /**
002     * ESUP-Portail Commons - Copyright (c) 2006 ESUP-Portail consortium
003     * http://sourcesup.cru.fr/projects/esup-commons
004     */
005    package org.esupportail.commons.services.ldap;
006    
007    import java.util.ArrayList;
008    import java.util.List;
009    import java.util.Map;
010    
011    import javax.naming.ServiceUnavailableException;
012    import javax.naming.directory.InvalidSearchFilterException;
013    
014    import net.sf.ldaptemplate.BadLdapGrammarException;
015    import net.sf.ldaptemplate.LdapTemplate;
016    import net.sf.ldaptemplate.UncategorizedLdapException;
017    import net.sf.ldaptemplate.support.DistinguishedName;
018    import net.sf.ldaptemplate.support.filter.AndFilter;
019    import net.sf.ldaptemplate.support.filter.EqualsFilter;
020    import net.sf.ldaptemplate.support.filter.Filter;
021    import net.sf.ldaptemplate.support.filter.WhitespaceWildcardsFilter;
022    
023    import org.esupportail.commons.exceptions.UserNotFoundException;
024    import org.esupportail.commons.services.exceptionHandling.ExceptionUtils;
025    import org.esupportail.commons.services.logging.Logger;
026    import org.esupportail.commons.services.logging.LoggerImpl;
027    import org.esupportail.commons.utils.Assert;
028    import org.springframework.beans.factory.InitializingBean;
029    import org.springframework.dao.DataAccessException;
030    import org.springframework.dao.DataRetrievalFailureException;
031    import org.springframework.util.CollectionUtils;
032    import org.springframework.util.StringUtils;
033    
034    /**
035     * An implementation of LdapService based on LdapTemplate.
036     * 
037     * See /properties/ldap/ldap-example.xml.
038     */
039    public class SimpleLdapServiceImpl extends AbstractLdapService implements InitializingBean {
040    
041            /**
042             * The ObjectClass attribute.
043             */
044            private static final String OBJECT_CLASS_ATTRIBUTE = "objectclass";
045            
046            /**
047             * The person value.
048             */
049            private static final String PERSON_VALUE = "person";
050            
051            /**
052             * The default search attribute.
053             */
054            private static final String DEFAULT_SEARCH_ATTRIBUTE = "cn";
055            
056            /**
057             * The default uid attribute.
058             */
059            private static final String DEFAULT_UID_ATTRIBUTE = "uid";
060            
061            /**
062             * The default display attributes.
063             */
064            private static final String [] DEFAULT_SEARCH_DISPLAYED_ATTRIBUTES =  { 
065                    "uid", "cn", "departmentNumber", "employeeType", 
066                    };
067            
068            /**
069             * A logger.
070             */
071            private final Logger logger = new LoggerImpl(getClass());
072    
073            /**
074             * A LdapTemplate instance, to perform LDAP operations.
075             */
076            private LdapTemplate ldapTemplate;
077    
078    
079            /**
080             * The name of the attribute in which user tokens will be searched for.
081             */
082            private String searchAttribute;
083    
084    
085            /**
086             * The name of the attirbute that contains the uid.
087             */
088            private String uidAttribute;
089    
090            /**
091             * The attributes that should be presented to choose one user among several.  
092             */
093            private List<String> searchDisplayedAttributes;
094            
095            /**
096             * The other attributes that should be retrieved (in addition to searchDisplayedAttributes).
097             */
098            private List<String> otherAttributes;
099            
100            /**
101             * The attributes mapper.
102             */
103            private LdapUserAttributesMapper attributesMapper;
104    
105            /**
106             * The tets filter.
107             */
108            private String testFilter;
109    
110            /**
111             * Bean constructor.
112             */
113            public SimpleLdapServiceImpl() {
114                    super();
115            }
116    
117            /**
118             * Set the default search attribute.
119             */
120            private void setDefaultSearchAttribute() {
121                    searchAttribute = DEFAULT_SEARCH_ATTRIBUTE;
122            }
123    
124            /**
125             * Set the default uid attribute.
126             */
127            private void setDefaultUidAttribute() {
128                    uidAttribute = DEFAULT_UID_ATTRIBUTE;
129            }
130    
131            /**
132             * Set the default search displayed attributes.
133             */
134            private void setDefaultSearchDisplayedAttributes() {
135                    searchDisplayedAttributes = new ArrayList<String>();
136                    for (String searchDisplayedAttribute : DEFAULT_SEARCH_DISPLAYED_ATTRIBUTES) {
137                            searchDisplayedAttributes.add(searchDisplayedAttribute);
138                    }
139            }
140    
141            /**
142             * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
143             */
144            public void afterPropertiesSet() {
145                    Assert.notNull(ldapTemplate, 
146                                    "property ldapTemplate of class " + getClass().getName() + " can not be null");
147                    if (!StringUtils.hasText(uidAttribute)) {
148                            logger.info(getClass() + ": no uid attribute set, '" 
149                                            + DEFAULT_UID_ATTRIBUTE + "' will be used");
150                            setDefaultUidAttribute();
151                    }
152                    if (!StringUtils.hasText(searchAttribute)) {
153                            logger.info(getClass() + ": no searchAttribute attribute set, '" 
154                                            + DEFAULT_SEARCH_ATTRIBUTE + "' will be used");
155                            setDefaultSearchAttribute();
156                    }
157                    if (CollectionUtils.isEmpty(searchDisplayedAttributes)) {
158                            logger.info(getClass() + ": no searchDisplayAttributes attribute set, '" 
159                                            + DEFAULT_SEARCH_DISPLAYED_ATTRIBUTES + "' will be used");
160                            setDefaultSearchDisplayedAttributes();
161                    }
162                    if (CollectionUtils.isEmpty(otherAttributes)) {
163                            logger.info(getClass() + ": no otherAttributes attribute set, " 
164                                            + "no other attribute will be retrived from the LDAP directory");
165                            otherAttributes = new ArrayList<String>();
166                    }
167                    if (testFilter == null) {
168                            logger.info(getClass() + ": no testFilter attribute set, target ldap-test will not work."); 
169                    }
170                    List<String> attributes = new ArrayList<String>();
171                    attributes.add(uidAttribute);
172                    attributes.addAll(searchDisplayedAttributes);
173                    attributes.addAll(otherAttributes);
174                    attributesMapper = new LdapUserAttributesMapper(uidAttribute, attributes);
175            }
176    
177            /**
178             * Wrap an exception into a LdapException.
179             * @param message
180             * @param filterExpr 
181             * @param e
182             * @return a LdapException
183             * @throws LdapException 
184             */
185            private LdapException wrapException(
186                            final String message, 
187                            final String filterExpr,
188                            final Exception e) throws LdapException {
189                    if (e instanceof DataRetrievalFailureException) {
190                            return new LdapConnectionException(message, e);
191                    }
192                    if (e instanceof BadLdapGrammarException) {
193                            if (e.getCause() instanceof InvalidSearchFilterException) {
194                                    return new LdapBadFilterException(
195                                                    message + ": bad filter [" + filterExpr + "]: " 
196                                                    + e.getCause().getMessage(), (Exception) e.getCause());
197                            }
198                    }
199                    if (e instanceof InvalidSearchFilterException) {
200                            return new LdapBadFilterException(message + ": " + e.getMessage(), e);
201                    }
202                    return new LdapMiscException(message + ": " + e.getMessage(), e);
203            }
204            
205            /**
206             * @param filter 
207             * @param retry true to retry once on some conditions
208             * @return the LDAP users that correspond to a filter.
209             * @throws LdapException 
210             */
211            @SuppressWarnings({ "cast", "unchecked" })
212            private List<LdapUser> getLdapUsersFromFilter(
213                            final Filter filter, 
214                            final boolean retry) throws LdapException {
215                    Exception ex = null;
216                    DistinguishedName dn = new DistinguishedName();
217                    AndFilter theFilter = new AndFilter();
218                    theFilter.and(new EqualsFilter(OBJECT_CLASS_ATTRIBUTE, PERSON_VALUE));
219                    theFilter.and(filter);
220                    try {
221                            return (List<LdapUser>) ldapTemplate.search(dn, theFilter.encode(), attributesMapper);
222                    } catch (UncategorizedLdapException e) {
223                            if (e.getCause() != null && e.getCause() instanceof ServiceUnavailableException && retry) {
224                                    ExceptionUtils.catchException(wrapException(
225                                                    "could not retrieve users from the LDAP directory", 
226                                                    filter.encode(), e));
227                                    return getLdapUsersFromFilter(filter, false);
228                            }
229                            ex = e;
230                    } catch (DataAccessException e) {
231                            ex = e;
232                    }
233                    throw wrapException("could not retrieve users from the LDAP directory", filter.encode(), ex);
234            }
235            
236            /**
237             * @param filter 
238             * @return the LDAP users that correspond to a filter.
239             * @throws LdapException 
240             */
241            @SuppressWarnings({ "cast", "unchecked" })
242            protected List<LdapUser> getLdapUsersFromFilter(final Filter filter) throws LdapException {
243                    return getLdapUsersFromFilter(filter, true);
244            }
245            
246            /**
247             * @see org.esupportail.commons.services.ldap.LdapService#getLdapUsersFromToken(java.lang.String)
248             */
249            public List<LdapUser> getLdapUsersFromToken(final String token) throws LdapException {
250                    Filter filter = new WhitespaceWildcardsFilter(searchAttribute, token);
251                    return getLdapUsersFromFilter(filter.encode());
252            }
253            
254            /**
255             * @see org.esupportail.commons.services.ldap.LdapService#getLdapUsersFromFilter(java.lang.String)
256             */
257            public List<LdapUser> getLdapUsersFromFilter(final String filterExpr) throws LdapException {
258                    return getLdapUsersFromFilter(new StringFilter(filterExpr));
259            }
260            
261            /**
262             * @see org.esupportail.commons.services.ldap.LdapService#testLdapFilter(java.lang.String)
263             */
264            public String testLdapFilter(final String filterExpr) throws LdapException {
265                    try {
266                            getLdapUsersFromFilter(new StringFilter(filterExpr));
267                            return null;
268                    } catch (LdapBadFilterException e) {
269                            return e.getMessage();
270                    }
271            }
272            
273            /**
274             * @see org.esupportail.commons.services.ldap.LdapService#getLdapUser(java.lang.String)
275             */
276            public LdapUser getLdapUser(final String id) throws LdapException, UserNotFoundException {
277                    List<LdapUser> ldapUsers = getLdapUsersFromFilter(new EqualsFilter(uidAttribute, id));
278                    if (ldapUsers.isEmpty()) {
279                            throw new UserNotFoundException("No LDAP entry found for user [" + id + "]");
280                    } else if (ldapUsers.size() > 1) {
281                            throw new UserNotFoundException("Too many LDAP entries found for user [" + id + "]");
282                    } else {
283                            return ldapUsers.get(0);
284                    }
285            }
286    
287            /**
288             * @see org.esupportail.commons.services.ldap.LdapService#userMatchesFilter(java.lang.String, java.lang.String)
289             */
290            public boolean userMatchesFilter(final String id, final String filterExpr) throws LdapException {
291                    AndFilter filter = new AndFilter();
292                    filter.and(new EqualsFilter(uidAttribute, id));
293                    filter.and(new StringFilter(filterExpr));
294                    return !(getLdapUsersFromFilter(filter).isEmpty());
295            }
296    
297            /**
298             * @see org.esupportail.commons.services.ldap.AbstractLdapService#supportsTest()
299             */
300            @Override
301            public boolean supportsTest() {
302                    return true;
303            }
304    
305            /**
306             * @see org.esupportail.commons.services.ldap.AbstractLdapService#test()
307             */
308            @Override
309            public void test() {
310                    if (testFilter == null) {
311                            logger.error("can not test the LDAP connection when property testFilter is not set, " 
312                                            + "edit configuration file ldap.xml.");
313                            return;
314                    }
315                    List<LdapUser> ldapUsers;
316                    try {
317                            ldapUsers = getLdapUsersFromFilter(testFilter);
318                    } catch (LdapBadFilterException e) {
319                            logger.info("bad LDAP filter [" + testFilter + "], edit configuration file ldap.xml: " 
320                                            + e.getCause().getMessage());
321                            return;
322                    } catch (LdapConnectionException e) {
323                            logger.info("could not connect to LDAP, edit configuration file ldap.xml: " 
324                                            + e.getCause().getMessage());
325                            return;
326                    } catch (LdapException e) {
327                            logger.info("LDAP error, edit configuration file ldap.xml: " + e.getCause().getMessage());
328                            return;
329                    }
330                    if (ldapUsers.isEmpty()) {
331                            logger.info("no user retrieved for filter [" + testFilter + "]");
332                    } else {
333                            logger.info(ldapUsers.size() + " user(s) retrieved for filter [" + testFilter + "]:");
334                            for (LdapUser ldapUser : ldapUsers) {
335                                    logger.info(" - " + ldapUser.getId() + ":");
336                                    Map<String, List<String>> attributes = ldapUser.getAttributes();
337                                    for (String attributeName : ldapUser.getAttributeNames()) {
338                                            String str = "   * " + attributeName + " = ";
339                                            List<String> attrs = attributes.get(attributeName);
340                                            if (attrs.size() == 1) {
341                                                    str += attrs.get(0);
342                                            } else {
343                                                    str += "{";
344                                                    String separator = "";
345                                                    for (String attr : attrs) {
346                                                            str += separator + attr;
347                                                            separator = ", ";
348                                                    }
349                                                    str += "}";
350                                            }
351                                            logger.info(str);
352                                    }
353                            }
354                    }
355            }
356    
357            /**
358             * @see org.esupportail.commons.services.ldap.LdapService#getSearchDisplayedAttributeNames()
359             */
360            public List<String> getSearchDisplayedAttributeNames() {
361                    return searchDisplayedAttributes;
362            }
363    
364            /**
365             * @param ldapTemplate the ldapTemplate to set
366             */
367            public void setLdapTemplate(final LdapTemplate ldapTemplate) {
368                    this.ldapTemplate = ldapTemplate;
369            }
370    
371            /**
372             * @param searchAttribute the searchAttribute to set
373             */
374            public void setSearchAttribute(final String searchAttribute) {
375                    this.searchAttribute = searchAttribute;
376            }
377    
378            /**
379             * @param uidAttribute the uidAttribute to set
380             */
381            public  void setUidAttribute(final String uidAttribute) {
382                    this.uidAttribute = uidAttribute;
383            }
384    
385            /**
386             * @param searchDisplayedAttributes the displayAttributes to set
387             */
388            public void setSearchDisplayedAttributes(final List<String> searchDisplayedAttributes) {
389                    this.searchDisplayedAttributes = searchDisplayedAttributes;
390            }
391    
392            /**
393             * @param testFilter the testFilter to set
394             */
395            public void setTestFilter(final String testFilter) {
396                    this.testFilter = testFilter;
397            }
398    
399            /**
400             * @param otherAttributes the otherAttributes to set
401             */
402            public void setOtherAttributes(final List<String> otherAttributes) {
403                    this.otherAttributes = otherAttributes;
404            }
405    
406    }