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