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; 020 021import java.security.AccessControlException; 022import java.security.Principal; 023import java.security.PrivilegedAction; 024import java.util.*; 025 026import javax.security.auth.Subject; 027import javax.servlet.http.HttpServletRequest; 028import javax.servlet.http.HttpSession; 029 030import org.apache.commons.lang.StringUtils; 031import org.apache.log4j.Logger; 032 033import org.apache.wiki.auth.*; 034import org.apache.wiki.auth.authorize.Group; 035import org.apache.wiki.auth.authorize.GroupManager; 036import org.apache.wiki.auth.authorize.Role; 037import org.apache.wiki.auth.user.UserDatabase; 038import org.apache.wiki.auth.user.UserProfile; 039import org.apache.wiki.event.WikiEvent; 040import org.apache.wiki.event.WikiEventListener; 041import org.apache.wiki.event.WikiSecurityEvent; 042 043/** 044 * <p>Represents a long-running wiki session, with an associated user Principal, 045 * user Subject, and authentication status. This class is initialized with 046 * minimal, default-deny values: authentication is set to <code>false</code>, 047 * and the user principal is set to <code>null</code>.</p> 048 * <p>The WikiSession class allows callers to:</p> 049 * <ul> 050 * <li>Obtain the authentication status of the user via 051 * {@link #isAnonymous()} and {@link #isAuthenticated()}</li> 052 * <li>Query the session for Principals representing the 053 * user's identity via {@link #getLoginPrincipal()}, 054 * {@link #getUserPrincipal()} and {@link #getPrincipals()}</li> 055 * <li>Store, retrieve and clear UI messages via 056 * {@link #addMessage(String)}, {@link #getMessages(String)} 057 * and {@link #clearMessages(String)}</li> 058 * </ul> 059 * <p>To keep track of the Principals each user posseses, each WikiSession 060 * stores a JAAS Subject. Various login processes add or remove Principals 061 * when users authenticate or log out.</p> 062 * <p>WikiSession implements the {@link org.apache.wiki.event.WikiEventListener} 063 * interface and listens for group add/change/delete events fired by 064 * event sources the WikiSession is registered with. Normally, 065 * {@link org.apache.wiki.auth.AuthenticationManager} registers each WikiSession 066 * with the {@link org.apache.wiki.auth.authorize.GroupManager} 067 * so it can catch group events. Thus, when a user is added to a 068 * {@link org.apache.wiki.auth.authorize.Group}, a corresponding 069 * {@link org.apache.wiki.auth.GroupPrincipal} is injected into 070 * the Subject's Principal set. Likewise, when the user is removed from 071 * the Group or the Group is deleted, the GroupPrincipal is removed 072 * from the Subject. The effect that this strategy produces is extremely 073 * beneficial: when someone adds a user to a wiki group, that user 074 * <em>immediately</em> gains the privileges associated with that 075 * group; he or she does not need to re-authenticate. 076 * </p> 077 * <p>In addition to methods for examining individual <code>WikiSession</code> 078 * objects, this class also contains a number of static methods for 079 * managing WikiSessions for an entire wiki. These methods allow callers 080 * to find, query and remove WikiSession objects, and 081 * to obtain a list of the current wiki session users.</p> 082 * <p>WikiSession encloses a protected static class, {@link SessionMonitor}, 083 * to keep track of WikiSessions registered with each wiki.</p> 084 */ 085public final class WikiSession implements WikiEventListener 086{ 087 088 /** An anonymous user's session status. */ 089 public static final String ANONYMOUS = "anonymous"; 090 091 /** An asserted user's session status. */ 092 public static final String ASSERTED = "asserted"; 093 094 /** An authenticated user's session status. */ 095 public static final String AUTHENTICATED = "authenticated"; 096 097 private static final int ONE = 48; 098 099 private static final int NINE = 57; 100 101 private static final int DOT = 46; 102 103 private static final Logger log = Logger.getLogger( WikiSession.class ); 104 105 private static final String ALL = "*"; 106 107 private static ThreadLocal<WikiSession> c_guestSession = new ThreadLocal<WikiSession>(); 108 109 private final Subject m_subject = new Subject(); 110 111 private final Map<String,Set<String>> m_messages = new HashMap<String,Set<String>>(); 112 113 /** The WikiEngine that created this session. */ 114 private WikiEngine m_engine = null; 115 116 private String m_status = ANONYMOUS; 117 118 private Principal m_userPrincipal = WikiPrincipal.GUEST; 119 120 private Principal m_loginPrincipal = WikiPrincipal.GUEST; 121 122 private Locale m_cachedLocale = Locale.getDefault(); 123 124 /** 125 * Returns <code>true</code> if one of this WikiSession's user Principals 126 * can be shown to belong to a particular wiki group. If the user is 127 * not authenticated, this method will always return <code>false</code>. 128 * @param group the group to test 129 * @return the result 130 */ 131 protected boolean isInGroup( Group group ) 132 { 133 for ( Principal principal : getPrincipals() ) 134 { 135 if ( isAuthenticated() && group.isMember( principal ) ) 136 { 137 return true; 138 } 139 } 140 return false; 141 } 142 143 /** 144 * Private constructor to prevent WikiSession from being instantiated 145 * directly. 146 */ 147 private WikiSession() 148 { 149 } 150 151 /** 152 * Returns <code>true</code> if the user is considered asserted via 153 * a session cookie; that is, the Subject contains the Principal 154 * Role.ASSERTED. 155 * @return Returns <code>true</code> if the user is asserted 156 */ 157 public boolean isAsserted() 158 { 159 return m_subject.getPrincipals().contains( Role.ASSERTED ); 160 } 161 162 /** 163 * Returns the authentication status of the user's session. The user is 164 * considered authenticated if the Subject contains the Principal 165 * Role.AUTHENTICATED. If this method determines that an earlier 166 * LoginModule did not inject Role.AUTHENTICATED, it will inject one 167 * if the user is not anonymous <em>and</em> not asserted. 168 * @return Returns <code>true</code> if the user is authenticated 169 */ 170 public boolean isAuthenticated() 171 { 172 // If Role.AUTHENTICATED is in principals set, always return true. 173 if ( m_subject.getPrincipals().contains( Role.AUTHENTICATED ) ) 174 { 175 return true; 176 } 177 178 // With non-JSPWiki LoginModules, the role may not be there, so 179 // we need to add it if the user really is authenticated. 180 if ( !isAnonymous() && !isAsserted() ) 181 { 182 // Inject AUTHENTICATED role 183 m_subject.getPrincipals().add( Role.AUTHENTICATED ); 184 return true; 185 } 186 187 return false; 188 } 189 190 /** 191 * <p>Determines whether the current session is anonymous. This will be 192 * true if any of these conditions are true:</p> 193 * <ul> 194 * <li>The session's Principal set contains 195 * {@link org.apache.wiki.auth.authorize.Role#ANONYMOUS}</li> 196 * <li>The session's Principal set contains 197 * {@link org.apache.wiki.auth.WikiPrincipal#GUEST}</li> 198 * <li>The Principal returned by {@link #getUserPrincipal()} evaluates 199 * to an IP address.</li> 200 * </ul> 201 * <p>The criteria above are listed in the order in which they are 202 * evaluated.</p> 203 * @return whether the current user's identity is equivalent to an IP 204 * address 205 */ 206 public boolean isAnonymous() 207 { 208 Set<Principal> principals = m_subject.getPrincipals(); 209 return principals.contains( Role.ANONYMOUS ) || 210 principals.contains( WikiPrincipal.GUEST ) || 211 isIPV4Address( getUserPrincipal().getName() ); 212 } 213 214 /** 215 * <p> Returns the Principal used to log in to an authenticated session. The 216 * login principal is determined by examining the Subject's Principal set 217 * for PrincipalWrappers or WikiPrincipals with type designator 218 * <code>LOGIN_NAME</code>; the first one found is the login principal. 219 * If one is not found, this method returns the first principal that isn't 220 * of type Role or GroupPrincipal. If neither of these conditions hold, this method returns 221 * {@link org.apache.wiki.auth.WikiPrincipal#GUEST}. 222 * @return the login Principal. If it is a PrincipalWrapper containing an 223 * externally-provided Principal, the object returned is the Principal, not 224 * the wrapper around it. 225 */ 226 public Principal getLoginPrincipal() 227 { 228 return m_loginPrincipal; 229 } 230 231 /** 232 * <p>Returns the primary user Principal associated with this session. The 233 * primary user principal is determined as follows:</p> <ol> <li>If the 234 * Subject's Principal set contains WikiPrincipals, the first WikiPrincipal 235 * with type designator <code>WIKI_NAME</code> or (alternatively) 236 * <code>FULL_NAME</code> is the primary Principal.</li> 237 * <li>For all other cases, the first Principal in the Subject's principal 238 * collection that that isn't of type Role or GroupPrincipal is the primary.</li> 239 * </ol> 240 * If no primary user Principal is found, this method returns 241 * {@link org.apache.wiki.auth.WikiPrincipal#GUEST}. 242 * @return the primary user Principal 243 */ 244 public Principal getUserPrincipal() 245 { 246 return m_userPrincipal; 247 } 248 249 /** 250 * Returns a cached Locale object for this user. It's better to use 251 * WikiContext's corresponding getBundle() method, since that will actually 252 * react if the user changes the locale in the middle, but if that's not 253 * available (or, for some reason, you need the speed), this method can 254 * also be used. The Locale expires when the WikiSession expires, and 255 * currently there is no way to reset the Locale. 256 * 257 * @return A cached Locale object 258 * @since 2.5.96 259 */ 260 public Locale getLocale() 261 { 262 return m_cachedLocale; 263 } 264 265 /** 266 * Adds a message to the generic list of messages associated with the 267 * session. These messages retain their order of insertion and remain until 268 * the {@link #clearMessages()} method is called. 269 * @param message the message to add; if <code>null</code> it is ignored. 270 */ 271 public void addMessage(String message) 272 { 273 addMessage( ALL, message ); 274 } 275 276 277 /** 278 * Adds a message to the specific set of messages associated with the 279 * session. These messages retain their order of insertion and remain until 280 * the {@link #clearMessages()} method is called. 281 * @param topic the topic to associate the message to; 282 * @param message the message to add 283 */ 284 public void addMessage(String topic, String message) 285 { 286 if ( topic == null ) 287 { 288 throw new IllegalArgumentException( "addMessage: topic cannot be null." ); 289 } 290 Set<String> messages = m_messages.get( topic ); 291 if (messages == null ) 292 { 293 messages = new LinkedHashSet<String>(); 294 m_messages.put( topic, messages ); 295 } 296 messages.add( StringUtils.defaultString( message ) ); 297 } 298 299 /** 300 * Clears all messages associated with this session. 301 */ 302 public void clearMessages() 303 { 304 m_messages.clear(); 305 } 306 307 /** 308 * Clears all messages associated with a session topic. 309 * @param topic the topic whose messages should be cleared. 310 */ 311 public void clearMessages( String topic ) 312 { 313 Set<String> messages = m_messages.get( topic ); 314 if ( messages != null ) 315 { 316 m_messages.clear(); 317 } 318 } 319 320 /** 321 * Returns all generic messages associated with this session. 322 * The messages stored with the session persist throughout the 323 * session unless they have been reset with {@link #clearMessages()}. 324 * @return the current messages. 325 */ 326 public String[] getMessages() 327 { 328 return getMessages( ALL ); 329 } 330 331 /** 332 * Returns all messages associated with a session topic. 333 * The messages stored with the session persist throughout the 334 * session unless they have been reset with {@link #clearMessages(String)}. 335 * @return the current messages. 336 * @param topic The topic 337 */ 338 public String[] getMessages( String topic ) 339 { 340 Set<String> messages = m_messages.get( topic ); 341 if ( messages == null || messages.size() == 0 ) 342 { 343 return new String[0]; 344 } 345 return messages.toArray( new String[messages.size()] ); 346 } 347 348 /** 349 * Returns all user Principals associated with this session. User principals 350 * are those in the Subject's principal collection that aren't of type Role or 351 * of type GroupPrincipal. This is a defensive copy. 352 * @return Returns the user principal 353 * @see org.apache.wiki.auth.AuthenticationManager#isUserPrincipal(Principal) 354 */ 355 public Principal[] getPrincipals() 356 { 357 ArrayList<Principal> principals = new ArrayList<Principal>(); 358 359 // Take the first non Role as the main Principal 360 for( Principal principal : m_subject.getPrincipals() ) 361 { 362 if ( AuthenticationManager.isUserPrincipal( principal ) ) 363 { 364 principals.add( principal ); 365 } 366 } 367 368 return principals.toArray( new Principal[principals.size()] ); 369 } 370 371 /** 372 * Returns an array of Principal objects that represents the groups and 373 * roles that the user associated with a WikiSession possesses. The array is 374 * built by iterating through the Subject's Principal set and extracting all 375 * Role and GroupPrincipal objects into a list. The list is returned as an 376 * array sorted in the natural order implied by each Principal's 377 * <code>getName</code> method. Note that this method does <em>not</em> 378 * consult the external Authorizer or GroupManager; it relies on the 379 * Principals that have been injected into the user's Subject at login time, 380 * or after group creation/modification/deletion. 381 * @return an array of Principal objects corresponding to the roles the 382 * Subject possesses 383 */ 384 public Principal[] getRoles() 385 { 386 Set<Principal> roles = new HashSet<Principal>(); 387 388 // Add all of the Roles possessed by the Subject directly 389 roles.addAll( m_subject.getPrincipals( Role.class ) ); 390 391 // Add all of the GroupPrincipals possessed by the Subject directly 392 roles.addAll( m_subject.getPrincipals( GroupPrincipal.class ) ); 393 394 // Return a defensive copy 395 Principal[] roleArray = roles.toArray( new Principal[roles.size()] ); 396 Arrays.sort( roleArray, WikiPrincipal.COMPARATOR ); 397 return roleArray; 398 } 399 400 /** 401 * Removes the wiki session associated with the user's HTTP request 402 * from the cache of wiki sessions, typically as part of a logout 403 * process. 404 * @param engine the wiki engine 405 * @param request the users's HTTP request 406 */ 407 public static void removeWikiSession( WikiEngine engine, HttpServletRequest request ) 408 { 409 if ( engine == null || request == null ) 410 { 411 throw new IllegalArgumentException( "Request or engine cannot be null." ); 412 } 413 SessionMonitor monitor = SessionMonitor.getInstance( engine ); 414 monitor.remove( request.getSession() ); 415 } 416 417 /** 418 * Returns <code>true</code> if the WikiSession's Subject 419 * possess a supplied Principal. This method eliminates the need 420 * to externally request and inspect the JAAS subject. 421 * @param principal the Principal to test 422 * @return the result 423 */ 424 public boolean hasPrincipal( Principal principal ) 425 { 426 return m_subject.getPrincipals().contains( principal ); 427 428 } 429 430 /** 431 * Listens for WikiEvents generated by source objects such as the 432 * GroupManager. This method adds Principals to the private Subject managed 433 * by the WikiSession. 434 * @see org.apache.wiki.event.WikiEventListener#actionPerformed(org.apache.wiki.event.WikiEvent) 435 */ 436 public void actionPerformed( WikiEvent event ) 437 { 438 if ( event instanceof WikiSecurityEvent ) 439 { 440 WikiSecurityEvent e = (WikiSecurityEvent)event; 441 if ( e.getTarget() != null ) 442 { 443 switch (e.getType() ) 444 { 445 case WikiSecurityEvent.GROUP_ADD: 446 { 447 Group group = (Group)e.getTarget(); 448 if ( isInGroup( group ) ) 449 { 450 m_subject.getPrincipals().add( group.getPrincipal() ); 451 } 452 break; 453 } 454 case WikiSecurityEvent.GROUP_REMOVE: 455 { 456 Group group = (Group)e.getTarget(); 457 if ( m_subject.getPrincipals().contains( group.getPrincipal() ) ) 458 { 459 m_subject.getPrincipals().remove( group.getPrincipal() ); 460 } 461 break; 462 } 463 case WikiSecurityEvent.GROUP_CLEAR_GROUPS: 464 { 465 m_subject.getPrincipals().removeAll( m_subject.getPrincipals( GroupPrincipal.class ) ); 466 break; 467 } 468 case WikiSecurityEvent.LOGIN_INITIATED: 469 { 470 // Do nothing 471 } 472 case WikiSecurityEvent.PRINCIPAL_ADD: 473 { 474 WikiSession target = (WikiSession)e.getTarget(); 475 if ( this.equals( target ) && m_status == AUTHENTICATED ) 476 { 477 Set<Principal> principals = m_subject.getPrincipals(); 478 principals.add( (Principal)e.getPrincipal()); 479 } 480 break; 481 } 482 case WikiSecurityEvent.LOGIN_ANONYMOUS: 483 { 484 WikiSession target = (WikiSession)e.getTarget(); 485 if ( this.equals( target ) ) 486 { 487 m_status = ANONYMOUS; 488 489 // Set the login/user principals and login status 490 Set<Principal> principals = m_subject.getPrincipals(); 491 m_loginPrincipal = (Principal)e.getPrincipal(); 492 m_userPrincipal = m_loginPrincipal; 493 494 // Add the login principal to the Subject, and set the built-in roles 495 principals.clear(); 496 principals.add( m_loginPrincipal ); 497 principals.add( Role.ALL ); 498 principals.add( Role.ANONYMOUS ); 499 } 500 break; 501 } 502 case WikiSecurityEvent.LOGIN_ASSERTED: 503 { 504 WikiSession target = (WikiSession)e.getTarget(); 505 if ( this.equals( target ) ) 506 { 507 m_status = ASSERTED; 508 509 // Set the login/user principals and login status 510 Set<Principal> principals = m_subject.getPrincipals(); 511 m_loginPrincipal = (Principal)e.getPrincipal(); 512 m_userPrincipal = m_loginPrincipal; 513 514 // Add the login principal to the Subject, and set the built-in roles 515 principals.clear(); 516 principals.add( m_loginPrincipal ); 517 principals.add( Role.ALL ); 518 principals.add( Role.ASSERTED ); 519 } 520 break; 521 } 522 case WikiSecurityEvent.LOGIN_AUTHENTICATED: 523 { 524 WikiSession target = (WikiSession)e.getTarget(); 525 if ( this.equals( target ) ) 526 { 527 m_status = AUTHENTICATED; 528 529 // Set the login/user principals and login status 530 Set<Principal> principals = m_subject.getPrincipals(); 531 m_loginPrincipal = (Principal)e.getPrincipal(); 532 m_userPrincipal = m_loginPrincipal; 533 534 // Add the login principal to the Subject, and set the built-in roles 535 principals.clear(); 536 principals.add( m_loginPrincipal ); 537 principals.add( Role.ALL ); 538 principals.add( Role.AUTHENTICATED ); 539 540 // Add the user and group principals 541 injectUserProfilePrincipals(); // Add principals for the user profile 542 injectGroupPrincipals(); // Inject group principals 543 } 544 break; 545 } 546 case WikiSecurityEvent.PROFILE_SAVE: 547 { 548 WikiSession source = e.getSrc(); 549 if ( this.equals( source ) ) 550 { 551 injectUserProfilePrincipals(); // Add principals for the user profile 552 injectGroupPrincipals(); // Inject group principals 553 } 554 break; 555 } 556 case WikiSecurityEvent.PROFILE_NAME_CHANGED: 557 { 558 // Refresh user principals based on new user profile 559 WikiSession source = e.getSrc(); 560 if ( this.equals( source ) && m_status == AUTHENTICATED ) 561 { 562 // To prepare for refresh, set the new full name as the primary principal 563 UserProfile[] profiles = (UserProfile[])e.getTarget(); 564 UserProfile newProfile = profiles[1]; 565 if ( newProfile.getFullname() == null ) 566 { 567 throw new IllegalStateException( "User profile FullName cannot be null." ); 568 } 569 570 Set<Principal> principals = m_subject.getPrincipals(); 571 m_loginPrincipal = new WikiPrincipal( newProfile.getLoginName() ); 572 573 // Add the login principal to the Subject, and set the built-in roles 574 principals.clear(); 575 principals.add( m_loginPrincipal ); 576 principals.add( Role.ALL ); 577 principals.add( Role.AUTHENTICATED ); 578 579 // Add the user and group principals 580 injectUserProfilePrincipals(); // Add principals for the user profile 581 injectGroupPrincipals(); // Inject group principals 582 } 583 break; 584 } 585 586 // 587 // No action, if the event is not recognized. 588 // 589 default: 590 break; 591 } 592 } 593 } 594 } 595 596 /** 597 * Invalidates the WikiSession and resets its Subject's 598 * Principals to the equivalent of a "guest session". 599 */ 600 public void invalidate() 601 { 602 m_subject.getPrincipals().clear(); 603 m_subject.getPrincipals().add( WikiPrincipal.GUEST ); 604 m_subject.getPrincipals().add( Role.ANONYMOUS ); 605 m_subject.getPrincipals().add( Role.ALL ); 606 m_userPrincipal = WikiPrincipal.GUEST; 607 m_loginPrincipal = WikiPrincipal.GUEST; 608 } 609 610 /** 611 * Injects GroupPrincipal objects into the user's Principal set based on the 612 * groups the user belongs to. For Groups, the algorithm first calls the 613 * {@link GroupManager#getRoles()} to obtain the array of GroupPrincipals 614 * the authorizer knows about. Then, the method 615 * {@link GroupManager#isUserInRole(WikiSession, Principal)} is called for 616 * each Principal. If the user is a member of the group, an equivalent 617 * GroupPrincipal is injected into the user's principal set. Existing 618 * GroupPrincipals are flushed and replaced. This method should generally be 619 * called after a user's {@link org.apache.wiki.auth.user.UserProfile} is 620 * saved. If the wiki session is null, or there is no matching user profile, 621 * the method returns silently. 622 */ 623 protected void injectGroupPrincipals() 624 { 625 // Flush the existing GroupPrincipals 626 m_subject.getPrincipals().removeAll( m_subject.getPrincipals(GroupPrincipal.class) ); 627 628 // Get the GroupManager and test for each Group 629 GroupManager manager = m_engine.getGroupManager(); 630 for ( Principal group : manager.getRoles() ) 631 { 632 if ( manager.isUserInRole( this, group ) ) 633 { 634 m_subject.getPrincipals().add( group ); 635 } 636 } 637 } 638 639 /** 640 * Adds Principal objects to the Subject that correspond to the 641 * logged-in user's profile attributes for the wiki name, full name 642 * and login name. These Principals will be WikiPrincipals, and they 643 * will replace all other WikiPrincipals in the Subject. <em>Note: 644 * this method is never called during anonymous or asserted sessions.</em> 645 */ 646 protected void injectUserProfilePrincipals() 647 { 648 // Search for the user profile 649 String searchId = m_loginPrincipal.getName(); 650 if ( searchId == null ) 651 { 652 // Oh dear, this wasn't an authenticated user after all 653 log.info("Refresh principals failed because WikiSession had no user Principal; maybe not logged in?"); 654 return; 655 } 656 657 // Look up the user and go get the new Principals 658 UserDatabase database = m_engine.getUserManager().getUserDatabase(); 659 if ( database == null ) 660 { 661 throw new IllegalStateException( "User database cannot be null." ); 662 } 663 try 664 { 665 UserProfile profile = database.find( searchId ); 666 Principal[] principals = database.getPrincipals( profile.getLoginName() ); 667 for ( Principal principal : principals ) 668 { 669 // Add the Principal to the Subject 670 m_subject.getPrincipals().add( principal ); 671 672 // Set the user principal if needed; we prefer FullName, but the WikiName will also work 673 boolean isFullNamePrincipal = ( principal instanceof WikiPrincipal && ((WikiPrincipal)principal).getType() == WikiPrincipal.FULL_NAME ); 674 if ( isFullNamePrincipal ) 675 { 676 m_userPrincipal = principal; 677 } 678 else if ( !( m_userPrincipal instanceof WikiPrincipal ) ) 679 { 680 m_userPrincipal = principal; 681 } 682 } 683 } 684 catch ( NoSuchPrincipalException e ) 685 { 686 // We will get here if the user has a principal but not a profile 687 // For example, it's a container-managed user who hasn't set up a profile yet 688 log.warn("User profile '" + searchId + "' not found. This is normal for container-auth users who haven't set up a profile yet."); 689 } 690 } 691 692 /** 693 * <p>Returns the status of the wiki session as a text string. Valid values are:</p> 694 * <ul> 695 * <li>{@link #AUTHENTICATED}</li> 696 * <li>{@link #ASSERTED}</li> 697 * <li>{@link #ANONYMOUS}</li> 698 * </ul> 699 * @return the user's session status 700 */ 701 public String getStatus() 702 { 703 return m_status; 704 } 705 706 /** 707 * <p>Static factory method that returns the WikiSession object associated with 708 * the current HTTP request. This method looks up the associated HttpSession 709 * in an internal WeakHashMap and attempts to retrieve the WikiSession. If 710 * not found, one is created. This method is guaranteed to always return a 711 * WikiSession, although the authentication status is unpredictable until 712 * the user attempts to log in. If the servlet request parameter is 713 * <code>null</code>, a synthetic {@link #guestSession(WikiEngine)}is returned.</p> 714 * <p>When a session is created, this method attaches a WikiEventListener 715 * to the GroupManager so that changes to groups are detected automatically.</p> 716 * @param engine the wiki engine 717 * @param request the servlet request object 718 * @return the existing (or newly created) wiki session 719 */ 720 public static WikiSession getWikiSession( WikiEngine engine, HttpServletRequest request ) 721 { 722 // If request is null, return guest session 723 if ( request == null ) 724 { 725 if ( log.isDebugEnabled() ) 726 { 727 log.debug( "Looking up WikiSession for NULL HttpRequest: returning guestSession()" ); 728 } 729 return staticGuestSession( engine ); 730 } 731 732 // Look for a WikiSession associated with the user's Http Session 733 // and create one if it isn't there yet. 734 HttpSession session = request.getSession(); 735 SessionMonitor monitor = SessionMonitor.getInstance( engine ); 736 WikiSession wikiSession = monitor.find( session ); 737 738 // Attach reference to wiki engine 739 wikiSession.m_engine = engine; 740 741 wikiSession.m_cachedLocale = request.getLocale(); 742 743 return wikiSession; 744 } 745 746 /** 747 * Static factory method that creates a new "guest" session containing a single 748 * user Principal {@link org.apache.wiki.auth.WikiPrincipal#GUEST}, 749 * plus the role principals {@link Role#ALL} and 750 * {@link Role#ANONYMOUS}. This method also adds the session as a listener 751 * for GroupManager, AuthenticationManager and UserManager events. 752 * @param engine the wiki engine 753 * @return the guest wiki session 754 */ 755 public static WikiSession guestSession( WikiEngine engine ) 756 { 757 WikiSession session = new WikiSession(); 758 session.m_engine = engine; 759 session.invalidate(); 760 761 // Add the session as listener for GroupManager, AuthManager, UserManager events 762 GroupManager groupMgr = engine.getGroupManager(); 763 AuthenticationManager authMgr = engine.getAuthenticationManager(); 764 UserManager userMgr = engine.getUserManager(); 765 groupMgr.addWikiEventListener( session ); 766 authMgr.addWikiEventListener( session ); 767 userMgr.addWikiEventListener( session ); 768 769 return session; 770 } 771 772 /** 773 * Returns a static guest session, which is available for this 774 * thread only. This guest session is used internally whenever 775 * there is no HttpServletRequest involved, but the request is 776 * done e.g. when embedding JSPWiki code. 777 * 778 * @param engine WikiEngine for this session 779 * @return A static WikiSession which is shared by all in this 780 * same Thread. 781 */ 782 // FIXME: Should really use WeakReferences to clean away unused sessions. 783 784 private static WikiSession staticGuestSession( WikiEngine engine ) 785 { 786 WikiSession session = c_guestSession.get(); 787 788 if( session == null ) 789 { 790 session = guestSession( engine ); 791 792 c_guestSession.set( session ); 793 } 794 795 return session; 796 } 797 798 /** 799 * Returns the total number of active wiki sessions for a 800 * particular wiki. This method delegates to the wiki's 801 * {@link SessionMonitor#sessions()} method. 802 * @param engine the wiki session 803 * @return the number of sessions 804 */ 805 public static int sessions( WikiEngine engine ) 806 { 807 SessionMonitor monitor = SessionMonitor.getInstance( engine ); 808 return monitor.sessions(); 809 } 810 811 /** 812 * Returns Principals representing the current users known 813 * to a particular wiki. Each Principal will correspond to the 814 * value returned by each WikiSession's {@link #getUserPrincipal()} 815 * method. This method delegates to {@link SessionMonitor#userPrincipals()}. 816 * @param engine the wiki engine 817 * @return an array of Principal objects, sorted by name 818 */ 819 public static Principal[] userPrincipals( WikiEngine engine ) 820 { 821 SessionMonitor monitor = SessionMonitor.getInstance( engine ); 822 return monitor.userPrincipals(); 823 } 824 825 /** 826 * Wrapper for 827 * {@link javax.security.auth.Subject#doAsPrivileged(Subject, java.security.PrivilegedExceptionAction, java.security.AccessControlContext)} 828 * that executes an action with the privileges posssessed by a 829 * WikiSession's Subject. The action executes with a <code>null</code> 830 * AccessControlContext, which has the effect of running it "cleanly" 831 * without the AccessControlContexts of the caller. 832 * @param session the wiki session 833 * @param action the privileged action 834 * @return the result of the privileged action; may be <code>null</code> 835 * @throws java.security.AccessControlException if the action is not permitted 836 * by the security policy 837 */ 838 public static Object doPrivileged( WikiSession session, PrivilegedAction<?> action ) throws AccessControlException 839 { 840 return Subject.doAsPrivileged( session.m_subject, action, null ); 841 } 842 843 /** 844 * Verifies whether a String represents an IPv4 address. The algorithm is 845 * extremely efficient and does not allocate any objects. 846 * @param name the address to test 847 * @return the result 848 */ 849 protected static boolean isIPV4Address( String name ) 850 { 851 if ( name.charAt( 0 ) == DOT || name.charAt( name.length() - 1 ) == DOT ) 852 { 853 return false; 854 } 855 856 int[] addr = new int[] 857 { 0, 0, 0, 0 }; 858 int currentOctet = 0; 859 for( int i = 0; i < name.length(); i++ ) 860 { 861 int ch = name.charAt( i ); 862 boolean isDigit = ch >= ONE && ch <= NINE; 863 boolean isDot = ch == DOT; 864 if ( !isDigit && !isDot ) 865 { 866 return false; 867 } 868 if ( isDigit ) 869 { 870 addr[currentOctet] = 10 * addr[currentOctet] + ( ch - ONE ); 871 if ( addr[currentOctet] > 255 ) 872 { 873 return false; 874 } 875 } 876 else if ( name.charAt( i - 1 ) == DOT ) 877 { 878 return false; 879 } 880 else 881 { 882 currentOctet++; 883 } 884 } 885 return currentOctet == 3; 886 } 887 888}