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