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