You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jspwiki.apache.org by ju...@apache.org on 2020/03/06 18:04:21 UTC

[jspwiki] 19/35: JSPWIKI-303: extract o.a.w.api.core.Session from o.a.w.WikiSession

This is an automated email from the ASF dual-hosted git repository.

juanpablo pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/jspwiki.git

commit 442402d3010310e000cd88b74748b9c3d83b804f
Author: juanpablo <ju...@apache.org>
AuthorDate: Wed Mar 4 20:18:57 2020 +0100

    JSPWIKI-303: extract o.a.w.api.core.Session from o.a.w.WikiSession
---
 .../src/main/java/org/apache/wiki/WikiSession.java | 583 +++++++--------------
 .../java/org/apache/wiki/api/core/Session.java     | 248 +++++++++
 2 files changed, 449 insertions(+), 382 deletions(-)

diff --git a/jspwiki-main/src/main/java/org/apache/wiki/WikiSession.java b/jspwiki-main/src/main/java/org/apache/wiki/WikiSession.java
index 39ac604..12e6f36 100644
--- a/jspwiki-main/src/main/java/org/apache/wiki/WikiSession.java
+++ b/jspwiki-main/src/main/java/org/apache/wiki/WikiSession.java
@@ -21,6 +21,7 @@ package org.apache.wiki;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
 import org.apache.wiki.api.core.Engine;
+import org.apache.wiki.api.core.Session;
 import org.apache.wiki.auth.AuthenticationManager;
 import org.apache.wiki.auth.GroupPrincipal;
 import org.apache.wiki.auth.NoSuchPrincipalException;
@@ -33,81 +34,40 @@ import org.apache.wiki.auth.authorize.Role;
 import org.apache.wiki.auth.user.UserDatabase;
 import org.apache.wiki.auth.user.UserProfile;
 import org.apache.wiki.event.WikiEvent;
-import org.apache.wiki.event.WikiEventListener;
 import org.apache.wiki.event.WikiSecurityEvent;
+import org.apache.wiki.util.HttpUtil;
 
 import javax.security.auth.Subject;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpSession;
-import java.security.AccessControlException;
 import java.security.Principal;
-import java.security.PrivilegedAction;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
 
 /**
- * <p>Represents a long-running wiki session, with an associated user Principal, user Subject, and authentication status. This class
- * is initialized with minimal, default-deny values: authentication is set to <code>false</code>, and the user principal is set to
- * <code>null</code>.</p>
- * <p>The WikiSession class allows callers to:</p>
- * <ul>
- *   <li>Obtain the authentication status of the user via
- *     {@link #isAnonymous()} and {@link #isAuthenticated()}</li>
- *   <li>Query the session for Principals representing the
- *     user's identity via {@link #getLoginPrincipal()},
- *     {@link #getUserPrincipal()} and {@link #getPrincipals()}</li>
- *   <li>Store, retrieve and clear UI messages via
- *     {@link #addMessage(String)}, {@link #getMessages(String)}
- *     and {@link #clearMessages(String)}</li>
- * </ul>
- * <p>To keep track of the Principals each user posseses, each WikiSession stores a JAAS Subject. Various login processes add or
- * remove Principals when users authenticate or log out.</p>
- * <p>WikiSession implements the {@link org.apache.wiki.event.WikiEventListener} interface and listens for group add/change/delete
- * events fired by event sources the WikiSession is registered with. Normally, {@link org.apache.wiki.auth.AuthenticationManager}
- * registers each WikiSession with the {@link org.apache.wiki.auth.authorize.GroupManager} so it can catch group events. Thus, when
- * a user is added to a {@link org.apache.wiki.auth.authorize.Group}, a corresponding {@link org.apache.wiki.auth.GroupPrincipal} is
- * injected into the Subject's Principal set. Likewise, when the user is removed from the Group or the Group is deleted, the
- * GroupPrincipal is removed from the Subject. The effect that this strategy produces is extremely beneficial: when someone adds a user
- * to a wiki group, that user <em>immediately</em> gains the privileges associated with that group; he or she does not need to
- * re-authenticate.
- * </p>
+ * <p>Default implementation for {@link Session}.</p>
  * <p>In addition to methods for examining individual <code>WikiSession</code> objects, this class also contains a number of static
  * methods for managing WikiSessions for an entire wiki. These methods allow callers to find, query and remove WikiSession objects, and
  * to obtain a list of the current wiki session users.</p>
- * <p>WikiSession encloses a protected static class, {@link SessionMonitor}, to keep track of WikiSessions registered with each wiki.</p>
  */
