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