001: package org.apache.turbine.services.security.ldap;
002:
003: /*
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021:
022: import java.util.List;
023: import java.util.Hashtable;
024: import java.util.Vector;
025:
026: import javax.naming.AuthenticationException;
027: import javax.naming.Context;
028: import javax.naming.NamingEnumeration;
029: import javax.naming.NamingException;
030: import javax.naming.directory.Attributes;
031: import javax.naming.directory.DirContext;
032: import javax.naming.directory.SearchControls;
033: import javax.naming.directory.SearchResult;
034:
035: import org.apache.commons.configuration.Configuration;
036:
037: import org.apache.torque.util.Criteria;
038:
039: import org.apache.turbine.om.security.User;
040: import org.apache.turbine.services.security.TurbineSecurity;
041: import org.apache.turbine.services.security.UserManager;
042: import org.apache.turbine.util.security.DataBackendException;
043: import org.apache.turbine.util.security.EntityExistsException;
044: import org.apache.turbine.util.security.PasswordMismatchException;
045: import org.apache.turbine.util.security.UnknownEntityException;
046:
047: /**
048: * A UserManager performs {@link org.apache.turbine.om.security.User}
049: * object related tasks on behalf of the
050: * {@link org.apache.turbine.services.security.SecurityService}.
051: *
052: * This implementation uses ldap for retrieving user data. It
053: * expects that the User interface implementation will be castable to
054: * {@link org.apache.turbine.om.BaseObject}.
055: *
056: * @author <a href="mailto:jon@collab.net">Jon S. Stevens</a>
057: * @author <a href="mailto:john.mcnally@clearink.com">John D. McNally</a>
058: * @author <a href="mailto:frank.kim@clearink.com">Frank Y. Kim</a>
059: * @author <a href="mailto:cberry@gluecode.com">Craig D. Berry</a>
060: * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
061: * @author <a href="mailto:tadewunmi@gluecode.com">Tracy M. Adewunmi</a>
062: * @author <a href="mailto:lflournoy@gluecode.com">Leonard J. Flournoy</a>
063: * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
064: * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
065: * @author <a href="mailto:hhernandez@itweb.com.mx">Humberto Hernandez</a>
066: * @version $Id: LDAPUserManager.java 534527 2007-05-02 16:10:59Z tv $
067: */
068: public class LDAPUserManager implements UserManager {
069: /**
070: * Initializes the UserManager
071: *
072: * @param conf A Configuration object to init this Manager
073: */
074: public void init(Configuration conf) {
075: // GNDN
076: }
077:
078: /**
079: * Check wether a specified user's account exists.
080: *
081: * The login name is used for looking up the account.
082: *
083: * @param user The user to be checked.
084: * @return true if the specified account exists
085: * @throws DataBackendException Error accessing the data backend.
086: */
087: public boolean accountExists(User user) throws DataBackendException {
088: return accountExists(user.getName());
089: }
090:
091: /**
092: *
093: * Check wether a specified user's account exists.
094: * The login name is used for looking up the account.
095: *
096: * @param username The name of the user to be checked.
097: * @return true if the specified account exists
098: * @throws DataBackendException Error accessing the data backend.
099: */
100: public boolean accountExists(String username)
101: throws DataBackendException {
102: try {
103: User ldapUser = retrieve(username);
104: } catch (UnknownEntityException ex) {
105: return false;
106: }
107:
108: return true;
109: }
110:
111: /**
112: * Retrieve a user from persistent storage using username as the
113: * key.
114: *
115: * @param username the name of the user.
116: * @return an User object.
117: * @exception UnknownEntityException if the user's account does not
118: * exist in the database.
119: * @exception DataBackendException Error accessing the data backend.
120: */
121: public User retrieve(String username)
122: throws UnknownEntityException, DataBackendException {
123: try {
124: DirContext ctx = bindAsAdmin();
125:
126: /*
127: * Define the search.
128: */
129: String userBaseSearch = LDAPSecurityConstants
130: .getBaseSearch();
131: String filter = LDAPSecurityConstants.getNameAttribute();
132:
133: filter = "(" + filter + "=" + username + ")";
134:
135: /*
136: * Create the default search controls.
137: */
138: SearchControls ctls = new SearchControls();
139:
140: NamingEnumeration answer = ctx.search(userBaseSearch,
141: filter, ctls);
142:
143: if (answer.hasMore()) {
144: SearchResult sr = (SearchResult) answer.next();
145: Attributes attribs = sr.getAttributes();
146: LDAPUser ldapUser = createLDAPUser();
147:
148: ldapUser.setLDAPAttributes(attribs);
149: ldapUser.setTemp("turbine.user", ldapUser);
150:
151: return ldapUser;
152: } else {
153: throw new UnknownEntityException("The given user: "
154: + username + "\n does not exist.");
155: }
156: } catch (NamingException ex) {
157: throw new DataBackendException(
158: "The LDAP server specified is unavailable", ex);
159: }
160: }
161:
162: /**
163: * Retrieve a user from persistent storage using the primary key
164: *
165: * @param key The primary key object
166: * @return an User object.
167: * @throws UnknownEntityException if the user's record does not
168: * exist in the database.
169: * @throws DataBackendException if there is a problem accessing the
170: * storage.
171: */
172: public User retrieveById(Object key) throws UnknownEntityException,
173: DataBackendException {
174: try {
175: DirContext ctx = bindAsAdmin();
176:
177: /*
178: * Define the search.
179: */
180: StringBuffer userBaseSearch = new StringBuffer();
181: userBaseSearch.append(LDAPSecurityConstants
182: .getUserIdAttribute());
183: userBaseSearch.append("=");
184: userBaseSearch.append(String.valueOf(key));
185: userBaseSearch.append(",");
186: userBaseSearch
187: .append(LDAPSecurityConstants.getBaseSearch());
188:
189: /*
190: * Create the default search controls.
191: */
192: NamingEnumeration answer = ctx.search(userBaseSearch
193: .toString(), (Attributes) null);
194:
195: if (answer.hasMore()) {
196: SearchResult sr = (SearchResult) answer.next();
197: Attributes attribs = sr.getAttributes();
198: LDAPUser ldapUser = createLDAPUser();
199:
200: ldapUser.setLDAPAttributes(attribs);
201: ldapUser.setTemp("turbine.user", ldapUser);
202:
203: return ldapUser;
204: } else {
205: throw new UnknownEntityException(
206: "No user exists for the key: "
207: + String.valueOf(key) + "\n");
208: }
209: } catch (NamingException ex) {
210: throw new DataBackendException(
211: "The LDAP server specified is unavailable", ex);
212: }
213: }
214:
215: /**
216: * This is currently not implemented to behave as expected. It
217: * ignores the Criteria argument and returns all the users.
218: *
219: * Retrieve a set of users that meet the specified criteria.
220: *
221: * As the keys for the criteria, you should use the constants that
222: * are defined in {@link User} interface, plus the the names
223: * of the custom attributes you added to your user representation
224: * in the data storage. Use verbatim names of the attributes -
225: * without table name prefix in case of DB implementation.
226: *
227: * @param criteria The criteria of selection.
228: * @return a List of users meeting the criteria.
229: * @throws DataBackendException Error accessing the data backend.
230: * @deprecated Use <a href="#retrieveList">retrieveList</a> instead.
231: */
232: public User[] retrieve(Criteria criteria)
233: throws DataBackendException {
234: return (User[]) retrieveList(criteria).toArray(new User[0]);
235: }
236:
237: /**
238: * Retrieve a list of users that meet the specified criteria.
239: *
240: * As the keys for the criteria, you should use the constants that
241: * are defined in {@link User} interface, plus the names
242: * of the custom attributes you added to your user representation
243: * in the data storage. Use verbatim names of the attributes -
244: * without table name prefix in case of Torque implementation.
245: *
246: * @param criteria The criteria of selection.
247: * @return a List of users meeting the criteria.
248: * @throws DataBackendException if there is a problem accessing the
249: * storage.
250: */
251: public List retrieveList(Criteria criteria)
252: throws DataBackendException {
253: List users = new Vector(0);
254:
255: try {
256: DirContext ctx = bindAsAdmin();
257:
258: String userBaseSearch = LDAPSecurityConstants
259: .getBaseSearch();
260: String filter = LDAPSecurityConstants.getNameAttribute();
261:
262: filter = "(" + filter + "=*)";
263:
264: /*
265: * Create the default search controls.
266: */
267: SearchControls ctls = new SearchControls();
268:
269: NamingEnumeration answer = ctx.search(userBaseSearch,
270: filter, ctls);
271:
272: while (answer.hasMore()) {
273: SearchResult sr = (SearchResult) answer.next();
274: Attributes attribs = sr.getAttributes();
275: LDAPUser ldapUser = createLDAPUser();
276:
277: ldapUser.setLDAPAttributes(attribs);
278: ldapUser.setTemp("turbine.user", ldapUser);
279: users.add(ldapUser);
280: }
281: } catch (NamingException ex) {
282: throw new DataBackendException(
283: "The LDAP server specified is unavailable", ex);
284: }
285: return users;
286: }
287:
288: /**
289: * Retrieve a user from persistent storage using username as the
290: * key, and authenticate the user. The implementation may chose
291: * to authenticate to the server as the user whose data is being
292: * retrieved.
293: *
294: * @param username the name of the user.
295: * @param password the user supplied password.
296: * @return an User object.
297: * @exception PasswordMismatchException if the supplied password was
298: * incorrect.
299: * @exception UnknownEntityException if the user's account does not
300: * exist in the database.
301: * @exception DataBackendException Error accessing the data backend.
302: */
303: public User retrieve(String username, String password)
304: throws PasswordMismatchException, UnknownEntityException,
305: DataBackendException {
306: User user = retrieve(username);
307:
308: authenticate(user, password);
309: return user;
310: }
311:
312: /**
313: * Save a User object to persistent storage. User's account is
314: * required to exist in the storage.
315: *
316: * @param user an User object to store.
317: * @throws UnknownEntityException if the user's account does not
318: * exist in the database.
319: * @throws DataBackendException if there is an LDAP error
320: *
321: */
322: public void store(User user) throws UnknownEntityException,
323: DataBackendException {
324: if (!accountExists(user)) {
325: throw new UnknownEntityException("The account '"
326: + user.getName() + "' does not exist");
327: }
328:
329: try {
330: LDAPUser ldapUser = (LDAPUser) user;
331: Attributes attrs = ldapUser.getLDAPAttributes();
332: String name = ldapUser.getDN();
333:
334: DirContext ctx = bindAsAdmin();
335:
336: ctx.modifyAttributes(name, DirContext.REPLACE_ATTRIBUTE,
337: attrs);
338: } catch (NamingException ex) {
339: throw new DataBackendException("NamingException caught", ex);
340: }
341: }
342:
343: /**
344: * This method is not yet implemented.
345: * Saves User data when the session is unbound. The user account is required
346: * to exist in the storage.
347: *
348: * LastLogin, AccessCounter, persistent pull tools, and any data stored
349: * in the permData hashtable that is not mapped to a column will be saved.
350: *
351: * @exception UnknownEntityException if the user's account does not
352: * exist in the database.
353: * @exception DataBackendException if there is a problem accessing the
354: * storage.
355: */
356: public void saveOnSessionUnbind(User user)
357: throws UnknownEntityException, DataBackendException {
358: if (!accountExists(user)) {
359: throw new UnknownEntityException("The account '"
360: + user.getName() + "' does not exist");
361: }
362: }
363:
364: /**
365: * Authenticate a User with the specified password. If authentication
366: * is successful the method returns nothing. If there are any problems,
367: * exception was thrown.
368: *
369: * @param user a User object to authenticate.
370: * @param password the user supplied password.
371: * @exception PasswordMismatchException if the supplied password was
372: * incorrect.
373: * @exception UnknownEntityException if the user's account does not
374: * exist in the database.
375: * @exception DataBackendException Error accessing the data backend.
376: */
377: public void authenticate(User user, String password)
378: throws PasswordMismatchException, UnknownEntityException,
379: DataBackendException {
380: LDAPUser ldapUser = (LDAPUser) user;
381:
382: try {
383: bind(ldapUser.getDN(), password);
384: } catch (AuthenticationException ex) {
385: throw new PasswordMismatchException(
386: "The given password for: " + ldapUser.getDN()
387: + " is invalid\n");
388: } catch (NamingException ex) {
389: throw new DataBackendException("NamingException caught:",
390: ex);
391: }
392: }
393:
394: /**
395: * This method is not yet implemented
396: * Change the password for an User.
397: *
398: * @param user an User to change password for.
399: * @param newPass the new password.
400: * @param oldPass the old password.
401: * @exception PasswordMismatchException if the supplied password was
402: * incorrect.
403: * @exception UnknownEntityException if the user's account does not
404: * exist in the database.
405: * @exception DataBackendException Error accessing the data backend.
406: */
407: public void changePassword(User user, String oldPass, String newPass)
408: throws PasswordMismatchException, UnknownEntityException,
409: DataBackendException {
410: throw new DataBackendException(
411: "The method changePassword has no implementation.");
412: }
413:
414: /**
415: * This method is not yet implemented
416: * Forcibly sets new password for an User.
417: *
418: * This is supposed to be used by the administrator to change the forgotten
419: * or compromised passwords. Certain implementatations of this feature
420: * would require adminstrative level access to the authenticating
421: * server / program.
422: *
423: * @param user an User to change password for.
424: * @param password the new password.
425: * @exception UnknownEntityException if the user's record does not
426: * exist in the database.
427: * @exception DataBackendException Error accessing the data backend.
428: */
429: public void forcePassword(User user, String password)
430: throws UnknownEntityException, DataBackendException {
431: throw new DataBackendException(
432: "The method forcePassword has no implementation.");
433: }
434:
435: /**
436: * Creates new user account with specified attributes.
437: *
438: * @param user the object describing account to be created.
439: * @param initialPassword Not used yet.
440: * @throws DataBackendException Error accessing the data backend.
441: * @throws EntityExistsException if the user account already exists.
442: */
443: public void createAccount(User user, String initialPassword)
444: throws EntityExistsException, DataBackendException {
445: if (accountExists(user)) {
446: throw new EntityExistsException("The account '"
447: + user.getName() + "' already exist");
448: }
449:
450: try {
451: LDAPUser ldapUser = (LDAPUser) user;
452: Attributes attrs = ldapUser.getLDAPAttributes();
453: String name = ldapUser.getDN();
454:
455: DirContext ctx = bindAsAdmin();
456:
457: ctx.bind(name, null, attrs);
458: } catch (NamingException ex) {
459: throw new DataBackendException("NamingException caught", ex);
460: }
461: }
462:
463: /**
464: * Removes an user account from the system.
465: *
466: * @param user the object describing the account to be removed.
467: * @throws DataBackendException Error accessing the data backend.
468: * @throws UnknownEntityException if the user account is not present.
469: */
470: public void removeAccount(User user) throws UnknownEntityException,
471: DataBackendException {
472: if (!accountExists(user)) {
473: throw new UnknownEntityException("The account '"
474: + user.getName() + "' does not exist");
475: }
476:
477: try {
478: LDAPUser ldapUser = (LDAPUser) user;
479: String name = ldapUser.getDN();
480:
481: DirContext ctx = bindAsAdmin();
482:
483: ctx.unbind(name);
484: } catch (NamingException ex) {
485: throw new DataBackendException("NamingException caught", ex);
486: }
487: }
488:
489: /**
490: * Bind as the admin user.
491: *
492: * @throws NamingException when an error occurs with the named server.
493: * @return a new DirContext.
494: */
495: public static DirContext bindAsAdmin() throws NamingException {
496: String adminUser = LDAPSecurityConstants.getAdminUsername();
497: String adminPassword = LDAPSecurityConstants.getAdminPassword();
498:
499: return bind(adminUser, adminPassword);
500: }
501:
502: /**
503: * Creates an initial context.
504: *
505: * @param username admin username supplied in TRP.
506: * @param password admin password supplied in TRP
507: * @throws NamingException when an error occurs with the named server.
508: * @return a new DirContext.
509: */
510: public static DirContext bind(String username, String password)
511: throws NamingException {
512: String host = LDAPSecurityConstants.getLDAPHost();
513: String port = LDAPSecurityConstants.getLDAPPort();
514: String providerURL = "ldap://" + host + ":" + port;
515: String ldapProvider = LDAPSecurityConstants.getLDAPProvider();
516: String authentication = LDAPSecurityConstants
517: .getLDAPAuthentication();
518:
519: /*
520: * creating an initial context using Sun's client
521: * LDAP Provider.
522: */
523: Hashtable env = new Hashtable();
524:
525: env.put(Context.INITIAL_CONTEXT_FACTORY, ldapProvider);
526: env.put(Context.PROVIDER_URL, providerURL);
527: env.put(Context.SECURITY_AUTHENTICATION, authentication);
528: env.put(Context.SECURITY_PRINCIPAL, username);
529: env.put(Context.SECURITY_CREDENTIALS, password);
530:
531: DirContext ctx = new javax.naming.directory.InitialDirContext(
532: env);
533:
534: return ctx;
535: }
536:
537: /**
538: * Create a new instance of the LDAP User according to the value
539: * configured in TurbineResources.properties.
540: * @return a new instance of the LDAP User.
541: * @throws DataBackendException if there is an error creating the
542: */
543: private LDAPUser createLDAPUser() throws DataBackendException {
544: try {
545: return (LDAPUser) TurbineSecurity.getUserInstance();
546: } catch (ClassCastException ex) {
547: throw new DataBackendException("ClassCastException:", ex);
548: } catch (UnknownEntityException ex) {
549: throw new DataBackendException("UnknownEntityException:",
550: ex);
551: }
552: }
553:
554: }
|