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 }