1/*2 * Copyright 2000-2004 The Apache Software Foundation.3 * 4 * Licensed under the Apache License, Version 2.0 (the "License");5 * you may not use this file except in compliance with the License.6 * You may obtain a copy of the License at7 * 8 * http://www.apache.org/licenses/LICENSE-2.09 * 10 * Unless required by applicable law or agreed to in writing, software11 * distributed under the License is distributed on an "AS IS" BASIS,12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.13 * See the License for the specific language governing permissions and14 * limitations under the License.15 */1617packageorg.apache.jetspeed.services.security.ldap;
1819import java.security.Principal;
20import java.util.Iterator;
21import java.util.List;
22import java.util.Vector;
23import javax.naming.NamingEnumeration;
24import javax.naming.directory.Attributes;
25import javax.naming.directory.BasicAttributes;
26import javax.naming.directory.DirContext;
27import javax.naming.directory.SearchResult;
28import javax.servlet.ServletConfig;
2930// Jetspeed classes31import org.apache.jetspeed.om.profile.Profile;
32import org.apache.jetspeed.om.security.JetspeedUser;
33import org.apache.jetspeed.om.security.UserNamePrincipal;
34import org.apache.jetspeed.om.security.ldap.LDAPUser;
35import org.apache.jetspeed.services.JetspeedLDAP;
36import org.apache.jetspeed.services.JetspeedSecurity;
37import org.apache.jetspeed.services.Profiler;
38import org.apache.jetspeed.services.PsmlManager;
39import org.apache.jetspeed.services.ldap.LDAPURL;
40import org.apache.jetspeed.services.logging.JetspeedLogFactoryService;
41import org.apache.jetspeed.services.logging.JetspeedLogger;
42import org.apache.jetspeed.services.rundata.JetspeedRunData;
43import org.apache.jetspeed.services.rundata.JetspeedRunDataService;
44import org.apache.jetspeed.services.security.CredentialsManagement;
45import org.apache.jetspeed.services.security.JetspeedSecurityException;
46import org.apache.jetspeed.services.security.JetspeedSecurityService;
47import org.apache.jetspeed.services.security.NotUniqueUserException;
48import org.apache.jetspeed.services.security.UnknownUserException;
49import org.apache.jetspeed.services.security.UserException;
50import org.apache.jetspeed.services.security.UserManagement;
5152// Turbine classes53import org.apache.turbine.services.InitializationException;
54import org.apache.turbine.services.TurbineBaseService;
55import org.apache.turbine.services.TurbineServices;
56import org.apache.turbine.services.resources.ResourceService;
57import org.apache.turbine.services.rundata.RunDataService;
5859/***60 *61 * @author <a href="mailto:ender@kilicoglu.nom.tr">Ender KILICOGLU</a>62 * @author <a href="mailto:sami.leino@netorek.fi">Sami Leino</a>63 *64 * @version $Id: LDAPUserManagement.java,v 1.10 2004/02/23 03:52:33 jford Exp $65 *66 */67publicclassLDAPUserManagementextends TurbineBaseService
68 implements UserManagement,
69CredentialsManagement70 {
71/***72 * Static initialization of the logger for this class73 */74privatestaticfinalJetspeedLogger logger = JetspeedLogFactoryService.getLogger(LDAPUserManagement.class.getName());
7576// Constants77privatefinalstatic String CONFIG_SECURE_PASSWORDS_KEY = "secure.passwords";
78privatefinalstatic String CONFIG_SECURE_PASSWORDS_ALGORITHM = "secure.passwords.algorithm";
79privatefinalstatic String CONFIG_SECURE_PASSWORDS_SUFFIX = "secure.passwords.suffix";
80privatefinalstatic String CONFIG_NEWUSER_ROLES = "newuser.roles";
81privatefinalstatic String[] DEFAULT_CONFIG_NEWUSER_ROLES = { "user" };
8283privatefinalstatic String[] ATTRS = { "ou", "userPassword", "uid", "mail", "sn", "givenName",
84"uidNumber", "name", "objectdata", "objectClass",
85"usergrouprole", "lastlogindate", "lastmodifieddate",
86"creationdate", "confirm", "disabled" };
8788// Class variables89protectedstaticboolean securePasswords = false;
90protectedstatic String passwordsAlgorithm = "crypt";
91protectedstatic String passwordsSuffix = "{crypt}";
9293// Instance variables94protectedJetspeedRunDataService runDataService = null;
95protected String roles[] = null;
9697///////////////////////////////////////////////////////////////////////////98// User Management Interfaces99100///////////////////////////////////////////////////////////////////////////101102/***103 * Retrieves a <code>JetspeedUser</code> given the primary principle.104 * The principal can be any valid Jetspeed Security Principal:105 * <code>org.apache.jetspeed.om.security.UserNamePrincipal</code>106 * <code>org.apache.jetspeed.om.security.UserIdPrincipal</code>107 *108 * The security service may optionally check the current user context109 * to determine if the requestor has permission to perform this action.110 *111 * @param principal a principal identity to be retrieved.112 *113 * @return a <code>JetspeedUser</code> associated to the principal identity.114 * @exception UserException when the security provider has a general failure retrieving a user.115 * @exception UnknownUserException when the security provider cannot match116 * the principal identity to a user.117 * @exception InsufficientPrivilegeException when the requestor is denied118 * due to insufficient privilege119 */120publicJetspeedUser getUser(Principal principal)
121 throws JetspeedSecurityException122 {
123 BasicAttributes attr = new BasicAttributes();
124 Vector userurls = new Vector();
125LDAPUser user = null;
126127try128 {
129 userurls = JetspeedLDAP.search(JetspeedLDAP.buildURL("ou=users"),
130"(&(uid="+principal.getName()+")(objectclass=jetspeeduser))", ATTRS, true);
131 }
132catch (Exception e)
133 {
134 logger.error( "Failed to retrieve user '" + principal.getName() + "'", e );
135thrownewUserException("Failed to retrieve user '" + principal.getName() + "'", e);
136 }
137138if (userurls.size() == 1)
139 {
140 user = newLDAPUser((LDAPURL) ((Vector)userurls.elementAt(0)).firstElement());
141return user;
142 }
143elseif(userurls.size() > 1)
144 {
145thrownewUserException("Multiple Users with same username '" + principal.getName() + "'");
146 }
147else148 {
149thrownewUnknownUserException("Unknown user '" + principal.getName() + "'");
150 }
151 }
152153/***154 * Retrieves a collection of all <code>JetspeedUser</code>s.155 * The security service may optionally check the current user context156 * to determine if the requestor has permission to perform this action.157 *158 * @return a collection of <code>JetspeedUser</code> entities.159 * @exception UserException when the security provider has a general failure retrieving users.160 * @exception InsufficientPrivilegeException when the requestor is denied due to insufficient privilege161 */162public Iterator getUsers()
163 throws JetspeedSecurityException164 {
165 String filter = "(objectclass=jetspeeduser)";
166return getUsersUsingLDAPSpecificFilter(filter, null);
167 }
168169/***170 * Retrieves a collection of <code>JetspeedUser</code>s filtered by a security171 * provider-specific query string. For example SQL, OQL, JDOQL.172 * The security service may optionally check the current user context173 * to determine if the requestor has permission to perform this action.174 *175 * @return a collection of <code>JetspeedUser</code> entities.176 * @exception UserException when the security provider has a general failure retrieving users.177 * @exception InsufficientPrivilegeException when the requestor is denied due to insufficient privilege178 *179 */180public Iterator getUsers(String filter)
181 throws JetspeedSecurityException182 {
183// String ldapFilter = convert(filter);184return getUsersUsingLDAPSpecificFilter(filter, null);
185 }
186187/***188 * Retrieves a collection of <code>JetspeedUser</code>s filtered by a security189 * provider-specific query string. For example SQL, OQL, JDOQL.190 * The security service may optionally check the current user context191 * to determine if the requestor has permission to perform this action.192 *193 * @return a collection of <code>JetspeedUser</code> entities.194 * @exception UserException when the security provider has a general failure retrieving users.195 * @exception InsufficientPrivilegeException when the requestor is denied due to insufficient privilege196 */197protected Iterator getUsersUsingLDAPSpecificFilter(String filter, String[] attributesToFetch)
198 throws JetspeedSecurityException199 {
200 String baseDN = "ou=users";
201 NamingEnumeration userEnum = null;
202 List resultList = new Vector(1024);
203204try205 {
206LDAPURL url = JetspeedLDAP.buildURL( baseDN );
207 DirContext ctx = JetspeedLDAP.getService().connect(url);
208 userEnum = JetspeedLDAP.search(ctx, url.getDN(), filter, attributesToFetch, JetspeedLDAP.getService().SUB);
209210while (userEnum.hasMoreElements())
211 {
212LDAPUser user = buildUser(((SearchResult)userEnum.nextElement()).getAttributes());
213 resultList.add( user );
214 }
215216 JetspeedLDAP.getService().checkAndCloseContext(ctx);
217 }
218catch ( Exception e )
219 {
220 logger.error( "Failed to retrieve user with filter:" + filter, e );
221thrownewUserException( "Failed to retrieve user with filter:" + filter, e );
222 }
223224return ( resultList.iterator() );
225 }
226227protectedLDAPUser buildUser(Attributes attributes)
228 {
229returnnewLDAPUser(attributes);
230 }
231232/***233 * Saves a <code>JetspeedUser</code>'s attributes into permanent storage.234 * The user's account is required to exist in the storage.235 * The security service may optionally check the current user context236 * to determine if the requestor has permission to perform this action.237 *238 * @exception UserException when the security provider has a general failure retrieving users.239 * @exception InsufficientPrivilegeException when the requestor is denied due to insufficient privilege240 */241publicvoid saveUser(JetspeedUser user)
242 throws JetspeedSecurityException243 {
244if(!accountExists(user, true))
245 {
246thrownewUnknownUserException("Cannot save user '" + user.getUserName() +
247"', User doesn't exist");
248 }
249try250 {
251 ((LDAPUser)user).update(false);
252 }
253catch(Exception e)
254 {
255 logger.error( "Failed to save user object ", e);
256thrownewUserException("Failed to save user object ", e);
257 }
258 }
259260/***261 * Adds a <code>JetspeedUser</code> into permanent storage.262 * The security service can throw a <code>NotUniqueUserException</code> when the public263 * credentials fail to meet the security provider-specific unique constraints.264 * The security service may optionally check the current user context265 * to determine if the requestor has permission to perform this action.266 *267 * @exception UserException when the security provider has a general failure retrieving users.268 * @exception NotUniqueUserException when the public credentials fail to meet269 * the security provider-specific unique constraints.270 * @exception InsufficientPrivilegeException when the requestor is denied due to insufficient privilege271 */272publicvoid addUser(JetspeedUser user)
273 throws JetspeedSecurityException274 {
275if(accountExists(user))
276 {
277thrownewNotUniqueUserException("The account '" +
278 user.getUserName() + "' already exists");
279 }
280281 String initialPassword = user.getPassword();
282 String encrypted = JetspeedSecurity.encryptPassword(initialPassword);
283 user.setPassword(encrypted);
284 ((LDAPUser)user).update(true);
285286 addDefaultPSML(user);
287 }
288289/*290 * A default PSML page is added for the user, and the Jetspeed default roles291 * are assigned to the new user.292 *293 * @param user The new user.294 * @throws295 */296protectedvoid addDefaultPSML(JetspeedUser user)
297 throws JetspeedSecurityException298 {
299for (int ix = 0; ix < roles.length; ix++)
300 {
301try302 {
303304 JetspeedSecurity.grantRole(user.getUserName(),
305 JetspeedSecurity.getRole(roles[ix]).getName());
306 }
307catch(Exception e)
308 {
309 logger.error("Could not grant role: " + roles[ix] + " to user " + user.getUserName(), e);
310 }
311 }
312try313 {
314JetspeedRunData rundata = getRunData();
315if (rundata != null)
316 {
317Profile profile = Profiler.createProfile();
318 profile.setUser(user);
319 profile.setMediaType("html");
320 Profiler.createProfile(getRunData(), profile);
321 }
322 }
323catch (Exception e)
324 {
325 logger.error( "Failed to create profile for new user ", e );
326 removeUser(newUserNamePrincipal(user.getUserName()));
327thrownewUserException("Failed to create profile for new user ", e);
328 }
329 }
330331/***332 * Removes a <code>JetspeedUser</code> from the permanent store.333 * The security service may optionally check the current user context334 * to determine if the requestor has permission to perform this action.335 *336 * @param principal the principal identity to be retrieved.337 * @exception UserException when the security provider has a general failure retrieving a user.338 * @exception UnknownUserException when the security provider cannot match339 * the principal identity to a user.340 * @exception InsufficientPrivilegeException when the requestor is denied due to insufficient privilege341 */342publicvoid removeUser(Principal principal)
343 throws JetspeedSecurityException344 {
345 BasicAttributes attr= new BasicAttributes();
346 Vector userurls = new Vector();
347LDAPUser user = (LDAPUser)getUser(principal);
348349try350 {
351 JetspeedLDAP.deleteEntry(user.getldapurl());
352 PsmlManager.removeUserDocuments(user);
353 }
354catch(Exception e)
355 {
356 logger.error( "Failed to remove account '" + user.getUserName() + "'", e );
357thrownewUserException("Failed to remove account '" +
358 user.getUserName() + "'", e);
359 }
360361 }
362363///////////////////////////////////////////////////////////////////////////364// Credentials Management365///////////////////////////////////////////////////////////////////////////366367/***368 * Allows for a user to change their own password.369 *370 * @param user the JetspeedUser to change password371 * @param oldPassword the current password supplied by the user.372 * @param newPassword the current password requested by the user.373 * @exception UserException when the security provider has a general failure retrieving a user.374 * @exception UnknownUserException when the security provider cannot match375 * the principal identity to a user.376 * @exception InsufficientPrivilegeException when the requestor is denied due to insufficient privilege377 */378publicvoid changePassword( JetspeedUser user,
379 String oldPassword,
380 String newPassword )
381 throws JetspeedSecurityException382 {
383 oldPassword = JetspeedSecurity.convertPassword(oldPassword);
384 newPassword = JetspeedSecurity.convertPassword(newPassword);
385386if(!accountExists(user))
387 {
388thrownewUnknownUserException("The account '" +
389 user.getUserName() + "' does not exist");
390 }
391elseif (!passwordsMatch(user, oldPassword))
392 {
393thrownewUserException(
394"The supplied old password for '" + user.getUserName() +
395"' was incorrect");
396 }
397398 String encrypted = JetspeedSecurity.encryptPassword( newPassword );
399 user.setPassword(encrypted);
400401// save the changes in the database immediately, to prevent the password402// being 'reverted' to the old value if the user data is lost somehow403// before it is saved at session's expiry.404 saveUser(user);
405 }
406407/***408 * Forcibly sets new password for a User.409 *410 * Provides an administrator the ability to change the forgotten or411 * compromised passwords. Certain implementatations of this feature412 * would require administrative level access to the authenticating413 * server / program.414 *415 * @param user the user to change the password for.416 * @param password the new password.417 *418 * @exception UserException when the security provider has a general419 * failure retrieving a user.420 * @exception UnknownUserException when the security provider cannot match421 * the principal identity to a user.422 * @exception InsufficientPrivilegeException when the requestor is423 * denied due to insufficient privilege424 */425publicvoid forcePassword( JetspeedUser user, String password )
426 throws JetspeedSecurityException427 {
428if(!accountExists(user))
429 {
430thrownewUnknownUserException("The account '" +
431 user.getUserName() + "' does not exist");
432 }
433434 String encrypted = JetspeedSecurity.encryptPassword( password );
435 user.setPassword(encrypted);
436437438// save the changes in the database immediately, to prevent the439// password being 'reverted' to the old value if the user data440// is lost somehow before it is saved at session's expiry.441 saveUser(user);
442 }
443444/***445 * This method provides client-side encryption of passwords.446 *447 * If <code>secure.passwords</code> are enabled in 448 * JetspeedSecurity.properties,449 * the password will be encrypted, if not, it will be returned unchanged.450 * The <code>secure.passwords.algorithm</code> property can be used451 * to chose which digest algorithm should be used for performing the452 * encryption. <code>SHA</code> is used by default.453 *454 * @param password the password to process455 * 456 * @return processed password457 *458 */459public String encryptPassword( String password )
460 throws JetspeedSecurityException461 {
462if (securePasswords == false)
463 {
464return password;
465 }
466elseif(password == null)
467 {
468returnnull;
469 }
470elseif(password.startsWith(passwordsSuffix))
471 {
472// A kludge473return password;
474 }
475476return passwordsSuffix + UnixCrypt.crypt(password);
477 }
478479/***480 * <p>Check's if user's current password matches with the481 * supplied password.</p>482 * 483 * @param user User whose password will be checked484 * @param suppliedPassword Password to match485 *486 * @return True if passwords match.487 *488 */489publicstaticboolean passwordsMatch(JetspeedUser user, String suppliedPassword)
490 {
491if (securePasswords == false)
492 {
493return user.getPassword().equals(suppliedPassword);
494 }
495else496 {
497return UnixCrypt.matches(user.getPassword().substring(passwordsSuffix.length()), suppliedPassword);
498 }
499 }
500501///////////////////////////////////////////////////////////////////////////502// Service Init503///////////////////////////////////////////////////////////////////////////504505/***506 * This is the early initialization method called by the507 * Turbine <code>Service</code> framework508 * @param conf The <code>ServletConfig</code>509 * @exception throws a <code>InitializationException</code> if the service510 * fails to initialize511 */512publicsynchronizedvoid init(ServletConfig conf)
513 throws InitializationException
514 {
515if (getInit()) return;
516517super.init(conf);
518519// get configuration parameters from Jetspeed Resources520 ResourceService serviceConf = ((TurbineServices)TurbineServices.getInstance())
521 .getResources(JetspeedSecurityService.SERVICE_NAME);
522523 securePasswords = serviceConf.getBoolean(CONFIG_SECURE_PASSWORDS_KEY,
524 securePasswords);
525 passwordsAlgorithm = serviceConf.getString(CONFIG_SECURE_PASSWORDS_ALGORITHM,
526 passwordsAlgorithm);
527 passwordsSuffix = serviceConf.getString(CONFIG_SECURE_PASSWORDS_SUFFIX,
528 passwordsSuffix);
529530try531 {
532 roles = serviceConf.getStringArray(CONFIG_NEWUSER_ROLES);
533 }
534catch (Exception e)
535 {
536 }
537538if (null == roles || roles.length == 0)
539 {
540 roles = DEFAULT_CONFIG_NEWUSER_ROLES;
541 }
542543this.runDataService = (JetspeedRunDataService)TurbineServices.getInstance()
544 .getService(RunDataService.SERVICE_NAME);
545546 setInit(true);
547 }
548549///////////////////////////////////////////////////////////////////////////550// Internal551///////////////////////////////////////////////////////////////////////////552553/***554 * Check whether a specified user's account exists.555 *556 * The login name is used for looking up the account.557 *558 * @param user The user to be checked.559 * @param checkUniqueId Make sure that we aren't 560 * overwriting another user561 * with different id.562 *563 * @return true If the specified account exists564 *565 * @throws UserException If there was a general db access error566 *567 */568protectedboolean accountExists( JetspeedUser user )
569 throws UserException570 {
571return accountExists(user, false);
572 }
573574protectedboolean accountExists( JetspeedUser user, boolean checkUniqueId )
575 throws UserException576 {
577UserNamePrincipal principal = newUserNamePrincipal(user.getUserName());
578579try580 {
581 getUser(principal);
582returntrue;
583 }
584catch (Exception e)
585 {
586return false;
587 }
588 }
589590protectedJetspeedRunData getRunData()
591 {
592JetspeedRunData rundata = null;
593594if (this.runDataService != null)
595 {
596 rundata = this.runDataService.getCurrentRunData();
597 }
598599return rundata;
600 }
601602 }