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 <[email protected]> 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 ); + } + +}
