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 org.apache.commons.lang3.StringUtils; 022import org.apache.log4j.Logger; 023import org.apache.wiki.auth.AuthenticationManager; 024import org.apache.wiki.auth.GroupPrincipal; 025import org.apache.wiki.auth.NoSuchPrincipalException; 026import org.apache.wiki.auth.SessionMonitor; 027import org.apache.wiki.auth.UserManager; 028import org.apache.wiki.auth.WikiPrincipal; 029import org.apache.wiki.auth.authorize.Group; 030import org.apache.wiki.auth.authorize.GroupManager; 031import org.apache.wiki.auth.authorize.Role; 032import org.apache.wiki.auth.user.UserDatabase; 033import org.apache.wiki.auth.user.UserProfile; 034import org.apache.wiki.event.WikiEvent; 035import org.apache.wiki.event.WikiEventListener; 036import org.apache.wiki.event.WikiSecurityEvent; 037 038import javax.security.auth.Subject; 039import javax.servlet.http.HttpServletRequest; 040import javax.servlet.http.HttpSession; 041import java.security.AccessControlException; 042import java.security.Principal; 043import java.security.PrivilegedAction; 044import java.util.ArrayList; 045import java.util.Arrays; 046import java.util.HashMap; 047import java.util.HashSet; 048import java.util.LinkedHashSet; 049import java.util.Locale; 050import java.util.Map; 051import java.util.Set; 052 053/** 054 * <p>Represents a long-running wiki session, with an associated user Principal, 055 * user Subject, and authentication status. This class is initialized with 056 * minimal, default-deny values: authentication is set to <code>false</code>, 057 * and the user principal is set to <code>null</code>.</p> 058 * <p>The WikiSession class allows callers to:</p> 059 * <ul> 060 * <li>Obtain the authentication status of the user via 061 * {@link #isAnonymous()} and {@link #isAuthenticated()}</li> 062 * <li>Query the session for Principals representing the 063 * user's identity via {@link #getLoginPrincipal()}, 064 * {@link #getUserPrincipal()} and {@link #getPrincipals()}</li> 065 * <li>Store, retrieve and clear UI messages via 066 * {@link #addMessage(String)}, {@link #getMessages(String)} 067 * and {@link #clearMessages(String)}</li> 068 * </ul> 069 * <p>To keep track of the Principals each user posseses, each WikiSession 070 * stores a JAAS Subject. Various login processes add or remove Principals 071 * when users authenticate or log out.</p> 072 * <p>WikiSession implements the {@link org.apache.wiki.event.WikiEventListener} 073 * interface and listens for group add/change/delete events fired by 074 * event sources the WikiSession is registered with. Normally, 075 * {@link org.apache.wiki.auth.AuthenticationManager} registers each WikiSession 076 * with the {@link org.apache.wiki.auth.authorize.GroupManager} 077 * so it can catch group events. Thus, when a user is added to a 078 * {@link org.apache.wiki.auth.authorize.Group}, a corresponding 079 * {@link org.apache.wiki.auth.GroupPrincipal} is injected into 080 * the Subject's Principal set. Likewise, when the user is removed from 081 * the Group or the Group is deleted, the GroupPrincipal is removed 082 * from the Subject. The effect that this strategy produces is extremely 083 * beneficial: when someone adds a user to a wiki group, that user 084 * <em>immediately</em> gains the privileges associated with that 085 * group; he or she does not need to re-authenticate. 086 * </p> 087 * <p>In addition to methods for examining individual <code>WikiSession</code> 088 * objects, this class also contains a number of static methods for 089 * managing WikiSessions for an entire wiki. These methods allow callers 090 * to find, query and remove WikiSession objects, and 091 * to obtain a list of the current wiki session users.</p> 092 * <p>WikiSession encloses a protected static class, {@link SessionMonitor}, 093 * to keep track of WikiSessions registered with each wiki.</p> 094 */ 095public final class WikiSession implements WikiEventListener 096{ 097 098 /** An anonymous user's session status. */ 099 public static final String ANONYMOUS = "anonymous"; 100 101 /** An asserted user's session status. */ 102 public static final String ASSERTED = "asserted"; 103 104 /** An authenticated user's session status. */ 105 public static final String AUTHENTICATED = "authenticated"; 106 107 private static final int ONE = 48; 108 109 private static final int NINE = 57; 110 111 private static final int DOT = 46; 112 113 private static final Logger log = Logger.getLogger( WikiSession.class ); 114 115 private static final String ALL = "*"; 116 117 private static ThreadLocal<WikiSession> c_guestSession = new ThreadLocal<>(); 118 119 private final Subject m_subject = new Subject(); 120 121 private final Map<String,Set<String>> m_messages = new HashMap<>(); 122 123 /** The WikiEngine that created this session. */ 124 private WikiEngine m_engine = null; 125 126 private String m_status = ANONYMOUS; 127 128 private Principal m_userPrincipal = WikiPrincipal.GUEST; 129 130 private Principal m_loginPrincipal = WikiPrincipal.GUEST; 131 132 private Locale m_cachedLocale = Locale.getDefault(); 133 134 /** 135 * Returns <code>true</code> if one of this WikiSession's user Principals 136 * can be shown to belong to a particular wiki group. If the user is 137 * not authenticated, this method will always return <code>false</code>. 138 * @param group the group to test 139 * @return the result 140 */ 141 protected boolean isInGroup( Group group ) 142 { 143 for ( Principal principal : getPrincipals() ) 144 { 145 if ( isAuthenticated() && group.isMember( principal ) ) 146 { 147 return true; 148 } 149 } 150 return false; 151 } 152 153 /** 154 * Private constructor to prevent WikiSession from being instantiated 155 * directly. 156 */ 157 private WikiSession() 158 { 159 } 160 161 /** 162 * Returns <code>true</code> if the user is considered asserted via 163 * a session cookie; that is, the Subject contains the Principal 164 * Role.ASSERTED. 165 * @return Returns <code>true</code> if the user is asserted 166 */ 167 public boolean isAsserted() 168 { 169 return m_subject.getPrincipals().contains( Role.ASSERTED ); 170 } 171 172 /** 173 * Returns the authentication status of the user's session. The user is 174 * considered authenticated if the Subject contains the Principal 175 * Role.AUTHENTICATED. If this method determines that an earlier 176 * LoginModule did not inject Role.AUTHENTICATED, it will inject one 177 * if the user is not anonymous <em>and</em> not asserted. 178 * @return Returns <code>true</code> if the user is authenticated 179 */ 180 public boolean isAuthenticated() 181 { 182 // If Role.AUTHENTICATED is in principals set, always return true. 183 if ( m_subject.getPrincipals().contains( Role.AUTHENTICATED ) ) 184 { 185 return true; 186 } 187 188 // With non-JSPWiki LoginModules, the role may not be there, so 189 // we need to add it if the user really is authenticated. 190 if ( !isAnonymous() && !isAsserted() ) 191 { 192 // Inject AUTHENTICATED role 193 m_subject.getPrincipals().add( Role.AUTHENTICATED ); 194 return true; 195 } 196 197 return false; 198 } 199 200 /** 201 * <p>Determines whether the current session is anonymous. This will be 202 * true if any of these conditions are true:</p> 203 * <ul> 204 * <li>The session's Principal set contains 205 * {@link org.apache.wiki.auth.authorize.Role#ANONYMOUS}</li> 206 * <li>The session's Principal set contains 207 * {@link org.apache.wiki.auth.WikiPrincipal#GUEST}</li> 208 * <li>The Principal returned by {@link #getUserPrincipal()} evaluates 209 * to an IP address.</li> 210 * </ul> 211 * <p>The criteria above are listed in the order in which they are 212 * evaluated.</p> 213 * @return whether the current user's identity is equivalent to an IP 214 * address 215 */ 216 public boolean isAnonymous() 217 { 218 Set<Principal> principals = m_subject.getPrincipals(); 219 return principals.contains( Role.ANONYMOUS ) || 220 principals.contains( WikiPrincipal.GUEST ) || 221 isIPV4Address( getUserPrincipal().getName() ); 222 } 223 224 /** 225 * <p> Returns the Principal used to log in to an authenticated session. The 226 * login principal is determined by examining the Subject's Principal set 227 * for PrincipalWrappers or WikiPrincipals with type designator 228 * <code>LOGIN_NAME</code>; the first one found is the login principal. 229 * If one is not found, this method returns the first principal that isn't 230 * of type Role or GroupPrincipal. If neither of these conditions hold, this method returns 231 * {@link org.apache.wiki.auth.WikiPrincipal#GUEST}. 232 * @return the login Principal. If it is a PrincipalWrapper containing an 233 * externally-provided Principal, the object returned is the Principal, not 234 * the wrapper around it. 235 */ 236 public Principal getLoginPrincipal() 237 { 238 return m_loginPrincipal; 239 } 240 241 /** 242 * <p>Returns the primary user Principal associated with this session. The 243 * primary user principal is determined as follows:</p> <ol> <li>If the 244 * Subject's Principal set contains WikiPrincipals, the first WikiPrincipal 245 * with type designator <code>WIKI_NAME</code> or (alternatively) 246 * <code>FULL_NAME</code> is the primary Principal.</li> 247 * <li>For all other cases, the first Principal in the Subject's principal 248 * collection that that isn't of type Role or GroupPrincipal is the primary.</li> 249 * </ol> 250 * If no primary user Principal is found, this method returns 251 * {@link org.apache.wiki.auth.WikiPrincipal#GUEST}. 252 * @return the primary user Principal 253 */ 254 public Principal getUserPrincipal() 255 { 256 return m_userPrincipal; 257 } 258 259 /** 260 * Returns a cached Locale object for this user. It's better to use 261 * WikiContext's corresponding getBundle() method, since that will actually 262 * react if the user changes the locale in the middle, but if that's not 263 * available (or, for some reason, you need the speed), this method can 264 * also be used. The Locale expires when the WikiSession expires, and 265 * currently there is no way to reset the Locale. 266 * 267 * @return A cached Locale object 268 * @since 2.5.96 269 */ 270 public Locale getLocale() 271 { 272 return m_cachedLocale; 273 } 274 275 /** 276 * Adds a message to the generic list of messages associated with the 277 * session. These messages retain their order of insertion and remain until 278 * the {@link #clearMessages()} method is called. 279 * @param message the message to add; if <code>null</code> it is ignored. 280 */ 281 public void addMessage(String message) 282 { 283 addMessage( ALL, message ); 284 } 285 286 287 /** 288 * Adds a message to the specific set of messages associated with the 289 * session. These messages retain their order of insertion and remain until 290 * the {@link #clearMessages()} method is called. 291 * @param topic the topic to associate the message to; 292 * @param message the message to add 293 */ 294 public void addMessage(String topic, String message) 295 { 296 if ( topic == null ) 297 { 298 throw new IllegalArgumentException( "addMessage: topic cannot be null." ); 299 } 300 Set<String> messages = m_messages.get( topic ); 301 if (messages == null ) 302 { 303 messages = new LinkedHashSet<>(); 304 m_messages.put( topic, messages ); 305 } 306 messages.add( StringUtils.defaultString( message ) ); 307 } 308 309 /** 310 * Clears all messages associated with this session. 311 */ 312 public void clearMessages() 313 { 314 m_messages.clear(); 315 } 316 317 /** 318 * Clears all messages associated with a session topic. 319 * @param topic the topic whose messages should be cleared. 320 */ 321 public void clearMessages( String topic ) 322 { 323 Set<String> messages = m_messages.get( topic ); 324 if ( messages != null ) 325 { 326 m_messages.clear(); 327 } 328 } 329 330 /** 331 * Returns all generic messages associated with this session. 332 * The messages stored with the session persist throughout the 333 * session unless they have been reset with {@link #clearMessages()}. 334 * @return the current messages. 335 */ 336 public String[] getMessages() 337 { 338 return getMessages( ALL ); 339 } 340 341 /** 342 * Returns all messages associated with a session topic. 343 * The messages stored with the session persist throughout the 344 * session unless they have been reset with {@link #clearMessages(String)}. 345 * @return the current messages. 346 * @param topic The topic 347 */ 348 public String[] getMessages( String topic ) 349 { 350 Set<String> messages = m_messages.get( topic ); 351 if ( messages == null || messages.size() == 0 ) 352 { 353 return new String[0]; 354 } 355 return messages.toArray( new String[messages.size()] ); 356 } 357 358 /** 359 * Returns all user Principals associated with this session. User principals 360 * are those in the Subject's principal collection that aren't of type Role or 361 * of type GroupPrincipal. This is a defensive copy. 362 * @return Returns the user principal 363 * @see org.apache.wiki.auth.AuthenticationManager#isUserPrincipal(Principal) 364 */ 365 public Principal[] getPrincipals() 366 { 367 ArrayList<Principal> principals = new ArrayList<>(); 368 369 // Take the first non Role as the main Principal 370 for( Principal principal : m_subject.getPrincipals() ) 371 { 372 if ( AuthenticationManager.isUserPrincipal( principal ) ) 373 { 374 principals.add( principal ); 375 } 376 } 377 378 return principals.toArray( new Principal[principals.size()] ); 379 } 380 381 /** 382 * Returns an array of Principal objects that represents the groups and 383 * roles that the user associated with a WikiSession possesses. The array is 384 * built by iterating through the Subject's Principal set and extracting all 385 * Role and GroupPrincipal objects into a list. The list is returned as an 386 * array sorted in the natural order implied by each Principal's 387 * <code>getName</code> method. Note that this method does <em>not</em> 388 * consult the external Authorizer or GroupManager; it relies on the 389 * Principals that have been injected into the user's Subject at login time, 390 * or after group creation/modification/deletion. 391 * @return an array of Principal objects corresponding to the roles the 392 * Subject possesses 393 */ 394 public Principal[] getRoles() 395 { 396 Set<Principal> roles = new HashSet<>(); 397 398 // Add all of the Roles possessed by the Subject directly 399 roles.addAll( m_subject.getPrincipals( Role.class ) ); 400 401 // Add all of the GroupPrincipals possessed by the Subject directly 402 roles.addAll( m_subject.getPrincipals( GroupPrincipal.class ) ); 403 404 // Return a defensive copy 405 Principal[] roleArray = roles.toArray( new Principal[roles.size()] ); 406 Arrays.sort( roleArray, WikiPrincipal.COMPARATOR ); 407 return roleArray; 408 } 409 410 /** 411 * Removes the wiki session associated with the user's HTTP request 412 * from the cache of wiki sessions, typically as part of a logout 413 * process. 414 * @param engine the wiki engine 415 * @param request the users's HTTP request 416 */ 417 public static void removeWikiSession( WikiEngine engine, HttpServletRequest request ) 418 { 419 if ( engine == null || request == null ) 420 { 421 throw new IllegalArgumentException( "Request or engine cannot be null." ); 422 } 423 SessionMonitor monitor = SessionMonitor.getInstance( engine ); 424 monitor.remove( request.getSession() ); 425 } 426 427 /** 428 * Returns <code>true</code> if the WikiSession's Subject 429 * possess a supplied Principal. This method eliminates the need 430 * to externally request and inspect the JAAS subject. 431 * @param principal the Principal to test 432 * @return the result 433 */ 434 public boolean hasPrincipal( Principal principal ) 435 { 436 return m_subject.getPrincipals().contains( principal ); 437 438 } 439 440 /** 441 * Listens for WikiEvents generated by source objects such as the 442 * GroupManager. This method adds Principals to the private Subject managed 443 * by the WikiSession. 444 * @see org.apache.wiki.event.WikiEventListener#actionPerformed(org.apache.wiki.event.WikiEvent) 445 */ 446 @Override 447 public void actionPerformed( WikiEvent event ) 448 { 449 if ( event instanceof WikiSecurityEvent ) 450 { 451 WikiSecurityEvent e = (WikiSecurityEvent)event; 452 if ( e.getTarget() != null ) 453 { 454 switch (e.getType() ) 455 { 456 case WikiSecurityEvent.GROUP_ADD: 457 { 458 Group group = (Group)e.getTarget(); 459 if ( isInGroup( group ) ) 460 { 461 m_subject.getPrincipals().add( group.getPrincipal() ); 462 } 463 break; 464 } 465 case WikiSecurityEvent.GROUP_REMOVE: 466 { 467 Group group = (Group)e.getTarget(); 468 if ( m_subject.getPrincipals().contains( group.getPrincipal() ) ) 469 { 470 m_subject.getPrincipals().remove( group.getPrincipal() ); 471 } 472 break; 473 } 474 case WikiSecurityEvent.GROUP_CLEAR_GROUPS: 475 { 476 m_subject.getPrincipals().removeAll( m_subject.getPrincipals( GroupPrincipal.class ) ); 477 break; 478 } 479 case WikiSecurityEvent.LOGIN_INITIATED: 480 { 481 // Do nothing 482 break; 483 } 484 case WikiSecurityEvent.PRINCIPAL_ADD: 485 { 486 WikiSession target = (WikiSession)e.getTarget(); 487 if ( this.equals( target ) && m_status.equals(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.equals(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}