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