-public final class WikiSession implements WikiEventListener {
-
-    /** An anonymous user's session status. */
-    public static final String  ANONYMOUS             = "anonymous";
-
-    /** An asserted user's session status. */
-    public static final String  ASSERTED              = "asserted";
-
-    /** An authenticated user's session status. */
-    public static final String  AUTHENTICATED         = "authenticated";
-
-    private static final int    ONE                   = 48;
-
-    private static final int    NINE                  = 57;
-
-    private static final int    DOT                   = 46;
+public final class WikiSession implements Session {
 
     private static final Logger log                   = Logger.getLogger( WikiSession.class );
 
     private static final String ALL                   = "*";
 
-    private static ThreadLocal<WikiSession> c_guestSession = new ThreadLocal<>();
+    private static ThreadLocal< Session > c_guestSession = new ThreadLocal<>();
 
     private final Subject       m_subject             = new Subject();
 
-    private final Map<String,Set<String>> m_messages  = new HashMap<>();
+    private final Map< String, Set< String > > m_messages  = new ConcurrentHashMap<>();
 
     /** The Engine that created this session. */
     private Engine              m_engine              = null;
@@ -142,24 +102,14 @@ public final class WikiSession implements WikiEventListener {
     private WikiSession() {
     }
 
-    /**
-     * Returns <code>true</code> if the user is considered asserted via a session cookie; that is, the Subject contains the Principal
-     * Role.ASSERTED.
-     *
-     * @return Returns <code>true</code> if the user is asserted
-     */
-    public boolean isAsserted()
-    {
+    /** {@inheritDoc} */
+    @Override
+    public boolean isAsserted() {
         return m_subject.getPrincipals().contains( Role.ASSERTED );
     }
 
-    /**
-     * Returns the authentication status of the user's session. The user is considered authenticated if the Subject contains the
-     * Principal Role.AUTHENTICATED. If this method determines that an earlier LoginModule did not inject Role.AUTHENTICATED, it
-     * will inject one if the user is not anonymous <em>and</em> not asserted.
-     *
-     * @return Returns <code>true</code> if the user is authenticated
-     */
+    /** {@inheritDoc} */
+    @Override
     public boolean isAuthenticated() {
         // If Role.AUTHENTICATED is in principals set, always return true.
         if ( m_subject.getPrincipals().contains( Role.AUTHENTICATED ) ) {
@@ -175,89 +125,41 @@ public final class WikiSession implements WikiEventListener {
         return false;
     }
 
-    /**
-     * <p>Determines whether the current session is anonymous. This will be true if any of these conditions are true:</p>
-     * <ul>
-     *   <li>The session's Principal set contains
-     *       {@link org.apache.wiki.auth.authorize.Role#ANONYMOUS}</li>
-     *   <li>The session's Principal set contains
-     *       {@link org.apache.wiki.auth.WikiPrincipal#GUEST}</li>
-     *   <li>The Principal returned by {@link #getUserPrincipal()} evaluates
-     *       to an IP address.</li>
-     * </ul>
-     * <p>The criteria above are listed in the order in which they are evaluated.</p>
-     * @return whether the current user's identity is equivalent to an IP address
-     */
+    /** {@inheritDoc} */
+    @Override
     public boolean isAnonymous() {
         final Set< Principal > principals = m_subject.getPrincipals();
         return principals.contains( Role.ANONYMOUS ) ||
                principals.contains( WikiPrincipal.GUEST ) ||
-               isIPV4Address( getUserPrincipal().getName() );
+               HttpUtil.isIPV4Address( getUserPrincipal().getName() );
     }
 
-    /**
-     * <p> Returns the Principal used to log in to an authenticated session. The login principal is determined by examining the
-     * Subject's Principal set for PrincipalWrappers or WikiPrincipals with type designator <code>LOGIN_NAME</code>; the first one
-     * found is the login principal. If one is not found, this method returns the first principal that isn't of type Role or
-     * GroupPrincipal. If neither of these conditions hold, this method returns {@link org.apache.wiki.auth.WikiPrincipal#GUEST}.
-     *
-     * @return the login Principal. If it is a PrincipalWrapper containing an externally-provided Principal, the object returned is the
-     * Principal, not the wrapper around it.
-     */
-    public Principal getLoginPrincipal()
-    {
+    /** {@inheritDoc} */
+    @Override
+    public Principal getLoginPrincipal() {
         return m_loginPrincipal;
     }
 
-    /**
-     * <p>Returns the primary user Principal associated with this session. The primary user principal is determined as follows:</p>
-     * <ol>
-     *     <li>If the Subject's Principal set contains WikiPrincipals, the first WikiPrincipal with type designator
-     *         <code>WIKI_NAME</code> or (alternatively) <code>FULL_NAME</code> is the primary Principal.</li>
-     *     <li>For all other cases, the first Principal in the Subject's principal collection that that isn't of type Role or
-     *         GroupPrincipal is the primary.</li>
-     * </ol>
-     * If no primary user Principal is found, this method returns {@link org.apache.wiki.auth.WikiPrincipal#GUEST}.
-     *
-     * @return the primary user Principal
-     */
-    public Principal getUserPrincipal()
-    {
+    /** {@inheritDoc} */
+    @Override
+    public Principal getUserPrincipal() {
         return m_userPrincipal;
     }
 
-    /**
-     *  Returns a cached Locale object for this user.  It's better to use WikiContext's corresponding getBundle() method, since that
-     *  will actually react if the user changes the locale in the middle, but if that's not available (or, for some reason, you need
-     *  the speed), this method can also be used.  The Locale expires when the WikiSession expires, and currently there is no way to
-     *  reset the Locale.
-     *
-     *  @return A cached Locale object
-     *  @since 2.5.96
-     */
-    public Locale getLocale()
-    {
+    /** {@inheritDoc} */
+    @Override
+    public Locale getLocale() {
         return m_cachedLocale;
     }
 
-    /**
-     * Adds a message to the generic list of messages associated with the session. These messages retain their order of insertion and
-     * remain until the {@link #clearMessages()} method is called.
-     *
-     * @param message the message to add; if <code>null</code> it is ignored.
-     */
-    public void addMessage( final String message )
-    {
+    /** {@inheritDoc} */
+    @Override
+    public void addMessage( final String message ) {
         addMessage( ALL, message );
     }
 
-    /**
-     * Adds a message to the specific set of messages associated with the session. These messages retain their order of insertion and
-     * remain until the {@link #clearMessages()} method is called.
-     *
-     * @param topic the topic to associate the message to;
-     * @param message the message to add
-     */
+    /** {@inheritDoc} */
+    @Override
     public void addMessage( final String topic, final String message ) {
         if ( topic == null ) {
             throw new IllegalArgumentException( "addMessage: topic cannot be null." );
@@ -266,19 +168,14 @@ public final class WikiSession implements WikiEventListener {
         messages.add( StringUtils.defaultString( message ) );
     }
 
-    /**
-     * Clears all messages associated with this session.
-     */
-    public void clearMessages()
-    {
+    /** {@inheritDoc} */
+    @Override
+    public void clearMessages() {
         m_messages.clear();
     }
 
-    /**
-     * Clears all messages associated with a session topic.
-     *
-     * @param topic the topic whose messages should be cleared.
-     */
+    /** {@inheritDoc} */
+    @Override
     public void clearMessages( final String topic ) {
         final Set< String > messages = m_messages.get( topic );
         if ( messages != null ) {
@@ -286,39 +183,24 @@ public final class WikiSession implements WikiEventListener {
         }
     }
 
-    /**
-     * Returns all generic messages associated with this session.
-     * The messages stored with the session persist throughout the
-     * session unless they have been reset with {@link #clearMessages()}.
-     * @return the current messages.
-     */
-    public String[] getMessages()
-    {
+    /** {@inheritDoc} */
+    @Override
+    public String[] getMessages() {
         return getMessages( ALL );
     }
 
-    /**
-     * Returns all messages associated with a session topic.
-     * The messages stored with the session persist throughout the
-     * session unless they have been reset with {@link #clearMessages(String)}.
-     * @return the current messages.
-     * @param topic The topic
-     */
+    /** {@inheritDoc} */
+    @Override
     public String[] getMessages( final String topic ) {
         final Set< String > messages = m_messages.get( topic );
         if( messages == null || messages.size() == 0 ) {
-            return new String[0];
+            return new String[ 0 ];
         }
-        return messages.toArray( new String[messages.size()] );
+        return messages.toArray( new String[ messages.size() ] );
     }
 
-    /**
-     * Returns all user Principals associated with this session. User principals are those in the Subject's principal collection that
-     * aren't of type Role or of type GroupPrincipal. This is a defensive copy.
-     *
-     * @return Returns the user principal
-     * @see org.apache.wiki.auth.AuthenticationManager#isUserPrincipal(Principal)
-     */
+    /** {@inheritDoc} */
+    @Override
     public Principal[] getPrincipals() {
         final ArrayList< Principal > principals = new ArrayList<>();
 
@@ -329,18 +211,11 @@ public final class WikiSession implements WikiEventListener {
             }
         }
 
-        return principals.toArray( new Principal[principals.size()] );
+        return principals.toArray( new Principal[ principals.size() ] );
     }
 
-    /**
-     * Returns an array of Principal objects that represents the groups and roles that the user associated with a WikiSession possesses.
-     * The array is built by iterating through the Subject's Principal set and extracting all Role and GroupPrincipal objects into a
-     * list. The list is returned as an array sorted in the natural order implied by each Principal's <code>getName</code> method. Note
-     * that this method does <em>not</em> consult the external Authorizer or GroupManager; it relies on the Principals that have been
-     * injected into the user's Subject at login time, or after group creation/modification/deletion.
-     *
-     * @return an array of Principal objects corresponding to the roles the Subject possesses
-     */
+    /** {@inheritDoc} */
+    @Override
     public Principal[] getRoles() {
         final Set< Principal > roles = new HashSet<>();
 
@@ -351,170 +226,150 @@ public final class WikiSession implements WikiEventListener {
         roles.addAll( m_subject.getPrincipals( GroupPrincipal.class ) );
 
         // Return a defensive copy
-        final Principal[] roleArray = roles.toArray( new Principal[roles.size()] );
+        final Principal[] roleArray = roles.toArray( new Principal[ roles.size() ] );
         Arrays.sort( roleArray, WikiPrincipal.COMPARATOR );
         return roleArray;
     }
 
-    /**
-     * Removes the wiki session associated with the user's HTTP request from the cache of wiki sessions, typically as part of a
-     * logout process.
-     *
-     * @param engine the wiki engine
-     * @param request the users's HTTP request
-     */
-    public static void removeWikiSession( final Engine engine, final HttpServletRequest request ) {
-        if ( engine == null || request == null ) {
-            throw new IllegalArgumentException( "Request or engine cannot be null." );
-        }
-        final SessionMonitor monitor = SessionMonitor.getInstance( engine );
-        monitor.remove( request.getSession() );
-    }
-
-    /**
-     * Returns <code>true</code> if the WikiSession's Subject possess a supplied Principal. This method eliminates the need to externally
-     * request and inspect the JAAS subject.
-     *
-     * @param principal the Principal to test
-     * @return the result
-     */
+    /** {@inheritDoc} */
+    @Override
     public boolean hasPrincipal( final Principal principal ) {
         return m_subject.getPrincipals().contains( principal );
     }
 
     /**
-     * Listens for WikiEvents generated by source objects such as the GroupManager. This method adds Principals to the private Subject
-     * managed by the WikiSession.
+     * Listens for WikiEvents generated by source objects such as the GroupManager, UserManager or AuthenticationManager. This method adds
+     * Principals to the private Subject managed by the WikiSession.
      *
-     * @see org.apache.wiki.event.WikiEventListener#actionPerformed(org.apache.wiki.event.WikiEvent)
+     * @see org.apache.wiki.event.WikiEventListener#actionPerformed(WikiEvent)
      */
     @Override
     public void actionPerformed( final WikiEvent event ) {
         if ( event instanceof WikiSecurityEvent ) {
             final WikiSecurityEvent e = (WikiSecurityEvent)event;
             if ( e.getTarget() != null ) {
-                switch (e.getType() ) {
-                    case WikiSecurityEvent.GROUP_ADD:
-                        final Group groupAdd = (Group)e.getTarget();
-                        if ( isInGroup( groupAdd ) ) {
-                            m_subject.getPrincipals().add( groupAdd.getPrincipal() );
-                        }
-                        break;
-                    case WikiSecurityEvent.GROUP_REMOVE:
-                        final Group group = (Group)e.getTarget();
-                        m_subject.getPrincipals().remove( group.getPrincipal() );
-                        break;
-                    case WikiSecurityEvent.GROUP_CLEAR_GROUPS:
-                        m_subject.getPrincipals().removeAll( m_subject.getPrincipals( GroupPrincipal.class ) );
-                        break;
-                    case WikiSecurityEvent.LOGIN_INITIATED:
-                        // Do nothing
-                        break;
-                    case WikiSecurityEvent.PRINCIPAL_ADD:
-                        final WikiSession targetPA = (WikiSession)e.getTarget();
-                        if ( this.equals( targetPA ) && m_status.equals(AUTHENTICATED) ) {
-                            final Set<Principal> principals = m_subject.getPrincipals();
-                            principals.add( ( Principal )e.getPrincipal() );
-                        }
-                        break;
-                    case WikiSecurityEvent.LOGIN_ANONYMOUS:
-                        final WikiSession targetLAN = (WikiSession)e.getTarget();
-                        if( this.equals( targetLAN ) ) {
-                            m_status = ANONYMOUS;
-
-                            // Set the login/user principals and login status
-                            final Set<Principal> principals = m_subject.getPrincipals();
-                            m_loginPrincipal = (Principal)e.getPrincipal();
-                            m_userPrincipal = m_loginPrincipal;
-
-                            // Add the login principal to the Subject, and set the built-in roles
-                            principals.clear();
-                            principals.add( m_loginPrincipal );
-                            principals.add( Role.ALL );
-                            principals.add( Role.ANONYMOUS );
+                switch( e.getType() ) {
+                case WikiSecurityEvent.GROUP_ADD:
+                    final Group groupAdd = ( Group )e.getTarget();
+                    if( isInGroup( groupAdd ) ) {
+                        m_subject.getPrincipals().add( groupAdd.getPrincipal() );
+                    }
+                    break;
+                case WikiSecurityEvent.GROUP_REMOVE:
+                    final Group group = ( Group )e.getTarget();
+                    m_subject.getPrincipals().remove( group.getPrincipal() );
+                    break;
+                case WikiSecurityEvent.GROUP_CLEAR_GROUPS:
+                    m_subject.getPrincipals().removeAll( m_subject.getPrincipals( GroupPrincipal.class ) );
+                    break;
+                case WikiSecurityEvent.LOGIN_INITIATED:
+                    // Do nothing
+                    break;
+                case WikiSecurityEvent.PRINCIPAL_ADD:
+                    final WikiSession targetPA = ( WikiSession )e.getTarget();
+                    if( this.equals( targetPA ) && m_status.equals( AUTHENTICATED ) ) {
+                        final Set< Principal > principals = m_subject.getPrincipals();
+                        principals.add( ( Principal )e.getPrincipal() );
+                    }
+                    break;
+                case WikiSecurityEvent.LOGIN_ANONYMOUS:
+                    final WikiSession targetLAN = ( WikiSession )e.getTarget();
+                    if( this.equals( targetLAN ) ) {
+                        m_status = ANONYMOUS;
+
+                        // Set the login/user principals and login status
+                        final Set< Principal > principals = m_subject.getPrincipals();
+                        m_loginPrincipal = ( Principal )e.getPrincipal();
+                        m_userPrincipal = m_loginPrincipal;
+
+                        // Add the login principal to the Subject, and set the built-in roles
+                        principals.clear();
+                        principals.add( m_loginPrincipal );
+                        principals.add( Role.ALL );
+                        principals.add( Role.ANONYMOUS );
+                    }
+                    break;
+                case WikiSecurityEvent.LOGIN_ASSERTED:
+                    final WikiSession targetLAS = ( WikiSession )e.getTarget();
+                    if( this.equals( targetLAS ) ) {
+                        m_status = ASSERTED;
+
+                        // Set the login/user principals and login status
+                        final Set< Principal > principals = m_subject.getPrincipals();
+                        m_loginPrincipal = ( Principal )e.getPrincipal();
+                        m_userPrincipal = m_loginPrincipal;
+
+                        // Add the login principal to the Subject, and set the built-in roles
+                        principals.clear();
+                        principals.add( m_loginPrincipal );
+                        principals.add( Role.ALL );
+                        principals.add( Role.ASSERTED );
+                    }
+                    break;
+                case WikiSecurityEvent.LOGIN_AUTHENTICATED:
+                    final WikiSession targetLAU = ( WikiSession )e.getTarget();
+                    if( this.equals( targetLAU ) ) {
+                        m_status = AUTHENTICATED;
+
+                        // Set the login/user principals and login status
+                        final Set< Principal > principals = m_subject.getPrincipals();
+                        m_loginPrincipal = ( Principal )e.getPrincipal();
+                        m_userPrincipal = m_loginPrincipal;
+
+                        // Add the login principal to the Subject, and set the built-in roles
+                        principals.clear();
+                        principals.add( m_loginPrincipal );
+                        principals.add( Role.ALL );
+                        principals.add( Role.AUTHENTICATED );
+
+                        // Add the user and group principals
+                        injectUserProfilePrincipals();  // Add principals for the user profile
+                        injectGroupPrincipals();  // Inject group principals
+                    }
+                    break;
+                case WikiSecurityEvent.PROFILE_SAVE:
+                    final WikiSession sourcePS = e.getSrc();
+                    if( this.equals( sourcePS ) ) {
+                        injectUserProfilePrincipals();  // Add principals for the user profile
+                        injectGroupPrincipals();  // Inject group principals
+                    }
+                    break;
+                case WikiSecurityEvent.PROFILE_NAME_CHANGED:
+                    // Refresh user principals based on new user profile
+                    final WikiSession sourcePNC = e.getSrc();
+                    if( this.equals( sourcePNC ) && m_status.equals( AUTHENTICATED ) ) {
+                        // To prepare for refresh, set the new full name as the primary principal
+                        final UserProfile[] profiles = ( UserProfile[] )e.getTarget();
+                        final UserProfile newProfile = profiles[ 1 ];
+                        if( newProfile.getFullname() == null ) {
+                            throw new IllegalStateException( "User profile FullName cannot be null." );
                         }
-                        break;
-                    case WikiSecurityEvent.LOGIN_ASSERTED:
-                        final WikiSession targetLAS = (WikiSession)e.getTarget();
-                        if ( this.equals( targetLAS ) ) {
-                            m_status = ASSERTED;
-
-                            // Set the login/user principals and login status
-                            final Set<Principal> principals = m_subject.getPrincipals();
-                            m_loginPrincipal = (Principal)e.getPrincipal();
-                            m_userPrincipal = m_loginPrincipal;
-
-                            // Add the login principal to the Subject, and set the built-in roles
-                            principals.clear();
-                            principals.add( m_loginPrincipal );
-                            principals.add( Role.ALL );
-                            principals.add( Role.ASSERTED );
-                        }
-                        break;
-                    case WikiSecurityEvent.LOGIN_AUTHENTICATED:
-                        final WikiSession targetLAU = (WikiSession)e.getTarget();
-                        if ( this.equals( targetLAU ) ) {
-                            m_status = AUTHENTICATED;
-
-                            // Set the login/user principals and login status
-                            final Set<Principal> principals = m_subject.getPrincipals();
-                            m_loginPrincipal = (Principal)e.getPrincipal();
-                            m_userPrincipal = m_loginPrincipal;
-
-                            // Add the login principal to the Subject, and set the built-in roles
-                            principals.clear();
-                            principals.add( m_loginPrincipal );
-                            principals.add( Role.ALL );
-                            principals.add( Role.AUTHENTICATED );
-
-                            // Add the user and group principals
-                            injectUserProfilePrincipals();  // Add principals for the user profile
-                            injectGroupPrincipals();  // Inject group principals
-                        }
-                        break;
-                    case WikiSecurityEvent.PROFILE_SAVE:
-                        final WikiSession sourcePS = e.getSrc();
-                        if ( this.equals( sourcePS ) ) {
-                            injectUserProfilePrincipals();  // Add principals for the user profile
-                            injectGroupPrincipals();  // Inject group principals
-                        }
-                        break;
-                    case WikiSecurityEvent.PROFILE_NAME_CHANGED:
-                        // Refresh user principals based on new user profile
-                        final WikiSession sourcePNC = e.getSrc();
-                        if ( this.equals( sourcePNC ) && m_status.equals(AUTHENTICATED) ) {
-                            // To prepare for refresh, set the new full name as the primary principal
-                            final UserProfile[] profiles = (UserProfile[])e.getTarget();
-                            final UserProfile newProfile = profiles[1];
-                            if ( newProfile.getFullname() == null ) {
-                                throw new IllegalStateException( "User profile FullName cannot be null." );
-                            }
-
-                            final Set<Principal> principals = m_subject.getPrincipals();
-                            m_loginPrincipal = new WikiPrincipal( newProfile.getLoginName() );
-
-                            // Add the login principal to the Subject, and set the built-in roles
-                            principals.clear();
-                            principals.add( m_loginPrincipal );
-                            principals.add( Role.ALL );
-                            principals.add( Role.AUTHENTICATED );
-
-                            // Add the user and group principals
-                            injectUserProfilePrincipals();  // Add principals for the user profile
-                            injectGroupPrincipals();  // Inject group principals
-                        }
-                        break;
 
-                    //  No action, if the event is not recognized.
-                    default: break;
+                        final Set< Principal > principals = m_subject.getPrincipals();
+                        m_loginPrincipal = new WikiPrincipal( newProfile.getLoginName() );
+
+                        // Add the login principal to the Subject, and set the built-in roles
+                        principals.clear();
+                        principals.add( m_loginPrincipal );
+                        principals.add( Role.ALL );
+                        principals.add( Role.AUTHENTICATED );
+
+                        // Add the user and group principals
+                        injectUserProfilePrincipals();  // Add principals for the user profile
+                        injectGroupPrincipals();  // Inject group principals
+                    }
+                    break;
+
+                //  No action, if the event is not recognized.
+                default:
+                    break;
                 }
             }
         }
     }
 
-    /**
-     * Invalidates the WikiSession and resets its Subject's Principals to the equivalent of a "guest session".
-     */
+    /** {@inheritDoc} */
+    @Override
     public void invalidate() {
         m_subject.getPrincipals().clear();
         m_subject.getPrincipals().add( WikiPrincipal.GUEST );
@@ -587,33 +442,47 @@ public final class WikiSession implements WikiEventListener {
         }
     }
 
-    /**
-     * <p>Returns the status of the wiki session as a text string. Valid values are:</p>
-     * <ul>
-     *   <li>{@link #AUTHENTICATED}</li>
-     *   <li>{@link #ASSERTED}</li>
-     *   <li>{@link #ANONYMOUS}</li>
-     * </ul>
-     * @return the user's session status
-     */
+    /** {@inheritDoc} */
+    @Override
     public String getStatus() {
         return m_status;
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public Subject getSubject() {
+        return m_subject;
+    }
+
     /**
-     * <p>Static factory method that returns the WikiSession object associated with the current HTTP request. This method looks up
+     * Removes the wiki session associated with the user's HTTP request from the cache of wiki sessions, typically as part of a
+     * logout process.
+     *
+     * @param engine the wiki engine
+     * @param request the users's HTTP request
+     */
+    public static void removeWikiSession( final Engine engine, final HttpServletRequest request ) {
+        if ( engine == null || request == null ) {
+            throw new IllegalArgumentException( "Request or engine cannot be null." );
+        }
+        final SessionMonitor monitor = SessionMonitor.getInstance( engine );
+        monitor.remove( request.getSession() );
+    }
+
+    /**
+     * <p>Static factory method that returns the Session object associated with the current HTTP request. This method looks up
      * the associated HttpSession in an internal WeakHashMap and attempts to retrieve the WikiSession. If not found, one is created.
-     * This method is guaranteed to always return a WikiSession, although the authentication status is unpredictable until the user
+     * This method is guaranteed to always return a Session, although the authentication status is unpredictable until the user
      * attempts to log in. If the servlet request parameter is <code>null</code>, a synthetic {@link #guestSession(Engine)} is
      * returned.</p>
-     * <p>When a session is created, this method attaches a WikiEventListener to the GroupManager so that changes to groups are detected
-     * automatically.</p>
+     * <p>When a session is created, this method attaches a WikiEventListener to the GroupManager, UserManager and AuthenticationManager,
+     * so that changes to users, groups, logins, etc. are detected automatically.</p>
      *
-     * @param engine the wiki engine
+     * @param engine the engine
      * @param request the servlet request object
-     * @return the existing (or newly created) wiki session
+     * @return the existing (or newly created) session
      */
-    public static WikiSession getWikiSession( final Engine engine, final HttpServletRequest request ) {
+    public static Session getWikiSession( final Engine engine, final HttpServletRequest request ) {
         if ( request == null ) {
             if ( log.isDebugEnabled() ) {
                 log.debug( "Looking up WikiSession for NULL HttpRequest: returning guestSession()" );
@@ -640,7 +509,7 @@ public final class WikiSession implements WikiEventListener {
      * @param engine the wiki engine
      * @return the guest wiki session
      */
-    public static WikiSession guestSession( final Engine engine ) {
+    public static Session guestSession( final Engine engine ) {
         final WikiSession session = new WikiSession();
         session.m_engine = engine;
         session.invalidate();
@@ -664,8 +533,8 @@ public final class WikiSession implements WikiEventListener {
      *  @return A static WikiSession which is shared by all in this same Thread.
      */
     // FIXME: Should really use WeakReferences to clean away unused sessions.
-    private static WikiSession staticGuestSession( final Engine engine ) {
-        WikiSession session = c_guestSession.get();
+    private static Session staticGuestSession( final Engine engine ) {
+        Session session = c_guestSession.get();
         if( session == null ) {
             session = guestSession( engine );
             c_guestSession.set( session );
@@ -699,54 +568,4 @@ public final class WikiSession implements WikiEventListener {
         return monitor.userPrincipals();
     }
 
-    /**
-     * Wrapper for
-     * {@link javax.security.auth.Subject#doAsPrivileged(Subject, java.security.PrivilegedExceptionAction, java.security.AccessControlContext)}
-     * that executes an action with the privileges posssessed by a WikiSession's Subject. The action executes with a <code>null</code>
-     * AccessControlContext, which has the effect of running it "cleanly" without the AccessControlContexts of the caller.
-     *
-     * @param session the wiki session
-     * @param action the privileged action
-     * @return the result of the privileged action; may be <code>null</code>
-     * @throws java.security.AccessControlException if the action is not permitted by the security policy
-     */
-    public static Object doPrivileged( final WikiSession session, final PrivilegedAction<?> action ) throws AccessControlException {
-        return Subject.doAsPrivileged( session.m_subject, action, null );
-    }
-
-    /**
-     * Verifies whether a String represents an IPv4 address. The algorithm is
-     * extremely efficient and does not allocate any objects.
-     * @param name the address to test
-     * @return the result
-     */
-    protected static boolean isIPV4Address( final String name ) {
-        if ( name.charAt( 0 ) == DOT || name.charAt( name.length() - 1 ) == DOT ) {
-            return false;
-        }
-
-        final int[] addr = new int[]
-        { 0, 0, 0, 0 };
-        int currentOctet = 0;
-        for( int i = 0; i < name.length(); i++ ) {
-            final int ch = name.charAt( i );
-            final boolean isDigit = ch >= ONE && ch <= NINE;
-            final boolean isDot = ch == DOT;
-            if ( !isDigit && !isDot ) {
-                return false;
-            }
-            if( isDigit ) {
-                addr[currentOctet] = 10 * addr[currentOctet] + ( ch - ONE );
-                if ( addr[currentOctet] > 255 ) {
-                    return false;
-                }
-            } else if( name.charAt( i - 1 ) == DOT ) {
-                return false;
-            } else {
-                currentOctet++;
-            }
-        }
-        return  currentOctet == 3;
-    }
-
 }
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/api/core/Session.java b/jspwiki-main/src/main/java/org/apache/wiki/api/core/Session.java
new file mode 100644
index 0000000..8dce0d8
--- /dev/null
+++ b/jspwiki-main/src/main/java/org/apache/wiki/api/core/Session.java
@@ -0,0 +1,248 @@
+/*
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+ */
+package org.apache.wiki.api.core;
+
+import org.apache.wiki.event.WikiEventListener;
+
+import javax.security.auth.Subject;
+import java.security.AccessControlException;
+import java.security.Principal;
+import java.security.PrivilegedAction;
+import java.util.Locale;
+
+
+/**
+ * <p>Represents a long-running wiki session, with an associated user Principal, user Subject, and authentication status. The sesion
+ * is initialized with minimal, default-deny values: authentication is set to <code>false</code>, and the user principal is set to
+ * <code>null</code>.</p>
+ * <p>The Session allows callers to:</p>
+ * <ul>
+ *   <li>Obtain the authentication status of the user via
+ *     {@link #isAnonymous()} and {@link #isAuthenticated()}</li>
+ *   <li>Query the session for Principals representing the
+ *     user's identity via {@link #getLoginPrincipal()},
+ *     {@link #getUserPrincipal()} and {@link #getPrincipals()}</li>
+ *   <li>Store, retrieve and clear UI messages via
+ *     {@link #addMessage(String)}, {@link #getMessages(String)}
+ *     and {@link #clearMessages(String)}</li>
+ * </ul>
+ * <p>To keep track of the Principals each user posseses, each Session stores a JAAS Subject. Various login processes add or
+ * remove Principals when users authenticate or log out.</p>
+ * <p>Session extends the {@link org.apache.wiki.event.WikiEventListener} interface and listens for group add/change/delete
+ * events fired by event sources the Session is registered with: {@link org.apache.wiki.auth.AuthenticationManager},
+ * {@link org.apache.wiki.auth.UserManager} and {@link org.apache.wiki.auth.authorize.GroupManager}, so it can catch group events. Thus,
+ * when a user is added to a {@link org.apache.wiki.auth.authorize.Group}, a corresponding {@link org.apache.wiki.auth.GroupPrincipal} is
+ * injected into the Subject's Principal set. Likewise, when the user is removed from the Group or the Group is deleted, the
+ * GroupPrincipal is removed from the Subject. The effect that this strategy produces is extremely beneficial: when someone adds a user
+ * to a wiki group, that user <em>immediately</em> gains the privileges associated with that group; he or she does not need to
+ * re-authenticate.
+ * </p>
+ * <p>In addition to methods for examining individual <code>Session</code> objects, this class also contains a number of static
+ * methods for managing WikiSessions for an entire wiki. These methods allow callers to find, query and remove WikiSession objects, and
+ * to obtain a list of the current wiki session users.</p>
+ */
+public interface Session extends WikiEventListener {
+
+    /** An anonymous user's session status. */
+    String  ANONYMOUS = "anonymous";
+
+    /** An asserted user's session status. */
+    String  ASSERTED = "asserted";
+
+    /** An authenticated user's session status. */
+    String  AUTHENTICATED = "authenticated";
+
+    /**
+     * Returns <code>true</code> if the user is considered asserted via a session cookie; that is, the Subject contains the Principal
+     * Role.ASSERTED.
+     *
+     * @return Returns <code>true</code> if the user is asserted
+     */
+    boolean isAsserted();
+
+    /**
+     * Returns the authentication status of the user's session. The user is considered authenticated if the Subject contains the
+     * Principal Role.AUTHENTICATED. If this method determines that an earlier LoginModule did not inject Role.AUTHENTICATED, it
+     * will inject one if the user is not anonymous <em>and</em> not asserted.
+     *
+     * @return Returns <code>true</code> if the user is authenticated
+     */
+    boolean isAuthenticated();
+
+    /**
+     * <p>Determines whether the current session is anonymous. This will be true if any of these conditions are true:</p>
+     * <ul>
+     *   <li>The session's Principal set contains {@link org.apache.wiki.auth.authorize.Role#ANONYMOUS}</li>
+     *   <li>The session's Principal set contains {@link org.apache.wiki.auth.WikiPrincipal#GUEST}</li>
+     *   <li>The Principal returned by {@link #getUserPrincipal()} evaluates to an IP address.</li>
+     * </ul>
+     * <p>The criteria above are listed in the order in which they are evaluated.</p>
+     * @return whether the current user's identity is equivalent to an IP address
+     */
+    boolean isAnonymous();
+
+    /**
+     * <p> Returns the Principal used to log in to an authenticated session. The login principal is determined by examining the
+     * Subject's Principal set for PrincipalWrappers or WikiPrincipals with type designator <code>LOGIN_NAME</code>; the first one
+     * found is the login principal. If one is not found, this method returns the first principal that isn't of type Role or
+     * GroupPrincipal. If neither of these conditions hold, this method returns
+     * {@link org.apache.wiki.auth.WikiPrincipal#GUEST}.
+     *
+     * @return the login Principal. If it is a PrincipalWrapper containing an externally-provided Principal, the object returned is the
+     * Principal, not the wrapper around it.
+     */
+    Principal getLoginPrincipal();
+
+    /**
+     * <p>Returns the primary user Principal associated with this session. The primary user principal is determined as follows:</p>
+     * <ol>
+     *     <li>If the Subject's Principal set contains WikiPrincipals, the first WikiPrincipal with type designator
+     *         <code>WIKI_NAME</code> or (alternatively) <code>FULL_NAME</code> is the primary Principal.</li>
+     *     <li>For all other cases, the first Principal in the Subject's principal collection that that isn't of type Role or
+     *         GroupPrincipal is the primary.</li>
+     * </ol>
+     * If no primary user Principal is found, this method returns {@link org.apache.wiki.auth.WikiPrincipal#GUEST}.
+     *
+     * @return the primary user Principal
+     */
+    Principal getUserPrincipal();
+
+    /**
+     *  Returns a cached Locale object for this user.  It's better to use WikiContext's corresponding getBundle() method, since that
+     *  will actually react if the user changes the locale in the middle, but if that's not available (or, for some reason, you need
+     *  the speed), this method can also be used.  The Locale expires when the WikiSession expires, and currently there is no way to
+     *  reset the Locale.
+     *
+     *  @return A cached Locale object
+     *  @since 2.5.96
+     */
+    Locale getLocale();
+
+    /**
+     * Adds a message to the generic list of messages associated with the session. These messages retain their order of insertion and
+     * remain until the {@link #clearMessages()} method is called.
+     *
+     * @param message the message to add; if <code>null</code> it is ignored.
+     */
+    void addMessage( String message );
+
+    /**
+     * Adds a message to the specific set of messages associated with the session. These messages retain their order of insertion and
+     * remain until the {@link #clearMessages()} method is called.
+     *
+     * @param topic the topic to associate the message to;
+     * @param message the message to add
+     */
+    void addMessage( String topic, String message );
+
+    /**
+     * Clears all messages associated with this session.
+     */
+    void clearMessages();
+
+    /**
+     * Clears all messages associated with a session topic.
+     *
+     * @param topic the topic whose messages should be cleared.
+     */
+    void clearMessages( String topic );
+
+    /**
+     * Returns all generic messages associated with this session. The messages stored with the session persist throughout the
+     * session unless they have been reset with {@link #clearMessages()}.
+     *
+     * @return the current messages.
+     */
+    String[] getMessages();
+
+    /**
+     * Returns all messages associated with a session topic. The messages stored with the session persist throughout the
+     * session unless they have been reset with {@link #clearMessages(String)}.
+     *
+     * @return the current messages.
+     * @param topic The topic
+     */
+    String[] getMessages( String topic );
+
+    /**
+     * Returns all user Principals associated with this session. User principals are those in the Subject's principal collection that
+     * aren't of type Role or of type GroupPrincipal. This is a defensive copy.
+     *
+     * @return Returns the user principal
+     * @see org.apache.wiki.auth.AuthenticationManager#isUserPrincipal(Principal)
+     */
+    Principal[] getPrincipals();
+
+    /**
+     * Returns an array of Principal objects that represents the groups and roles that the user associated with a WikiSession possesses.
+     * The array is built by iterating through the Subject's Principal set and extracting all Role and GroupPrincipal objects into a
+     * list. The list is returned as an array sorted in the natural order implied by each Principal's <code>getName</code> method. Note
+     * that this method does <em>not</em> consult the external Authorizer or GroupManager; it relies on the Principals that have been
+     * injected into the user's Subject at login time, or after group creation/modification/deletion.
+     *
+     * @return an array of Principal objects corresponding to the roles the Subject possesses
+     */
+    Principal[] getRoles();
+
+    /**
+     * Returns <code>true</code> if the WikiSession's Subject possess a supplied Principal. This method eliminates the need to externally
+     * request and inspect the JAAS subject.
+     *
+     * @param principal the Principal to test
+     * @return the result
+     */
+    boolean hasPrincipal( Principal principal );
+
+    /** Invalidates the WikiSession and resets its Subject's Principals to the equivalent of a "guest session". */
+    void invalidate();
+
+    /**
+     * <p>Returns the status of the wiki session as a text string. Valid values are:</p>
+     * <ul>
+     *   <li>{@link #AUTHENTICATED}</li>
+     *   <li>{@link #ASSERTED}</li>
+     *   <li>{@link #ANONYMOUS}</li>
+     * </ul>
+     *
+     * @return the user's session status
+     */
+    String getStatus();
+
+    /**
+     * Returns the {@link Subject} associated to the session.
+     *
+     * @return {@link Subject} associated to the session.
+     */
+    Subject getSubject();
+
+    /**
+     * Wrapper for {@link Subject#doAsPrivileged(Subject, PrivilegedAction, java.security.AccessControlContext)}
+     * that executes an action with the privileges posssessed by a WikiSession's Subject. The action executes with a <code>null</code>
+     * AccessControlContext, which has the effect of running it "cleanly" without the AccessControlContexts of the caller.
+     *
+     * @param session the wiki session
+     * @param action the privileged action
+     * @return the result of the privileged action; may be <code>null</code>
+     * @throws java.security.AccessControlException if the action is not permitted by the security policy
+     */
+    static Object doPrivileged( final Session session, final PrivilegedAction<?> action ) throws AccessControlException {
+        return Subject.doAsPrivileged( session.getSubject(), action, null );
+    }
+
+}