001 /****************************************************************
002 * Licensed to the Apache Software Foundation (ASF) under one *
003 * or more contributor license agreements. See the NOTICE file *
004 * distributed with this work for additional information *
005 * regarding copyright ownership. The ASF licenses this file *
006 * to you under the Apache License, Version 2.0 (the *
007 * "License"); you may not use this file except in compliance *
008 * with the License. You may obtain a copy of the License at *
009 * *
010 * http://www.apache.org/licenses/LICENSE-2.0 *
011 * *
012 * Unless required by applicable law or agreed to in writing, *
013 * software distributed under the License is distributed on an *
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
015 * KIND, either express or implied. See the License for the *
016 * specific language governing permissions and limitations *
017 * under the License. *
018 ****************************************************************/
019
020 package org.apache.james.user.ldap;
021
022 import java.util.ArrayList;
023 import java.util.Collection;
024 import java.util.HashSet;
025 import java.util.Iterator;
026 import java.util.List;
027 import java.util.Map;
028 import java.util.Properties;
029 import java.util.Set;
030
031 import javax.annotation.PostConstruct;
032 import javax.naming.Context;
033 import javax.naming.NamingEnumeration;
034 import javax.naming.NamingException;
035 import javax.naming.directory.Attribute;
036 import javax.naming.directory.Attributes;
037 import javax.naming.directory.SearchControls;
038 import javax.naming.directory.SearchResult;
039 import javax.naming.ldap.InitialLdapContext;
040 import javax.naming.ldap.LdapContext;
041
042 import org.apache.commons.configuration.ConfigurationException;
043 import org.apache.commons.configuration.HierarchicalConfiguration;
044 import org.apache.james.lifecycle.api.Configurable;
045 import org.apache.james.lifecycle.api.LogEnabled;
046 import org.apache.james.user.api.UsersRepository;
047 import org.apache.james.user.api.UsersRepositoryException;
048 import org.apache.james.user.api.model.User;
049 import org.apache.james.user.ldap.api.LdapConstants;
050 import org.apache.james.util.retry.DoublingRetrySchedule;
051 import org.apache.james.util.retry.api.RetrySchedule;
052 import org.apache.james.util.retry.naming.ldap.RetryingLdapContext;
053
054 import org.slf4j.Logger;
055
056 /**
057 * <p>
058 * This repository implementation serves as a bridge between Apache James and
059 * LDAP. It allows James to authenticate users against an LDAP compliant server
060 * such as Apache DS or Microsoft AD. It also enables role/group based access
061 * restriction based on LDAP groups.
062 * </p>
063 * <p>
064 * It is intended for organisations that already have a user-authentication and
065 * authorisation mechanism in place, and want to leverage this when deploying
066 * James. The assumption inherent here is that such organisations would not want
067 * to manage user details via James, but will do so externally using whatever
068 * mechanism provided by, or built on top off, their LDAP implementation.
069 * </p>
070 * <p>
071 * Based on this assumption, this repository is strictly <b>read-only</b>. As a
072 * consequence, user modification, deletion and creation requests will be
073 * ignored when using this repository.
074 * </p>
075 * <p>
076 * The following fragment of XML provides an example configuration to enable
077 * this repository: </br>
078 *
079 * <pre>
080 * <users-store>
081 * <repository name="LDAPUsers"
082 * class="org.apache.james.userrepository.ReadOnlyUsersLDAPRepository"
083 * ldapHost="ldap://myldapserver:389"
084 * principal="uid=ldapUser,ou=system"
085 * credentials="password"
086 * userBase="ou=People,o=myorg.com,ou=system"
087 * userIdAttribute="uid"
088 * userObjectClass="inetOrgPerson"
089 * maxRetries="20"
090 * retryStartInterval="0"
091 * retryMaxInterval="30"
092 * retryIntervalScale="1000"
093 * </users-store>
094 * </pre>
095 *
096 * </br>
097 *
098 * Its constituent attributes are defined as follows:
099 * <ul>
100 * <li><b>ldapHost:</b> The URL of the LDAP server to connect to.</li>
101 * <li>
102 * <b>principal:</b> (optional) The name (DN) of the user with which to
103 * initially bind to the LDAP server.</li>
104 * <li>
105 * <b>credentials:</b> (optional) The password with which to initially bind to
106 * the LDAP server.</li>
107 * <li>
108 * <b>userBase:</b>The context within which to search for user entities.</li>
109 * <li>
110 * <b>userIdAttribute:</b>The name of the LDAP attribute which holds user ids.
111 * For example "uid" for Apache DS, or "sAMAccountName" for
112 * Microsoft Active Directory.</li>
113 * <li>
114 * <b>userObjectClass:</b>The objectClass value for user nodes below the
115 * userBase. For example "inetOrgPerson" for Apache DS, or
116 * "user" for Microsoft Active Directory.</li>
117 **
118 * <li>
119 * <b>maxRetries:</b> (optional, default = 0) The maximum number of times to
120 * retry a failed operation. -1 means retry forever.</li>
121 * <li>
122 * <b>retryStartInterval:</b> (optional, default = 0) The interval in
123 * milliseconds to wait before the first retry. If > 0, subsequent retries are
124 * made at double the proceeding one up to the <b>retryMaxInterval</b> described
125 * below. If = 0, the next retry is 1 and subsequent retries proceed as above.</li>
126 * <li>
127 * <b>retryMaxInterval:</b> (optional, default = 60) The maximum interval in
128 * milliseconds to wait between retries</li>
129 * <li>
130 * <b>retryIntervalScale:</b> (optional, default = 1000) The amount by which to
131 * multiply each retry interval. The default value of 1000 (milliseconds) is 1
132 * second, so the default <b>retryMaxInterval</b> of 60 is 60 seconds, or 1
133 * minute.
134 * </ul>
135 * </p>
136 * <p>
137 * <em>Example Schedules</em>
138 * <ul>
139 * <li>
140 * Retry after 1000 milliseconds, doubling the interval for each retry up to
141 * 30000 milliseconds, subsequent retry intervals are 30000 milliseconds until
142 * 10 retries have been attempted, after which the <code>Exception</code>
143 * causing the fault is thrown:
144 * <ul>
145 * <li>maxRetries = 10
146 * <li>retryStartInterval = 1000
147 * <li>retryMaxInterval = 30000
148 * <li>retryIntervalScale = 1
149 * </ul>
150 * <li>
151 * Retry immediately, then retry after 1 * 1000 milliseconds, doubling the
152 * interval for each retry up to 30 * 1000 milliseconds, subsequent retry
153 * intervals are 30 * 1000 milliseconds until 20 retries have been attempted,
154 * after which the <code>Exception</code> causing the fault is thrown:
155 * <ul>
156 * <li>maxRetries = 20
157 * <li>retryStartInterval = 0
158 * <li>retryMaxInterval = 30
159 * <li>retryIntervalScale = 1000
160 * </ul>
161 * <li>
162 * Retry after 5000 milliseconds, subsequent retry intervals are 5000
163 * milliseconds. Retry forever:
164 * <ul>
165 * <li>maxRetries = -1
166 * <li>retryStartInterval = 5000
167 * <li>retryMaxInterval = 5000
168 * <li>retryIntervalScale = 1
169 * </ul>
170 * </ul>
171 * </p>
172 *
173 * <p>
174 * In order to enable group/role based access restrictions, you can use the
175 * "<restriction>" configuration element. An example of this is
176 * shown below: <br>
177 *
178 * <pre>
179 * <restriction
180 * memberAttribute="uniqueMember">
181 * <group>cn=PermanentStaff,ou=Groups,o=myorg.co.uk,ou=system</group>
182 * <group>cn=TemporaryStaff,ou=Groups,o=myorg.co.uk,ou=system</group>
183 * </restriction>
184 * </pre>
185 *
186 * Its constituent attributes and elements are defined as follows:
187 * <ul>
188 * <li>
189 * <b>memberAttribute:</b> The LDAP attribute whose values indicate the DNs of
190 * the users which belong to the group or role.</li>
191 * <li>
192 * <b>group:</b> A valid group or role DN. A user is only authenticated
193 * (permitted access) if they belong to at least one of the groups listed under
194 * the "<restriction>" sections.</li>
195 * </ul>
196 * </p>
197 *
198 * <p>
199 * The following parameters may be used to adjust the underlying
200 * <code>com.sun.jndi.ldap.LdapCtxFactory</code>. See <a href=
201 * "http://docs.oracle.com/javase/1.5.0/docs/guide/jndi/jndi-ldap.html#SPIPROPS"
202 * > LDAP Naming Service Provider for the Java Naming and Directory InterfaceTM
203 * (JNDI) : Provider-specific Properties</a> for details.
204 * <ul>
205 * <li>
206 * <b>useConnectionPool:</b> (optional, default = true) Sets property
207 * <code>com.sun.jndi.ldap.connect.pool</code> to the specified boolean value
208 * <li>
209 * <b>connectionTimeout:</b> (optional) Sets property
210 * <code>com.sun.jndi.ldap.connect.timeout</code> to the specified integer value
211 * <li>
212 * <b>readTimeout:</b> (optional) Sets property
213 * <code>com.sun.jndi.ldap.read.timeout</code> to the specified integer value.
214 * Applicable to Java 6 and above.
215 * </ul>
216 *
217 * @see ReadOnlyLDAPUser
218 * @see ReadOnlyLDAPGroupRestriction
219 *
220 */
221 public class ReadOnlyUsersLDAPRepository implements UsersRepository, Configurable, LogEnabled {
222
223 // The name of the factory class which creates the initial context
224 // for the LDAP service provider
225 private static final String INITIAL_CONTEXT_FACTORY = "com.sun.jndi.ldap.LdapCtxFactory";
226
227 private static final String PROPERTY_NAME_CONNECTION_POOL = "com.sun.jndi.ldap.connect.pool";
228 private static final String PROPERTY_NAME_CONNECT_TIMEOUT = "com.sun.jndi.ldap.connect.timeout";
229 private static final String PROPERTY_NAME_READ_TIMEOUT = "com.sun.jndi.ldap.read.timeout";
230
231 /**
232 * The URL of the LDAP server against which users are to be authenticated.
233 * Note that users are actually authenticated by binding against the LDAP
234 * server using the users "dn" and "credentials".The
235 * value of this field is taken from the value of the configuration
236 * attribute "ldapHost".
237 */
238 private String ldapHost;
239
240 /**
241 * The value of this field is taken from the configuration attribute
242 * "userIdAttribute". This is the LDAP attribute type which holds
243 * the userId value. Note that this is not the same as the email address
244 * attribute.
245 */
246 private String userIdAttribute;
247
248 /**
249 * The value of this field is taken from the configuration attribute
250 * "userObjectClass". This is the LDAP object class to use in the
251 * search filter for user nodes under the userBase value.
252 */
253 private String userObjectClass;
254
255 /**
256 * This is the LDAP context/sub-context within which to search for user
257 * entities. The value of this field is taken from the configuration
258 * attribute "userBase".
259 */
260 private String userBase;
261
262 /**
263 * The user with which to initially bind to the LDAP server. The value of
264 * this field is taken from the configuration attribute
265 * "principal".
266 */
267 private String principal;
268
269 /**
270 * The password/credentials with which to initially bind to the LDAP server.
271 * The value of this field is taken from the configuration attribute
272 * "credentials".
273 */
274 private String credentials;
275
276 /**
277 * Encapsulates the information required to restrict users to LDAP groups or
278 * roles. This object is populated from the contents of the configuration
279 * element <restriction>.
280 */
281 private ReadOnlyLDAPGroupRestriction restriction;
282
283 /**
284 * The context for the LDAP server. This is the connection that is built
285 * from the configuration attributes "ldapHost",
286 * "principal" and "credentials".
287 */
288 private LdapContext ldapContext;
289
290 // Use a connection pool. Default is true.
291 private boolean useConnectionPool = true;
292
293 // The connection timeout in milliseconds.
294 // A value of less than or equal to zero means to use the network protocol's
295 // (i.e., TCP's) timeout value.
296 private int connectionTimeout = -1;
297
298 // The LDAP read timeout in milliseconds.
299 private int readTimeout = -1;
300
301 // The schedule for retry attempts
302 private RetrySchedule schedule = null;
303
304 // Maximum number of times to retry a connection attempts. Default is no
305 // retries.
306 private int maxRetries = 0;
307
308 private Logger log;
309
310 /**
311 * Creates a new instance of ReadOnlyUsersLDAPRepository.
312 *
313 */
314 public ReadOnlyUsersLDAPRepository() {
315 super();
316 }
317
318 /**
319 * Extracts the parameters required by the repository instance from the
320 * James server configuration data. The fields extracted include
321 * {@link #ldapHost}, {@link #userIdAttribute}, {@link #userBase},
322 * {@link #principal}, {@link #credentials} and {@link #restriction}.
323 *
324 * @param configuration
325 * An encapsulation of the James server configuration data.
326 */
327 public void configure(HierarchicalConfiguration configuration) throws ConfigurationException {
328 ldapHost = configuration.getString("[@ldapHost]", "");
329 principal = configuration.getString("[@principal]", "");
330 credentials = configuration.getString("[@credentials]", "");
331 userBase = configuration.getString("[@userBase]");
332 userIdAttribute = configuration.getString("[@userIdAttribute]");
333 userObjectClass = configuration.getString("[@userObjectClass]");
334 // Default is to use connection pooling
335 useConnectionPool = configuration.getBoolean("[@useConnectionPool]", true);
336 connectionTimeout = configuration.getInt("[@connectionTimeout]", -1);
337 readTimeout = configuration.getInt("[@readTimeout]", -1);
338 // Default maximum retries is 1, which allows an alternate connection to
339 // be found in a multi-homed environment
340 maxRetries = configuration.getInt("[@maxRetries]", 1);
341 // Default retry start interval is 0 second
342 long retryStartInterval = configuration.getLong("[@retryStartInterval]", 0);
343 // Default maximum retry interval is 60 seconds
344 long retryMaxInterval = configuration.getLong("[@retryMaxInterval]", 60);
345 int scale = configuration.getInt("[@retryIntervalScale]", 1000); // seconds
346 schedule = new DoublingRetrySchedule(retryStartInterval, retryMaxInterval, scale);
347
348 HierarchicalConfiguration restrictionConfig = null;
349 // Check if we have a restriction we can use
350 // See JAMES-1204
351 if (configuration.containsKey("restriction[@memberAttribute]")) {
352 restrictionConfig = configuration.configurationAt("restriction");
353 }
354 restriction = new ReadOnlyLDAPGroupRestriction(restrictionConfig);
355
356 }
357
358 /**
359 * Initialises the user-repository instance. It will create a connection to
360 * the LDAP host using the supplied configuration.
361 *
362 * @throws Exception
363 * If an error occurs authenticating or connecting to the
364 * specified LDAP host.
365 */
366 @PostConstruct
367 public void init() throws Exception {
368 if (log.isDebugEnabled()) {
369 log.debug(new StringBuilder(128).
370 append(this.getClass().getName()).
371 append(".init()").
372 append('\n').
373 append("LDAP host: ").
374 append(ldapHost).
375 append('\n').
376 append("User baseDN: ").
377 append(userBase).
378 append('\n').
379 append("userIdAttribute: ").
380 append(userIdAttribute).
381 append('\n').
382 append("Group restriction: ").
383 append(restriction).
384 append('\n').
385 append("UseConnectionPool: ").
386 append(useConnectionPool).
387 append('\n').
388 append("connectionTimeout: ").
389 append(connectionTimeout).
390 append('\n').
391 append("readTimeout: ").
392 append(readTimeout).
393 append('\n').
394 append("retrySchedule: ").
395 append(schedule).
396 append('\n').
397 append("maxRetries: ").
398 append(maxRetries).
399 append('\n').
400 toString());
401 }
402 // Setup the initial LDAP context
403 updateLdapContext();
404 }
405
406 /**
407 * Answer the LDAP context used to connect with the LDAP server.
408 *
409 * @return an <code>LdapContext</code>
410 * @throws NamingException
411 */
412 protected LdapContext getLdapContext() throws NamingException {
413 if (null == ldapContext) {
414 updateLdapContext();
415 }
416 return ldapContext;
417 }
418
419 protected void updateLdapContext() throws NamingException {
420 ldapContext = computeLdapContext();
421 }
422
423 /**
424 * Answers a new LDAP/JNDI context using the specified user credentials.
425 *
426 * @return an LDAP directory context
427 * @throws NamingException
428 * Propagated from underlying LDAP communication API.
429 */
430 protected LdapContext computeLdapContext() throws NamingException {
431 return new RetryingLdapContext(schedule, maxRetries, log) {
432
433 @Override
434 public Context newDelegate() throws NamingException {
435 return new InitialLdapContext(getContextEnvironment(), null);
436 }
437 };
438 }
439
440 protected Properties getContextEnvironment()
441 {
442 final Properties props = new Properties();
443 props.put(Context.INITIAL_CONTEXT_FACTORY, INITIAL_CONTEXT_FACTORY);
444 props.put(Context.PROVIDER_URL, null == ldapHost ? "" : ldapHost);
445 if (null == credentials || credentials.isEmpty()) {
446 props.put(Context.SECURITY_AUTHENTICATION, LdapConstants.SECURITY_AUTHENTICATION_NONE);
447 } else {
448 props.put(Context.SECURITY_AUTHENTICATION, LdapConstants.SECURITY_AUTHENTICATION_SIMPLE);
449 props.put(Context.SECURITY_PRINCIPAL, null == principal ? "" : principal);
450 props.put(Context.SECURITY_CREDENTIALS, credentials);
451 }
452 // The following properties are specific to com.sun.jndi.ldap.LdapCtxFactory
453 props.put(PROPERTY_NAME_CONNECTION_POOL, Boolean.toString(useConnectionPool));
454 if (connectionTimeout > -1)
455 {
456 props.put(PROPERTY_NAME_CONNECT_TIMEOUT, Integer.toString(connectionTimeout));
457 }
458 if (readTimeout > -1)
459 {
460 props.put(PROPERTY_NAME_READ_TIMEOUT, Integer.toString(readTimeout));
461 }
462 return props;
463 }
464
465 /**
466 * Indicates if the user with the specified DN can be found in the group
467 * membership map-as encapsulated by the specified parameter map.
468 *
469 * @param userDN
470 * The DN of the user to search for.
471 * @param groupMembershipList
472 * A map containing the entire group membership lists for the
473 * configured groups. This is organised as a map of
474 *
475 * <code>"<groupDN>=<[userDN1,userDN2,...,userDNn]>"</code>
476 * pairs. In essence, each <code>groupDN</code> string is
477 * associated to a list of <code>userDNs</code>.
478 * @return <code>True</code> if the specified userDN is associated with at
479 * least one group in the parameter map, and <code>False</code>
480 * otherwise.
481 */
482 private boolean userInGroupsMembershipList(String userDN,
483 Map<String, Collection<String>> groupMembershipList) {
484 boolean result = false;
485
486 Collection<Collection<String>> memberLists = groupMembershipList.values();
487 Iterator<Collection<String>> memberListsIterator = memberLists.iterator();
488
489 while (memberListsIterator.hasNext() && !result) {
490 Collection<String> groupMembers = memberListsIterator.next();
491 result = groupMembers.contains(userDN);
492 }
493
494 return result;
495 }
496
497 /**
498 * Gets all the user entities taken from the LDAP server, as taken from the
499 * search-context given by the value of the attribute {@link #userBase}.
500 *
501 * @return A set containing all the relevant users found in the LDAP
502 * directory.
503 * @throws NamingException
504 * Propagated from the LDAP communication layer.
505 */
506 private Set<String> getAllUsersFromLDAP() throws NamingException {
507 Set<String> result = new HashSet<String>();
508
509 SearchControls sc = new SearchControls();
510 sc.setSearchScope(SearchControls.SUBTREE_SCOPE);
511 sc.setReturningAttributes(new String[] { "distinguishedName" });
512 NamingEnumeration<SearchResult> sr = ldapContext.search(userBase, "(objectClass="
513 + userObjectClass + ")", sc);
514 while (sr.hasMore()) {
515 SearchResult r = sr.next();
516 result.add(r.getNameInNamespace());
517 }
518
519 return result;
520 }
521
522 /**
523 * Extract the user attributes for the given collection of userDNs, and
524 * encapsulates the user list as a collection of {@link ReadOnlyLDAPUser}s.
525 * This method delegates the extraction of a single user's details to the
526 * method {@link #buildUser(String)}.
527 *
528 * @param userDNs
529 * The distinguished-names (DNs) of the users whose information
530 * is to be extracted from the LDAP repository.
531 * @return A collection of {@link ReadOnlyLDAPUser}s as taken from the LDAP
532 * server.
533 * @throws NamingException
534 * Propagated from the underlying LDAP communication layer.
535 */
536 private Collection<ReadOnlyLDAPUser> buildUserCollection(Collection<String> userDNs)
537 throws NamingException {
538 List<ReadOnlyLDAPUser> results = new ArrayList<ReadOnlyLDAPUser>();
539
540 Iterator<String> userDNIterator = userDNs.iterator();
541
542 while (userDNIterator.hasNext()) {
543 ReadOnlyLDAPUser user = buildUser(userDNIterator.next());
544 results.add(user);
545 }
546
547 return results;
548 }
549
550 /**
551 * Given a userDN, this method retrieves the user attributes from the LDAP
552 * server, so as to extract the items that are of interest to James.
553 * Specifically it extracts the userId, which is extracted from the LDAP
554 * attribute whose name is given by the value of the field
555 * {@link #userIdAttribute}.
556 *
557 * @param userDN
558 * The distinguished-name of the user whose details are to be
559 * extracted from the LDAP repository.
560 * @return A {@link ReadOnlyLDAPUser} instance which is initialized with the
561 * userId of this user and ldap connection information with which
562 * the userDN and attributes were obtained.
563 * @throws NamingException
564 * Propagated by the underlying LDAP communication layer.
565 */
566 private ReadOnlyLDAPUser buildUser(String userDN) throws NamingException {
567 SearchControls sc = new SearchControls();
568 sc.setSearchScope(SearchControls.OBJECT_SCOPE);
569 sc.setReturningAttributes(new String[] { userIdAttribute });
570 sc.setCountLimit(1);
571
572 StringBuilder builderFilter = new StringBuilder("(objectClass=");
573 builderFilter.append(userObjectClass);
574 builderFilter.append(")");
575 NamingEnumeration<SearchResult> sr = ldapContext.search(userDN, builderFilter.toString(),
576 sc);
577
578 if (!sr.hasMore())
579 return null;
580
581 Attributes userAttributes = sr.next().getAttributes();
582 Attribute userName = userAttributes.get(userIdAttribute);
583
584 if (!restriction.isActivated()
585 || userInGroupsMembershipList(userDN, restriction
586 .getGroupMembershipLists(ldapContext)))
587 return new ReadOnlyLDAPUser(userName.get().toString(), userDN, ldapContext);
588
589 return null;
590 }
591
592 /**
593 * @see UsersRepository#contains(java.lang.String)
594 */
595 public boolean contains(String name) throws UsersRepositoryException {
596 if (getUserByName(name) != null) {
597 return true;
598 }
599 return false;
600 }
601
602 /*
603 * TODO Should this be deprecated? At least the method isn't declared in the
604 * interface anymore
605 *
606 * @see UsersRepository#containsCaseInsensitive(java.lang.String)
607 */
608 public boolean containsCaseInsensitive(String name) throws UsersRepositoryException {
609 if (getUserByNameCaseInsensitive(name) != null) {
610 return true;
611 }
612 return false;
613 }
614
615 /**
616 * @see UsersRepository#countUsers()
617 */
618 public int countUsers() throws UsersRepositoryException {
619 try {
620 return getValidUsers().size();
621 } catch (NamingException e) {
622 log.error("Unable to retrieve user count from ldap", e);
623 throw new UsersRepositoryException("Unable to retrieve user count from ldap", e);
624
625 }
626 }
627
628 /*
629 * TODO Should this be deprecated? At least the method isn't declared in the
630 * interface anymore
631 *
632 * @see UsersRepository#getRealName(java.lang.String)
633 */
634 public String getRealName(String name) throws UsersRepositoryException {
635 User u = getUserByNameCaseInsensitive(name);
636 if (u != null) {
637 return u.getUserName();
638 }
639
640 return null;
641 }
642
643 /**
644 * @see UsersRepository#getUserByName(java.lang.String)
645 */
646 public User getUserByName(String name) throws UsersRepositoryException {
647 try {
648 return buildUser(userIdAttribute + "=" + name + "," + userBase);
649 } catch (NamingException e) {
650 log.error("Unable to retrieve user from ldap", e);
651 throw new UsersRepositoryException("Unable to retrieve user from ldap", e);
652
653 }
654 }
655
656 /*
657 * TODO Should this be deprecated? At least the method isn't declared in the
658 * interface anymore
659 *
660 * @see UsersRepository#getUserByNameCaseInsensitive(java.lang.String)
661 */
662 public User getUserByNameCaseInsensitive(String name) throws UsersRepositoryException {
663 try {
664 Iterator<ReadOnlyLDAPUser> userIt = buildUserCollection(getValidUsers()).iterator();
665 while (userIt.hasNext()) {
666 ReadOnlyLDAPUser u = userIt.next();
667 if (u.getUserName().equalsIgnoreCase(name)) {
668 return u;
669 }
670 }
671
672 } catch (NamingException e) {
673 log.error("Unable to retrieve user from ldap", e);
674 throw new UsersRepositoryException("Unable to retrieve user from ldap", e);
675
676 }
677 return null;
678 }
679
680 /**
681 * @see UsersRepository#list()
682 */
683 public Iterator<String> list() throws UsersRepositoryException {
684 List<String> result = new ArrayList<String>();
685 try {
686
687 Iterator<ReadOnlyLDAPUser> userIt = buildUserCollection(getValidUsers()).iterator();
688
689 while (userIt.hasNext()) {
690 result.add(userIt.next().getUserName());
691 }
692 } catch (NamingException namingException) {
693 throw new UsersRepositoryException(
694 "Unable to retrieve users list from LDAP due to unknown naming error.",
695 namingException);
696 }
697
698 return result.iterator();
699 }
700
701 private Collection<String> getValidUsers() throws NamingException {
702 Set<String> userDNs = getAllUsersFromLDAP();
703 Collection<String> validUserDNs;
704
705 if (restriction.isActivated()) {
706 Map<String, Collection<String>> groupMembershipList = restriction
707 .getGroupMembershipLists(ldapContext);
708 validUserDNs = new ArrayList<String>();
709
710 Iterator<String> userDNIterator = userDNs.iterator();
711 String userDN;
712 while (userDNIterator.hasNext()) {
713 userDN = userDNIterator.next();
714 if (userInGroupsMembershipList(userDN, groupMembershipList))
715 validUserDNs.add(userDN);
716 }
717 } else {
718 validUserDNs = userDNs;
719 }
720 return validUserDNs;
721 }
722
723 /**
724 * @see UsersRepository#removeUser(java.lang.String)
725 */
726 public void removeUser(String name) throws UsersRepositoryException {
727 log.warn("This user-repository is read-only. Modifications are not permitted.");
728 throw new UsersRepositoryException(
729 "This user-repository is read-only. Modifications are not permitted.");
730
731 }
732
733 /**
734 * @see UsersRepository#test(java.lang.String, java.lang.String)
735 */
736 public boolean test(String name, String password) throws UsersRepositoryException {
737 User u = getUserByName(name);
738 if (u != null) {
739 return u.verifyPassword(password);
740 }
741 return false;
742 }
743
744 /**
745 * @see UsersRepository#addUser(java.lang.String, java.lang.String)
746 */
747 public void addUser(String username, String password) throws UsersRepositoryException {
748 log.error("This user-repository is read-only. Modifications are not permitted.");
749 throw new UsersRepositoryException(
750 "This user-repository is read-only. Modifications are not permitted.");
751 }
752
753 /**
754 * @see UsersRepository#updateUser(org.apache.james.api.user.User)
755 */
756 public void updateUser(User user) throws UsersRepositoryException {
757 log.error("This user-repository is read-only. Modifications are not permitted.");
758 throw new UsersRepositoryException(
759 "This user-repository is read-only. Modifications are not permitted.");
760 }
761
762 /**
763 * @see org.apache.james.lifecycle.api.LogEnabled#setLog(org.slf4j.Logger)
764 */
765 public void setLog(Logger log) {
766 this.log = log;
767 }
768
769 /**
770 * VirtualHosting not supported
771 */
772 public boolean supportVirtualHosting() throws UsersRepositoryException {
773 return false;
774 }
775
776 }