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 package org.apache.wiki.auth; 020 021 import java.security.Permission; 022 import java.security.Principal; 023 import java.text.MessageFormat; 024 import java.util.List; 025 import java.util.Locale; 026 import java.util.Map; 027 import java.util.Properties; 028 import java.util.ResourceBundle; 029 import java.util.WeakHashMap; 030 031 import javax.mail.MessagingException; 032 import javax.mail.internet.AddressException; 033 import javax.servlet.http.HttpServletRequest; 034 035 import org.apache.log4j.Logger; 036 import org.apache.wiki.WikiContext; 037 import org.apache.wiki.WikiEngine; 038 import org.apache.wiki.WikiSession; 039 import org.apache.wiki.api.engine.FilterManager; 040 import org.apache.wiki.api.exceptions.NoRequiredPropertyException; 041 import org.apache.wiki.api.exceptions.WikiException; 042 import org.apache.wiki.api.filters.PageFilter; 043 import org.apache.wiki.auth.permissions.AllPermission; 044 import org.apache.wiki.auth.permissions.WikiPermission; 045 import org.apache.wiki.auth.user.AbstractUserDatabase; 046 import org.apache.wiki.auth.user.DuplicateUserException; 047 import org.apache.wiki.auth.user.UserDatabase; 048 import org.apache.wiki.auth.user.UserProfile; 049 import org.apache.wiki.event.WikiEventListener; 050 import org.apache.wiki.event.WikiEventManager; 051 import org.apache.wiki.event.WikiSecurityEvent; 052 import org.apache.wiki.filters.SpamFilter; 053 import org.apache.wiki.i18n.InternationalizationManager; 054 import org.apache.wiki.preferences.Preferences; 055 import org.apache.wiki.rpc.RPCCallable; 056 import org.apache.wiki.rpc.json.JSONRPCManager; 057 import org.apache.wiki.ui.InputValidator; 058 import org.apache.wiki.util.ClassUtil; 059 import org.apache.wiki.util.MailUtil; 060 import org.apache.wiki.util.TextUtil; 061 import org.apache.wiki.workflow.Decision; 062 import org.apache.wiki.workflow.DecisionRequiredException; 063 import org.apache.wiki.workflow.Fact; 064 import org.apache.wiki.workflow.Outcome; 065 import org.apache.wiki.workflow.Task; 066 import org.apache.wiki.workflow.Workflow; 067 import org.apache.wiki.workflow.WorkflowBuilder; 068 069 070 /** 071 * Provides a facade for obtaining user information. 072 * @since 2.3 073 */ 074 public class UserManager { 075 076 private static final String USERDATABASE_PACKAGE = "org.apache.wiki.auth.user"; 077 private static final String SESSION_MESSAGES = "profile"; 078 private static final String PARAM_EMAIL = "email"; 079 private static final String PARAM_FULLNAME = "fullname"; 080 private static final String PARAM_PASSWORD = "password"; 081 private static final String PARAM_LOGINNAME = "loginname"; 082 private static final String UNKNOWN_CLASS = "<unknown>"; 083 084 private WikiEngine m_engine; 085 086 private static Logger log = Logger.getLogger(UserManager.class); 087 088 /** Message key for the "save profile" message. */ 089 public static final String SAVE_APPROVER = "workflow.createUserProfile"; 090 private static final String PROP_DATABASE = "jspwiki.userdatabase"; 091 protected static final String SAVE_TASK_MESSAGE_KEY = "task.createUserProfile"; 092 protected static final String SAVED_PROFILE = "userProfile"; 093 protected static final String SAVE_DECISION_MESSAGE_KEY = "decision.createUserProfile"; 094 protected static final String FACT_SUBMITTER = "fact.submitter"; 095 protected static final String PREFS_LOGIN_NAME = "prefs.loginname"; 096 protected static final String PREFS_FULL_NAME = "prefs.fullname"; 097 protected static final String PREFS_EMAIL = "prefs.email"; 098 099 // private static final String PROP_ACLMANAGER = "jspwiki.aclManager"; 100 101 /** Associates wiki sessions with profiles */ 102 private final Map<WikiSession,UserProfile> m_profiles = new WeakHashMap<WikiSession,UserProfile>(); 103 104 /** The user database loads, manages and persists user identities */ 105 private UserDatabase m_database; 106 107 private boolean m_useJAAS = true; 108 109 /** 110 * Constructs a new UserManager instance. 111 */ 112 public UserManager() 113 { 114 } 115 116 /** 117 * Initializes the engine for its nefarious purposes. 118 * @param engine the current wiki engine 119 * @param props the wiki engine initialization properties 120 */ 121 @SuppressWarnings("deprecation") 122 public void initialize( WikiEngine engine, Properties props ) 123 { 124 m_engine = engine; 125 126 m_useJAAS = AuthenticationManager.SECURITY_JAAS.equals( props.getProperty(AuthenticationManager.PROP_SECURITY, AuthenticationManager.SECURITY_JAAS ) ); 127 128 // Attach the PageManager as a listener 129 // TODO: it would be better if we did this in PageManager directly 130 addWikiEventListener( engine.getPageManager() ); 131 132 JSONRPCManager.registerGlobalObject( "users", new JSONUserModule(this), new AllPermission(null) ); 133 } 134 135 /** 136 * Returns the UserDatabase employed by this WikiEngine. The UserDatabase is 137 * lazily initialized by this method, if it does not exist yet. If the 138 * initialization fails, this method will use the inner class 139 * DummyUserDatabase as a default (which is enough to get JSPWiki running). 140 * @return the dummy user database 141 * @since 2.3 142 */ 143 public UserDatabase getUserDatabase() 144 { 145 // FIXME: Must not throw RuntimeException, but something else. 146 if( m_database != null ) 147 { 148 return m_database; 149 } 150 151 if( !m_useJAAS ) 152 { 153 m_database = new DummyUserDatabase(); 154 return m_database; 155 } 156 157 String dbClassName = UNKNOWN_CLASS; 158 159 try 160 { 161 dbClassName = TextUtil.getRequiredProperty( m_engine.getWikiProperties(), 162 PROP_DATABASE ); 163 164 log.info("Attempting to load user database class "+dbClassName); 165 Class<?> dbClass = ClassUtil.findClass( USERDATABASE_PACKAGE, dbClassName ); 166 m_database = (UserDatabase) dbClass.newInstance(); 167 m_database.initialize( m_engine, m_engine.getWikiProperties() ); 168 log.info("UserDatabase initialized."); 169 } 170 catch( NoRequiredPropertyException e ) 171 { 172 log.error( "You have not set the '"+PROP_DATABASE+"'. You need to do this if you want to enable user management by JSPWiki." ); 173 } 174 catch( ClassNotFoundException e ) 175 { 176 log.error( "UserDatabase class " + dbClassName + " cannot be found", e ); 177 } 178 catch( InstantiationException e ) 179 { 180 log.error( "UserDatabase class " + dbClassName + " cannot be created", e ); 181 } 182 catch( IllegalAccessException e ) 183 { 184 log.error( "You are not allowed to access this user database class", e ); 185 } 186 catch( WikiSecurityException e ) 187 { 188 log.error( "Exception initializing user database: " + e.getMessage() ); 189 } 190 finally 191 { 192 if( m_database == null ) 193 { 194 log.info("I could not create a database object you specified (or didn't specify), so I am falling back to a default."); 195 m_database = new DummyUserDatabase(); 196 } 197 } 198 199 return m_database; 200 } 201 202 /** 203 * <p>Retrieves the {@link org.apache.wiki.auth.user.UserProfile}for the 204 * user in a wiki session. If the user is authenticated, the UserProfile 205 * returned will be the one stored in the user database; if one does not 206 * exist, a new one will be initialized and returned. If the user is 207 * anonymous or asserted, the UserProfile will <i>always</i> be newly 208 * initialized to prevent spoofing of identities. If a UserProfile needs to 209 * be initialized, its 210 * {@link org.apache.wiki.auth.user.UserProfile#isNew()} method will 211 * return <code>true</code>, and its login name will will be set 212 * automatically if the user is authenticated. Note that this method does 213 * not modify the retrieved (or newly created) profile otherwise; other 214 * fields in the user profile may be <code>null</code>.</p> 215 * <p>If a new UserProfile was created, but its 216 * {@link org.apache.wiki.auth.user.UserProfile#isNew()} method returns 217 * <code>false</code>, this method throws an {@link IllegalStateException}. 218 * This is meant as a quality check for UserDatabase providers; 219 * it should only be thrown if the implementation is faulty.</p> 220 * @param session the wiki session, which may not be <code>null</code> 221 * @return the user's profile, which will be newly initialized if the user 222 * is anonymous or asserted, or if the user cannot be found in the user 223 * database 224 */ 225 public UserProfile getUserProfile( WikiSession session ) 226 { 227 // Look up cached user profile 228 UserProfile profile = m_profiles.get( session ); 229 boolean newProfile = profile == null; 230 Principal user = null; 231 232 // If user is authenticated, figure out if this is an existing profile 233 if ( session.isAuthenticated() ) 234 { 235 user = session.getUserPrincipal(); 236 try 237 { 238 profile = getUserDatabase().find( user.getName() ); 239 newProfile = false; 240 } 241 catch( NoSuchPrincipalException e ) 242 { 243 } 244 } 245 246 if ( newProfile ) 247 { 248 profile = getUserDatabase().newProfile(); 249 if ( user != null ) 250 { 251 profile.setLoginName( user.getName() ); 252 } 253 if ( !profile.isNew() ) 254 { 255 throw new IllegalStateException( 256 "New profile should be marked 'new'. Check your UserProfile implementation." ); 257 } 258 } 259 260 // Stash the profile for next time 261 m_profiles.put( session, profile ); 262 return profile; 263 } 264 265 /** 266 * <p> 267 * Saves the {@link org.apache.wiki.auth.user.UserProfile}for the user in 268 * a wiki session. This method verifies that a user profile to be saved 269 * doesn't collide with existing profiles; that is, the login name 270 * or full name is already used by another profile. If the profile 271 * collides, a <code>DuplicateUserException</code> is thrown. After saving 272 * the profile, the user database changes are committed, and the user's 273 * credential set is refreshed; if custom authentication is used, this means 274 * the user will be automatically be logged in. 275 * </p> 276 * <p> 277 * When the user's profile is saved successfully, this method fires a 278 * {@link WikiSecurityEvent#PROFILE_SAVE} event with the WikiSession as the 279 * source and the UserProfile as target. For existing profiles, if the 280 * user's full name changes, this method also fires a "name changed" 281 * event ({@link WikiSecurityEvent#PROFILE_NAME_CHANGED}) with the 282 * WikiSession as the source and an array containing the old and new 283 * UserProfiles, respectively. The <code>NAME_CHANGED</code> event allows 284 * the GroupManager and PageManager can change group memberships and 285 * ACLs if needed. 286 * </p> 287 * <p> 288 * Note that WikiSessions normally attach event listeners to the 289 * UserManager, so changes to the profile will automatically cause the 290 * correct Principals to be reloaded into the current WikiSession's Subject. 291 * </p> 292 * @param session the wiki session, which may not be <code>null</code> 293 * @param profile the user profile, which may not be <code>null</code> 294 * @throws DuplicateUserException if the proposed profile's login name or full name collides with another 295 * @throws WikiException if the save fails for some reason. If the current user does not have 296 * permission to save the profile, this will be a {@link org.apache.wiki.auth.WikiSecurityException}; 297 * if if the user profile must be approved before it can be saved, it will be a 298 * {@link org.apache.wiki.workflow.DecisionRequiredException}. All other WikiException 299 * indicate a condition that is not normal is probably due to mis-configuration 300 */ 301 public void setUserProfile( WikiSession session, UserProfile profile ) throws DuplicateUserException, WikiException 302 { 303 // Verify user is allowed to save profile! 304 Permission p = new WikiPermission( m_engine.getApplicationName(), WikiPermission.EDIT_PROFILE_ACTION ); 305 if ( !m_engine.getAuthorizationManager().checkPermission( session, p ) ) 306 { 307 throw new WikiSecurityException( "You are not allowed to save wiki profiles." ); 308 } 309 310 // Check if profile is new, and see if container allows creation 311 boolean newProfile = profile.isNew(); 312 313 // Check if another user profile already has the fullname or loginname 314 UserProfile oldProfile = getUserProfile( session ); 315 boolean nameChanged = ( oldProfile == null || oldProfile.getFullname() == null ) 316 ? false 317 : !( oldProfile.getFullname().equals( profile.getFullname() ) && 318 oldProfile.getLoginName().equals( profile.getLoginName() ) ); 319 UserProfile otherProfile; 320 try 321 { 322 otherProfile = getUserDatabase().findByLoginName( profile.getLoginName() ); 323 if ( otherProfile != null && !otherProfile.equals( oldProfile ) ) 324 { 325 throw new DuplicateUserException( "security.error.login.taken", profile.getLoginName() ); 326 } 327 } 328 catch( NoSuchPrincipalException e ) 329 { 330 } 331 try 332 { 333 otherProfile = getUserDatabase().findByFullName( profile.getFullname() ); 334 if ( otherProfile != null && !otherProfile.equals( oldProfile ) ) 335 { 336 throw new DuplicateUserException( "security.error.fullname.taken", profile.getFullname() ); 337 } 338 } 339 catch( NoSuchPrincipalException e ) 340 { 341 } 342 343 // For new accounts, create approval workflow for user profile save. 344 if ( newProfile && oldProfile != null && oldProfile.isNew() ) 345 { 346 WorkflowBuilder builder = WorkflowBuilder.getBuilder( m_engine ); 347 Principal submitter = session.getUserPrincipal(); 348 Task completionTask = new SaveUserProfileTask( m_engine, session.getLocale() ); 349 350 // Add user profile attribute as Facts for the approver (if required) 351 boolean hasEmail = profile.getEmail() != null; 352 Fact[] facts = new Fact[ hasEmail ? 4 : 3]; 353 facts[0] = new Fact( PREFS_FULL_NAME, profile.getFullname() ); 354 facts[1] = new Fact( PREFS_LOGIN_NAME, profile.getLoginName() ); 355 facts[2] = new Fact( FACT_SUBMITTER, submitter.getName() ); 356 if ( hasEmail ) 357 { 358 facts[3] = new Fact( PREFS_EMAIL, profile.getEmail() ); 359 } 360 Workflow workflow = builder.buildApprovalWorkflow( submitter, 361 SAVE_APPROVER, 362 null, 363 SAVE_DECISION_MESSAGE_KEY, 364 facts, 365 completionTask, 366 null ); 367 368 workflow.setAttribute( SAVED_PROFILE, profile ); 369 m_engine.getWorkflowManager().start(workflow); 370 371 boolean approvalRequired = workflow.getCurrentStep() instanceof Decision; 372 373 // If the profile requires approval, redirect user to message page 374 if ( approvalRequired ) 375 { 376 throw new DecisionRequiredException( "This profile must be approved before it becomes active" ); 377 } 378 379 // If the profile doesn't need approval, then just log the user in 380 381 try 382 { 383 AuthenticationManager mgr = m_engine.getAuthenticationManager(); 384 if ( newProfile && !mgr.isContainerAuthenticated() ) 385 { 386 mgr.login( session, null, profile.getLoginName(), profile.getPassword() ); 387 } 388 } 389 catch ( WikiException e ) 390 { 391 throw new WikiSecurityException( e.getMessage(), e ); 392 } 393 394 // Alert all listeners that the profile changed... 395 // ...this will cause credentials to be reloaded in the wiki session 396 fireEvent( WikiSecurityEvent.PROFILE_SAVE, session, profile ); 397 } 398 399 // For existing accounts, just save the profile 400 else 401 { 402 // If login name changed, rename it first 403 if ( nameChanged && oldProfile != null && !oldProfile.getLoginName().equals( profile.getLoginName() ) ) 404 { 405 getUserDatabase().rename( oldProfile.getLoginName(), profile.getLoginName() ); 406 } 407 408 // Now, save the profile (userdatabase will take care of timestamps for us) 409 getUserDatabase().save( profile ); 410 411 if ( nameChanged ) 412 { 413 // Fire an event if the login name or full name changed 414 UserProfile[] profiles = new UserProfile[] { oldProfile, profile }; 415 fireEvent( WikiSecurityEvent.PROFILE_NAME_CHANGED, session, profiles ); 416 } 417 else 418 { 419 // Fire an event that says we have new a new profile (new principals) 420 fireEvent( WikiSecurityEvent.PROFILE_SAVE, session, profile ); 421 } 422 } 423 } 424 425 /** 426 * <p> Extracts user profile parameters from the HTTP request and populates 427 * a UserProfile with them. The UserProfile will either be a copy of the 428 * user's existing profile (if one can be found), or a new profile (if not). 429 * The rules for populating the profile as as follows: </p> <ul> <li>If the 430 * <code>email</code> or <code>password</code> parameter values differ 431 * from those in the existing profile, the passed parameters override the 432 * old values.</li> <li>For new profiles, the user-supplied 433 * <code>fullname</code> parameter is always 434 * used; for existing profiles the existing value is used, and whatever 435 * value the user supplied is discarded. The wiki name is automatically 436 * computed by taking the full name and extracting all whitespace.</li> 437 * <li>In all cases, the 438 * created/last modified timestamps of the user's existing or new profile 439 * always override whatever values the user supplied.</li> <li>If 440 * container authentication is used, the login name property of the profile 441 * is set to the name of 442 * {@link org.apache.wiki.WikiSession#getLoginPrincipal()}. Otherwise, 443 * the value of the <code>loginname</code> parameter is used.</li> </ul> 444 * @param context the current wiki context 445 * @return a new, populated user profile 446 */ 447 public UserProfile parseProfile( WikiContext context ) 448 { 449 // Retrieve the user's profile (may have been previously cached) 450 UserProfile profile = getUserProfile( context.getWikiSession() ); 451 HttpServletRequest request = context.getHttpRequest(); 452 453 // Extract values from request stream (cleanse whitespace as needed) 454 String loginName = request.getParameter( PARAM_LOGINNAME ); 455 String password = request.getParameter( PARAM_PASSWORD ); 456 String fullname = request.getParameter( PARAM_FULLNAME ); 457 String email = request.getParameter( PARAM_EMAIL ); 458 loginName = InputValidator.isBlank( loginName ) ? null : loginName; 459 password = InputValidator.isBlank( password ) ? null : password; 460 fullname = InputValidator.isBlank( fullname ) ? null : fullname; 461 email = InputValidator.isBlank( email ) ? null : email; 462 463 // A special case if we have container authentication 464 // If authenticated, login name is always taken from container 465 if ( m_engine.getAuthenticationManager().isContainerAuthenticated() && 466 context.getWikiSession().isAuthenticated() ) 467 { 468 loginName = context.getWikiSession().getLoginPrincipal().getName(); 469 } 470 471 // Set the profile fields! 472 profile.setLoginName( loginName ); 473 profile.setEmail( email ); 474 profile.setFullname( fullname ); 475 profile.setPassword( password ); 476 return profile; 477 } 478 479 /** 480 * Validates a user profile, and appends any errors to the session errors 481 * list. If the profile is new, the password will be checked to make sure it 482 * isn't null. Otherwise, the password is checked for length and that it 483 * matches the value of the 'password2' HTTP parameter. Note that we have a 484 * special case when container-managed authentication is used and the user 485 * is not authenticated; this will always cause validation to fail. Any 486 * validation errors are added to the wiki session's messages collection 487 * (see {@link WikiSession#getMessages()}. 488 * @param context the current wiki context 489 * @param profile the supplied UserProfile 490 */ 491 public void validateProfile( WikiContext context, UserProfile profile ) 492 { 493 boolean isNew = profile.isNew(); 494 WikiSession session = context.getWikiSession(); 495 InputValidator validator = new InputValidator( SESSION_MESSAGES, context ); 496 ResourceBundle rb = Preferences.getBundle( context, InternationalizationManager.CORE_BUNDLE ); 497 498 // 499 // Query the SpamFilter first 500 // 501 FilterManager fm = m_engine.getFilterManager(); 502 List<PageFilter> ls = fm.getFilterList(); 503 for( PageFilter pf : ls ) 504 { 505 if( pf instanceof SpamFilter ) 506 { 507 if( ((SpamFilter)pf).isValidUserProfile( context, profile ) == false ) 508 { 509 session.addMessage( SESSION_MESSAGES, "Invalid userprofile" ); 510 return; 511 } 512 break; 513 } 514 } 515 516 // If container-managed auth and user not logged in, throw an error 517 if ( m_engine.getAuthenticationManager().isContainerAuthenticated() 518 && !context.getWikiSession().isAuthenticated() ) 519 { 520 session.addMessage( SESSION_MESSAGES, rb.getString("security.error.createprofilebeforelogin") ); 521 } 522 523 validator.validateNotNull( profile.getLoginName(), rb.getString("security.user.loginname") ); 524 validator.validateNotNull( profile.getFullname(), rb.getString("security.user.fullname") ); 525 validator.validate( profile.getEmail(), rb.getString("security.user.email"), InputValidator.EMAIL ); 526 527 // If new profile, passwords must match and can't be null 528 if ( !m_engine.getAuthenticationManager().isContainerAuthenticated() ) 529 { 530 String password = profile.getPassword(); 531 if ( password == null ) 532 { 533 if ( isNew ) 534 { 535 session.addMessage( SESSION_MESSAGES, rb.getString("security.error.blankpassword") ); 536 } 537 } 538 else 539 { 540 HttpServletRequest request = context.getHttpRequest(); 541 String password2 = ( request == null ) ? null : request.getParameter( "password2" ); 542 if ( !password.equals( password2 ) ) 543 { 544 session.addMessage( SESSION_MESSAGES, rb.getString("security.error.passwordnomatch") ); 545 } 546 } 547 } 548 549 UserProfile otherProfile; 550 String fullName = profile.getFullname(); 551 String loginName = profile.getLoginName(); 552 553 // It's illegal to use as a full name someone else's login name 554 try 555 { 556 otherProfile = getUserDatabase().find( fullName ); 557 if ( otherProfile != null && !profile.equals( otherProfile ) && !fullName.equals( otherProfile.getFullname() ) ) 558 { 559 Object[] args = { fullName }; 560 session.addMessage( SESSION_MESSAGES, MessageFormat.format( rb.getString("security.error.illegalfullname"), 561 args ) ); 562 } 563 } 564 catch ( NoSuchPrincipalException e) 565 { /* It's clean */ } 566 567 // It's illegal to use as a login name someone else's full name 568 try 569 { 570 otherProfile = getUserDatabase().find( loginName ); 571 if ( otherProfile != null && !profile.equals( otherProfile ) && !loginName.equals( otherProfile.getLoginName() ) ) 572 { 573 Object[] args = { loginName }; 574 session.addMessage( SESSION_MESSAGES, MessageFormat.format( rb.getString("security.error.illegalloginname"), 575 args ) ); 576 } 577 } 578 catch ( NoSuchPrincipalException e) 579 { /* It's clean */ } 580 } 581 582 /** 583 * A helper method for returning all of the known WikiNames in this system. 584 * 585 * @return An Array of Principals 586 * @throws WikiSecurityException If for reason the names cannot be fetched 587 */ 588 public Principal[] listWikiNames() 589 throws WikiSecurityException 590 { 591 return getUserDatabase().getWikiNames(); 592 } 593 594 /** 595 * This is a database that gets used if nothing else is available. It does 596 * nothing of note - it just mostly throws NoSuchPrincipalExceptions if 597 * someone tries to log in. 598 */ 599 public static class DummyUserDatabase extends AbstractUserDatabase 600 { 601 602 /** 603 * No-op. 604 * @throws WikiSecurityException never... 605 */ 606 public void commit() throws WikiSecurityException 607 { 608 // No operation 609 } 610 611 /** 612 * No-op. 613 * @param loginName the login name to delete 614 * @throws WikiSecurityException never... 615 */ 616 public void deleteByLoginName( String loginName ) throws WikiSecurityException 617 { 618 // No operation 619 } 620 621 /** 622 * No-op; always throws <code>NoSuchPrincipalException</code>. 623 * @param index the name to search for 624 * @return the user profile 625 * @throws NoSuchPrincipalException never... 626 */ 627 public UserProfile findByEmail(String index) throws NoSuchPrincipalException 628 { 629 throw new NoSuchPrincipalException("No user profiles available"); 630 } 631 632 /** 633 * No-op; always throws <code>NoSuchPrincipalException</code>. 634 * @param index the name to search for 635 * @return the user profile 636 * @throws NoSuchPrincipalException never... 637 */ 638 public UserProfile findByFullName(String index) throws NoSuchPrincipalException 639 { 640 throw new NoSuchPrincipalException("No user profiles available"); 641 } 642 643 /** 644 * No-op; always throws <code>NoSuchPrincipalException</code>. 645 * @param index the name to search for 646 * @return the user profile 647 * @throws NoSuchPrincipalException never... 648 */ 649 public UserProfile findByLoginName(String index) throws NoSuchPrincipalException 650 { 651 throw new NoSuchPrincipalException("No user profiles available"); 652 } 653 654 /** 655 * No-op; always throws <code>NoSuchPrincipalException</code>. 656 * @param uid the unique identifier to search for 657 * @return the user profile 658 * @throws NoSuchPrincipalException never... 659 */ 660 public UserProfile findByUid( String uid ) throws NoSuchPrincipalException 661 { 662 throw new NoSuchPrincipalException("No user profiles available"); 663 } 664 /** 665 * No-op; always throws <code>NoSuchPrincipalException</code>. 666 * @param index the name to search for 667 * @return the user profile 668 * @throws NoSuchPrincipalException never... 669 */ 670 public UserProfile findByWikiName(String index) throws NoSuchPrincipalException 671 { 672 throw new NoSuchPrincipalException("No user profiles available"); 673 } 674 675 /** 676 * No-op. 677 * @return a zero-length array 678 * @throws WikiSecurityException never... 679 */ 680 public Principal[] getWikiNames() throws WikiSecurityException 681 { 682 return new Principal[0]; 683 } 684 685 /** 686 * No-op. 687 * 688 * @param engine the wiki engine 689 * @param props the properties used to initialize the wiki engine 690 * @throws NoRequiredPropertyException never... 691 */ 692 public void initialize(WikiEngine engine, Properties props) throws NoRequiredPropertyException 693 { 694 } 695 696 /** 697 * No-op; always throws <code>NoSuchPrincipalException</code>. 698 * @param loginName the login name 699 * @param newName the proposed new login name 700 * @throws DuplicateUserException never... 701 * @throws WikiSecurityException never... 702 */ 703 public void rename( String loginName, String newName ) throws DuplicateUserException, WikiSecurityException 704 { 705 throw new NoSuchPrincipalException("No user profiles available"); 706 } 707 708 /** 709 * No-op. 710 * @param profile the user profile 711 * @throws WikiSecurityException never... 712 */ 713 public void save( UserProfile profile ) throws WikiSecurityException 714 { 715 } 716 717 } 718 719 // workflow task inner classes.................................................... 720 721 /** 722 * Inner class that handles the actual profile save action. Instances 723 * of this class are assumed to have been added to an approval workflow via 724 * {@link org.apache.wiki.workflow.WorkflowBuilder#buildApprovalWorkflow(Principal, String, Task, String, org.apache.wiki.workflow.Fact[], Task, String)}; 725 * they will not function correctly otherwise. 726 * 727 */ 728 public static class SaveUserProfileTask extends Task 729 { 730 private static final long serialVersionUID = 6994297086560480285L; 731 private final UserDatabase m_db; 732 private final WikiEngine m_engine; 733 private final Locale m_loc; 734 735 /** 736 * Constructs a new Task for saving a user profile. 737 * @param engine the wiki engine 738 * @deprecated will be removed in 2.10 scope. Consider using 739 * {@link #SaveUserProfileTask(WikiEngine, Locale)} instead 740 */ 741 @Deprecated 742 public SaveUserProfileTask( WikiEngine engine ) 743 { 744 super( SAVE_TASK_MESSAGE_KEY ); 745 m_engine = engine; 746 m_db = engine.getUserManager().getUserDatabase(); 747 m_loc = null; 748 } 749 750 public SaveUserProfileTask( WikiEngine engine, Locale loc ) 751 { 752 super( SAVE_TASK_MESSAGE_KEY ); 753 m_engine = engine; 754 m_db = engine.getUserManager().getUserDatabase(); 755 m_loc = loc; 756 } 757 758 /** 759 * Saves the user profile to the user database. 760 * @return {@link org.apache.wiki.workflow.Outcome#STEP_COMPLETE} if the 761 * task completed successfully 762 * @throws WikiException if the save did not complete for some reason 763 */ 764 public Outcome execute() throws WikiException 765 { 766 // Retrieve user profile 767 UserProfile profile = (UserProfile) getWorkflow().getAttribute( SAVED_PROFILE ); 768 769 // Save the profile (userdatabase will take care of timestamps for us) 770 m_db.save( profile ); 771 772 // Send e-mail if user supplied an e-mail address 773 if ( profile.getEmail() != null ) 774 { 775 try 776 { 777 InternationalizationManager i18n = m_engine.getInternationalizationManager(); 778 String app = m_engine.getApplicationName(); 779 String to = profile.getEmail(); 780 String subject = i18n.get( InternationalizationManager.DEF_TEMPLATE, m_loc, 781 "notification.createUserProfile.accept.subject", app ); 782 783 String content = i18n.get( InternationalizationManager.DEF_TEMPLATE, m_loc, 784 "notification.createUserProfile.accept.content", app, 785 profile.getLoginName(), 786 profile.getFullname(), 787 profile.getEmail(), 788 m_engine.getURL( WikiContext.LOGIN, null, null, true ) ); 789 MailUtil.sendMessage( m_engine.getWikiProperties(), to, subject, content); 790 } 791 catch ( AddressException e) 792 { 793 } 794 catch ( MessagingException me ) 795 { 796 log.error( "Could not send registration confirmation e-mail. Is the e-mail server running?", me ); 797 } 798 } 799 800 return Outcome.STEP_COMPLETE; 801 } 802 } 803 804 // events processing ....................................................... 805 806 /** 807 * Registers a WikiEventListener with this instance. 808 * This is a convenience method. 809 * @param listener the event listener 810 */ 811 public synchronized void addWikiEventListener( WikiEventListener listener ) 812 { 813 WikiEventManager.addWikiEventListener( this, listener ); 814 } 815 816 /** 817 * Un-registers a WikiEventListener with this instance. 818 * This is a convenience method. 819 * @param listener the event listener 820 */ 821 public synchronized void removeWikiEventListener( WikiEventListener listener ) 822 { 823 WikiEventManager.removeWikiEventListener( this, listener ); 824 } 825 826 /** 827 * Fires a WikiSecurityEvent of the provided type, Principal and target Object 828 * to all registered listeners. 829 * 830 * @see org.apache.wiki.event.WikiSecurityEvent 831 * @param type the event type to be fired 832 * @param session the wiki session supporting the event 833 * @param profile the user profile (or array of user profiles), which may be <code>null</code> 834 */ 835 protected void fireEvent( int type, WikiSession session, Object profile ) 836 { 837 if ( WikiEventManager.isListening(this) ) 838 { 839 WikiEventManager.fireEvent(this,new WikiSecurityEvent(session,type,profile)); 840 } 841 } 842 843 /** 844 * Implements the JSON API for usermanager. 845 * <p> 846 * Even though this gets serialized whenever container shuts down/restarts, 847 * this gets reinstalled to the session when JSPWiki starts. This means 848 * that it's not actually necessary to save anything. 849 */ 850 public static final class JSONUserModule implements RPCCallable 851 { 852 private volatile UserManager m_manager; 853 854 /** 855 * Create a new JSONUserModule. 856 * @param mgr Manager 857 */ 858 public JSONUserModule( UserManager mgr ) 859 { 860 m_manager = mgr; 861 } 862 863 /** 864 * Directly returns the UserProfile object attached to an uid. 865 * 866 * @param uid The user id (e.g. WikiName) 867 * @return A UserProfile object 868 * @throws NoSuchPrincipalException If such a name does not exist. 869 */ 870 public UserProfile getUserInfo( String uid ) 871 throws NoSuchPrincipalException 872 { 873 if( m_manager != null ) 874 { 875 UserProfile prof = m_manager.getUserDatabase().find( uid ); 876 877 return prof; 878 } 879 880 throw new IllegalStateException("The manager is offline."); 881 } 882 } 883 }