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/02/24 16:52:47 UTC
[jspwiki] 05/38: JSPWIKI-120: rename + extract interface from
AuthenticationManager
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 380de2a22cf5ef84d3f6efd218d51429b93726fe
Author: juanpablo <ju...@apache.org>
AuthorDate: Wed Feb 19 20:39:01 2020 +0100
JSPWIKI-120: rename + extract interface from AuthenticationManager
---
.../apache/wiki/auth/AuthenticationManager.java | 679 +++------------------
.../wiki/auth/DefaultAuthenticationManager.java | 507 +++++++++++++++
.../src/main/resources/ini/classmappings.xml | 4 +-
3 files changed, 600 insertions(+), 590 deletions(-)
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/auth/AuthenticationManager.java b/jspwiki-main/src/main/java/org/apache/wiki/auth/AuthenticationManager.java
index b9eb341..197619a 100644
--- a/jspwiki-main/src/main/java/org/apache/wiki/auth/AuthenticationManager.java
+++ b/jspwiki-main/src/main/java/org/apache/wiki/auth/AuthenticationManager.java
@@ -18,598 +18,182 @@
*/
package org.apache.wiki.auth;
-import org.apache.log4j.Logger;
-import org.apache.wiki.WikiEngine;
import org.apache.wiki.WikiSession;
-import org.apache.wiki.api.exceptions.WikiException;
import org.apache.wiki.auth.authorize.Role;
-import org.apache.wiki.auth.authorize.WebAuthorizer;
-import org.apache.wiki.auth.authorize.WebContainerAuthorizer;
-import org.apache.wiki.auth.login.AnonymousLoginModule;
import org.apache.wiki.auth.login.CookieAssertionLoginModule;
import org.apache.wiki.auth.login.CookieAuthenticationLoginModule;
-import org.apache.wiki.auth.login.UserDatabaseLoginModule;
-import org.apache.wiki.auth.login.WebContainerCallbackHandler;
-import org.apache.wiki.auth.login.WebContainerLoginModule;
-import org.apache.wiki.auth.login.WikiCallbackHandler;
import org.apache.wiki.event.WikiEventListener;
import org.apache.wiki.event.WikiEventManager;
import org.apache.wiki.event.WikiSecurityEvent;
-import org.apache.wiki.util.TextUtil;
-import org.apache.wiki.util.TimedCounterList;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpSession;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.lang.reflect.InvocationTargetException;
-import java.net.MalformedURLException;
-import java.net.URL;
import java.security.Principal;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
import java.util.Map;
-import java.util.Properties;
import java.util.Set;
+
/**
- * Manages authentication activities for a WikiEngine: user login, logout, and
- * credential refreshes. This class uses JAAS to determine how users log in.
+ * Manages authentication activities for a WikiEngine: user login, logout, and credential refreshes. This class uses JAAS to determine how
+ * users log in.
* <p>
- * The login procedure is protected in addition by a mechanism which prevents
- * a hacker to try and force-guess passwords by slowing down attempts to log in
- * into the same account. Every login attempt is recorded, and stored for a while
- * (currently ten minutes), and each login attempt during that time incurs a penalty
- * of 2^login attempts milliseconds - that is, 10 login attempts incur a login penalty of 1.024 seconds.
- * The delay is currently capped to 20 seconds.
+ * The login procedure is protected in addition by a mechanism which prevents a hacker to try and force-guess passwords by slowing down
+ * attempts to log in into the same account. Every login attempt is recorded, and stored for a while (currently ten minutes), and each
+ * login attempt during that time incurs a penalty of 2^login attempts milliseconds - that is, 10 login attempts incur a login penalty
+ * of 1.024 seconds. The delay is currently capped to 20 seconds.
*
* @since 2.3
*/
-public class AuthenticationManager {
-
- /** How many milliseconds the logins are stored before they're cleaned away. */
- private static final long LASTLOGINS_CLEANUP_TIME = 10*60*1000L; // Ten minutes
+public interface AuthenticationManager {
- private static final long MAX_LOGIN_DELAY = 20*1000L; // 20 seconds
-
/** The name of the built-in cookie assertion module */
- public static final String COOKIE_MODULE = CookieAssertionLoginModule.class.getName();
+ String COOKIE_MODULE = CookieAssertionLoginModule.class.getName();
/** The name of the built-in cookie authentication module */
- public static final String COOKIE_AUTHENTICATION_MODULE = CookieAuthenticationLoginModule.class.getName();
+ String COOKIE_AUTHENTICATION_MODULE = CookieAuthenticationLoginModule.class.getName();
/** If this jspwiki.properties property is <code>true</code>, logs the IP address of the editor on saving. */
- public static final String PROP_STOREIPADDRESS = "jspwiki.storeIPAddress";
+ String PROP_STOREIPADDRESS = "jspwiki.storeIPAddress";
/** If this jspwiki.properties property is <code>true</code>, allow cookies to be used for authentication. */
- public static final String PROP_ALLOW_COOKIE_AUTH = "jspwiki.cookieAuthentication";
+ String PROP_ALLOW_COOKIE_AUTH = "jspwiki.cookieAuthentication";
/** Whether logins should be throttled to limit brute-forcing attempts. Defaults to true. */
- public static final String PROP_LOGIN_THROTTLING = "jspwiki.login.throttling";
-
- protected static final Logger log = Logger.getLogger( AuthenticationManager.class );
+ String PROP_LOGIN_THROTTLING = "jspwiki.login.throttling";
/** Prefix for LoginModule options key/value pairs. */
- protected static final String PREFIX_LOGIN_MODULE_OPTIONS = "jspwiki.loginModule.options.";
+ String PREFIX_LOGIN_MODULE_OPTIONS = "jspwiki.loginModule.options.";
/** If this jspwiki.properties property is <code>true</code>, allow cookies to be used to assert identities. */
- protected static final String PROP_ALLOW_COOKIE_ASSERTIONS = "jspwiki.cookieAssertions";
-
- /** The {@link javax.security.auth.spi.LoginModule} to use for custom authentication. */
- protected static final String PROP_LOGIN_MODULE = "jspwiki.loginModule.class";
-
- /** Empty Map passed to JAAS {@link #doJAASLogin(Class, CallbackHandler, Map)} method. */
- protected static final Map<String,String> EMPTY_MAP = Collections.unmodifiableMap( new HashMap<String,String>() );
-
- /** Class (of type LoginModule) to use for custom authentication. */
- protected Class<? extends LoginModule> m_loginModuleClass = UserDatabaseLoginModule.class;
-
- /** Options passed to {@link javax.security.auth.spi.LoginModule#initialize(Subject, CallbackHandler, Map, Map)};
- * initialized by {@link #initialize(WikiEngine, Properties)}. */
- protected Map<String,String> m_loginModuleOptions = new HashMap<String,String>();
-
- /** The default {@link javax.security.auth.spi.LoginModule} class name to use for custom authentication. */
- private static final String DEFAULT_LOGIN_MODULE = "org.apache.wiki.auth.login.UserDatabaseLoginModule";
-
- /** Empty principal set. */
- private static final Set<Principal> NO_PRINCIPALS = new HashSet<>();
-
- /** Static Boolean for lazily-initializing the "allows assertions" flag */
- private boolean m_allowsCookieAssertions = true;
-
- private boolean m_throttleLogins = true;
-
- /** Static Boolean for lazily-initializing the "allows cookie authentication" flag */
- private boolean m_allowsCookieAuthentication = false;
-
- private WikiEngine m_engine = null;
-
- /** If true, logs the IP address of the editor */
- private boolean m_storeIPAddress = true;
-
- /** Keeps a list of the usernames who have attempted a login recently. */
- private TimedCounterList<String> m_lastLoginAttempts = new TimedCounterList<String>();
-
- /**
- * Creates an AuthenticationManager instance for the given WikiEngine and
- * the specified set of properties. All initialization for the modules is
- * done here.
- * @param engine the wiki engine
- * @param props the properties used to initialize the wiki engine
- * @throws WikiException if the AuthenticationManager cannot be initialized
- */
- @SuppressWarnings("unchecked")
- public void initialize( WikiEngine engine, Properties props ) throws WikiException
- {
- m_engine = engine;
- m_storeIPAddress = TextUtil.getBooleanProperty( props, PROP_STOREIPADDRESS, m_storeIPAddress );
-
- // Should we allow cookies for assertions? (default: yes)
- m_allowsCookieAssertions = TextUtil.getBooleanProperty( props,
- PROP_ALLOW_COOKIE_ASSERTIONS,
- true );
-
- // Should we allow cookies for authentication? (default: no)
- m_allowsCookieAuthentication = TextUtil.getBooleanProperty( props,
- PROP_ALLOW_COOKIE_AUTH,
- false );
-
- // Should we throttle logins? (default: yes)
- m_throttleLogins = TextUtil.getBooleanProperty( props,
- PROP_LOGIN_THROTTLING,
- true );
+ String PROP_ALLOW_COOKIE_ASSERTIONS = "jspwiki.cookieAssertions";
- // Look up the LoginModule class
- String loginModuleClassName = TextUtil.getStringProperty( props, PROP_LOGIN_MODULE, DEFAULT_LOGIN_MODULE );
- try
- {
- m_loginModuleClass = (Class<? extends LoginModule>) Class.forName( loginModuleClassName );
- }
- catch (ClassNotFoundException e)
- {
- log.error( e.getMessage(), e );
- throw new WikiException( "Could not instantiate LoginModule class.", e );
- }
-
- // Initialize the LoginModule options
- initLoginModuleOptions( props );
- }
+ /** The {@link LoginModule} to use for custom authentication. */
+ String PROP_LOGIN_MODULE = "jspwiki.loginModule.class";
/**
- * Returns true if this WikiEngine uses container-managed authentication.
- * This method is used primarily for cosmetic purposes in the JSP tier, and
- * performs no meaningful security function per se. Delegates to
+ * Returns true if this WikiEngine uses container-managed authentication. This method is used primarily for cosmetic purposes in the
+ * JSP tier, and performs no meaningful security function per se. Delegates to
* {@link org.apache.wiki.auth.authorize.WebContainerAuthorizer#isContainerAuthorized()},
* if used as the external authorizer; otherwise, returns <code>false</code>.
- * @return <code>true</code> if the wiki's authentication is managed by
- * the container, <code>false</code> otherwise
+ *
+ * @return <code>true</code> if the wiki's authentication is managed by the container, <code>false</code> otherwise
*/
- public boolean isContainerAuthenticated()
- {
- try
- {
- Authorizer authorizer = m_engine.getAuthorizationManager().getAuthorizer();
- if ( authorizer instanceof WebContainerAuthorizer )
- {
- return ( ( WebContainerAuthorizer )authorizer ).isContainerAuthorized();
- }
- }
- catch ( WikiException e )
- {
- // It's probably ok to fail silently...
- }
- return false;
- }
+ boolean isContainerAuthenticated();
/**
- * <p>Logs in the user by attempting to populate a WikiSession Subject from
- * a web servlet request by examining the request
- * for the presence of container credentials and user cookies. The processing
- * logic is as follows:
+ * <p>Logs in the user by attempting to populate a WikiSession Subject from a web servlet request by examining the request
+ * for the presence of container credentials and user cookies. The processing logic is as follows:
* </p>
* <ul>
- * <li>If the WikiSession had previously been unauthenticated, check to see if
- * user has subsequently authenticated. To be considered "authenticated,"
- * the request must supply one of the following (in order of preference):
- * the container <code>userPrincipal</code>, container <code>remoteUser</code>,
- * or authentication cookie. If the user is authenticated, this method fires event
- * {@link org.apache.wiki.event.WikiSecurityEvent#LOGIN_AUTHENTICATED}
- * with two parameters: a Principal representing the login principal,
- * and the current WikiSession. In addition, if the authorizer is of type
- * WebContainerAuthorizer, this method iterates through the container roles returned by
- * {@link org.apache.wiki.auth.authorize.WebContainerAuthorizer#getRoles()},
- * tests for membership in each one, and adds those that pass to the Subject's principal set.</li>
- * <li>If, after checking for authentication, the WikiSession is still Anonymous,
- * this method next checks to see if the user has "asserted" an identity
- * by supplying an assertion cookie. If the user is found to be asserted,
- * this method fires event {@link org.apache.wiki.event.WikiSecurityEvent#LOGIN_ASSERTED}
- * with two parameters: <code>WikiPrincipal(<em>cookievalue</em>)</code>, and
- * the current WikiSession.</li>
- * <li>If, after checking for authenticated and asserted status, the WikiSession is
- * <em>still</em> anonymous, this method fires event
- * {@link org.apache.wiki.event.WikiSecurityEvent#LOGIN_ANONYMOUS} with
- * two parameters: <code>WikiPrincipal(<em>remoteAddress</em>)</code>,
+ * <li>If the WikiSession had previously been unauthenticated, check to see if user has subsequently authenticated. To be considered
+ * "authenticated," the request must supply one of the following (in order of preference): the container <code>userPrincipal</code>,
+ * container <code>remoteUser</code>, or authentication cookie. If the user is authenticated, this method fires event
+ * {@link org.apache.wiki.event.WikiSecurityEvent#LOGIN_AUTHENTICATED} with two parameters: a Principal representing the login principal,
+ * and the current WikiSession. In addition, if the authorizer is of type WebContainerAuthorizer, this method iterates through the
+ * container roles returned by {@link org.apache.wiki.auth.authorize.WebContainerAuthorizer#getRoles()}, tests for membership in each
+ * one, and adds those that pass to the Subject's principal set.</li>
+ * <li>If, after checking for authentication, the WikiSession is still Anonymous, this method next checks to see if the user has
+ * "asserted" an identity by supplying an assertion cookie. If the user is found to be asserted, this method fires event
+ * {@link org.apache.wiki.event.WikiSecurityEvent#LOGIN_ASSERTED} with two parameters: <code>WikiPrincipal(<em>cookievalue</em>)</code>,
+ * and the current WikiSession.</li>
+ * <li>If, after checking for authenticated and asserted status, the WikiSession is <em>still</em> anonymous, this method fires event
+ * {@link org.apache.wiki.event.WikiSecurityEvent#LOGIN_ANONYMOUS} with two parameters: <code>WikiPrincipal(<em>remoteAddress</em>)</code>,
* and the current WikiSession </li>
* </ul>
+ *
* @param request servlet request for this user
* @return always returns <code>true</code> (because anonymous login, at least, will always succeed)
* @throws org.apache.wiki.auth.WikiSecurityException if the user cannot be logged in for any reason
* @since 2.3
*/
- public boolean login( HttpServletRequest request ) throws WikiSecurityException
- {
- HttpSession httpSession = request.getSession();
- WikiSession session = SessionMonitor.getInstance(m_engine).find( httpSession );
- AuthenticationManager authenticationMgr = m_engine.getAuthenticationManager();
- AuthorizationManager authorizationMgr = m_engine.getAuthorizationManager();
- CallbackHandler handler = null;
- Map<String,String> options = EMPTY_MAP;
-
- // If user not authenticated, check if container logged them in, or if
- // there's an authentication cookie
- if ( !session.isAuthenticated() )
- {
- // Create a callback handler
- handler = new WebContainerCallbackHandler( m_engine, request );
-
- // Execute the container login module, then (if that fails) the cookie auth module
- Set<Principal> principals = authenticationMgr.doJAASLogin( WebContainerLoginModule.class, handler, options );
- if ( principals.size() == 0 && authenticationMgr.allowsCookieAuthentication() )
- {
- principals = authenticationMgr.doJAASLogin( CookieAuthenticationLoginModule.class, handler, options );
- }
-
- // If the container logged the user in successfully, tell the WikiSession (and add all of the Principals)
- if ( principals.size() > 0 )
- {
- fireEvent( WikiSecurityEvent.LOGIN_AUTHENTICATED, getLoginPrincipal( principals ), session );
- for ( Principal principal : principals )
- {
- fireEvent( WikiSecurityEvent.PRINCIPAL_ADD, principal, session );
- }
-
- // Add all appropriate Authorizer roles
- injectAuthorizerRoles( session, authorizationMgr.getAuthorizer(), request );
- }
- }
-
- // If user still not authenticated, check if assertion cookie was supplied
- if ( !session.isAuthenticated() && authenticationMgr.allowsCookieAssertions() )
- {
- // Execute the cookie assertion login module
- Set<Principal> principals = authenticationMgr.doJAASLogin( CookieAssertionLoginModule.class, handler, options );
- if ( principals.size() > 0 )
- {
- fireEvent( WikiSecurityEvent.LOGIN_ASSERTED, getLoginPrincipal( principals ), session);
- }
- }
-
- // If user still anonymous, use the remote address
- if (session.isAnonymous() )
- {
- Set<Principal> principals = authenticationMgr.doJAASLogin( AnonymousLoginModule.class, handler, options );
- if ( principals.size() > 0 )
- {
- fireEvent( WikiSecurityEvent.LOGIN_ANONYMOUS, getLoginPrincipal( principals ), session );
- return true;
- }
- }
-
- // If by some unusual turn of events the Anonymous login module doesn't work, login failed!
- return false;
- }
+ boolean login( HttpServletRequest request ) throws WikiSecurityException;
/**
- * Attempts to perform a WikiSession login for the given username/password
- * combination using JSPWiki's custom authentication mode. In order to log in,
- * the JAAS LoginModule supplied by the WikiEngine property {@link #PROP_LOGIN_MODULE}
- * will be instantiated, and its
- * {@link javax.security.auth.spi.LoginModule#initialize(Subject, CallbackHandler, Map, Map)}
- * method will be invoked. By default, the {@link org.apache.wiki.auth.login.UserDatabaseLoginModule}
- * class will be used. When the LoginModule's <code>initialize</code> method is invoked,
- * an options Map populated by properties keys prefixed by {@link #PREFIX_LOGIN_MODULE_OPTIONS}
- * will be passed as a parameter.
+ * Attempts to perform a WikiSession login for the given username/password combination using JSPWiki's custom authentication mode. In
+ * order to log in, the JAAS LoginModule supplied by the WikiEngine property {@link #PROP_LOGIN_MODULE} will be instantiated, and its
+ * {@link javax.security.auth.spi.LoginModule#initialize(Subject, CallbackHandler, Map, Map)} method will be invoked. By default,
+ * the {@link org.apache.wiki.auth.login.UserDatabaseLoginModule} class will be used. When the LoginModule's <code>initialize</code>
+ * method is invoked, an options Map populated by properties keys prefixed by {@link #PREFIX_LOGIN_MODULE_OPTIONS} will be passed as a
+ * parameter.
+ *
* @param session the current wiki session; may not be <code>null</code>.
- * @param request the user's HTTP request. This parameter may be <code>null</code>, but the configured
- * LoginModule will not have access to the HTTP request in this case.
- * @param username The user name. This is a login name, not a WikiName. In
- * most cases they are the same, but in some cases, they might
- * not be.
+ * @param request the user's HTTP request. This parameter may be <code>null</code>, but the configured LoginModule will not have access
+ * to the HTTP request in this case.
+ * @param username The user name. This is a login name, not a WikiName. In most cases they are the same, but in some cases, they might not be.
* @param password the password
* @return true, if the username/password is valid
* @throws org.apache.wiki.auth.WikiSecurityException if the Authorizer or UserManager cannot be obtained
*/
- public boolean login( WikiSession session, HttpServletRequest request, String username, String password ) throws WikiSecurityException
- {
- if ( session == null )
- {
- log.error( "No wiki session provided, cannot log in." );
- return false;
- }
-
- // Protect against brute-force password guessing if configured to do so
- if ( m_throttleLogins )
- {
- delayLogin(username);
- }
-
- CallbackHandler handler = new WikiCallbackHandler( m_engine, null, username, password );
-
- // Execute the user's specified login module
- Set<Principal> principals = doJAASLogin( m_loginModuleClass, handler, m_loginModuleOptions );
- if (principals.size() > 0)
- {
- fireEvent(WikiSecurityEvent.LOGIN_AUTHENTICATED, getLoginPrincipal( principals ), session );
- for ( Principal principal : principals )
- {
- fireEvent( WikiSecurityEvent.PRINCIPAL_ADD, principal, session );
- }
-
- // Add all appropriate Authorizer roles
- injectAuthorizerRoles( session, m_engine.getAuthorizationManager().getAuthorizer(), null );
-
- return true;
- }
- return false;
- }
-
- /**
- * This method builds a database of login names that are being attempted, and will try to
- * delay if there are too many requests coming in for the same username.
- * <p>
- * The current algorithm uses 2^loginattempts as the delay in milliseconds, i.e.
- * at 10 login attempts it'll add 1.024 seconds to the login.
- *
- * @param username The username that is being logged in
- */
- private void delayLogin( String username )
- {
- try
- {
- m_lastLoginAttempts.cleanup( LASTLOGINS_CLEANUP_TIME );
- int count = m_lastLoginAttempts.count( username );
-
- long delay = Math.min( 1<<count, MAX_LOGIN_DELAY );
- log.debug( "Sleeping for "+delay+" ms to allow login." );
- Thread.sleep( delay );
-
- m_lastLoginAttempts.add( username );
- }
- catch( InterruptedException e )
- {
- // FALLTHROUGH is fine
- }
- }
+ boolean login( WikiSession session, HttpServletRequest request, String username, String password ) throws WikiSecurityException;
/**
- * Logs the user out by retrieving the WikiSession associated with the
- * HttpServletRequest and unbinding all of the Subject's Principals,
- * except for {@link Role#ALL}, {@link Role#ANONYMOUS}.
- * is a cheap-and-cheerful way to do it without invoking JAAS LoginModules.
- * The logout operation will also flush the JSESSIONID cookie from
- * the user's browser session, if it was set.
+ * Logs the user out by retrieving the WikiSession associated with the HttpServletRequest and unbinding all of the Subject's Principals,
+ * except for {@link Role#ALL}, {@link Role#ANONYMOUS}. is a cheap-and-cheerful way to do it without invoking JAAS LoginModules.
+ * The logout operation will also flush the JSESSIONID cookie from the user's browser session, if it was set.
+ *
* @param request the current HTTP request
*/
- public void logout( HttpServletRequest request )
- {
- if( request == null )
- {
- log.error( "No HTTP reqest provided; cannot log out." );
- return;
- }
-
- HttpSession session = request.getSession();
- String sid = ( session == null ) ? "(null)" : session.getId();
- if( log.isDebugEnabled() )
- {
- log.debug( "Invalidating WikiSession for session ID=" + sid );
- }
- // Retrieve the associated WikiSession and clear the Principal set
- WikiSession wikiSession = WikiSession.getWikiSession( m_engine, request );
- Principal originalPrincipal = wikiSession.getLoginPrincipal();
- wikiSession.invalidate();
-
- // Remove the wikiSession from the WikiSession cache
- WikiSession.removeWikiSession( m_engine, request );
-
- // We need to flush the HTTP session too
- if ( session != null )
- {
- session.invalidate();
- }
-
- // Log the event
- fireEvent( WikiSecurityEvent.LOGOUT, originalPrincipal, null );
- }
+ void logout( HttpServletRequest request );
/**
- * Determines whether this WikiEngine allows users to assert identities using
- * cookies instead of passwords. This is determined by inspecting
+ * Determines whether this WikiEngine allows users to assert identities using cookies instead of passwords. This is determined by inspecting
* the WikiEngine property {@link #PROP_ALLOW_COOKIE_ASSERTIONS}.
+ *
* @return <code>true</code> if cookies are allowed
*/
- public boolean allowsCookieAssertions()
- {
- return m_allowsCookieAssertions;
- }
+ boolean allowsCookieAssertions();
/**
- * Determines whether this WikiEngine allows users to authenticate using
- * cookies instead of passwords. This is determined by inspecting
+ * Determines whether this WikiEngine allows users to authenticate using cookies instead of passwords. This is determined by inspecting
* the WikiEngine property {@link #PROP_ALLOW_COOKIE_AUTH}.
+ *
* @return <code>true</code> if cookies are allowed for authentication
* @since 2.5.62
*/
- public boolean allowsCookieAuthentication()
- {
- return m_allowsCookieAuthentication;
- }
+ boolean allowsCookieAuthentication();
+
+ /**
+ * Instantiates and executes a single JAAS {@link LoginModule}, and returns a Set of Principals that results from a successful login.
+ * The LoginModule is instantiated, then its {@link LoginModule#initialize(Subject, CallbackHandler, Map, Map)} method is called. The
+ * parameters passed to <code>initialize</code> is a dummy Subject, an empty shared-state Map, and an options Map the caller supplies.
+ *
+ * @param clazz the LoginModule class to instantiate
+ * @param handler the callback handler to supply to the LoginModule
+ * @param options a Map of key/value strings for initializing the LoginModule
+ * @return the set of Principals returned by the JAAS method {@link Subject#getPrincipals()}
+ * @throws WikiSecurityException if the LoginModule could not be instantiated for any reason
+ */
+ Set< Principal > doJAASLogin( Class<? extends LoginModule> clazz, CallbackHandler handler, Map< String, String > options) throws WikiSecurityException;
/**
* Determines whether the supplied Principal is a "role principal".
+ *
* @param principal the principal to test
- * @return <code>true</code> if the Principal is of type
- * {@link GroupPrincipal} or
- * {@link org.apache.wiki.auth.authorize.Role},
- * <code>false</code> otherwise
+ * @return {@code true} if the Principal is of type {@link GroupPrincipal} or {@link Role}, {@code false} otherwise.
*/
- public static boolean isRolePrincipal( final Principal principal ) {
+ static boolean isRolePrincipal( final Principal principal ) {
return principal instanceof Role || principal instanceof GroupPrincipal;
}
/**
* Determines whether the supplied Principal is a "user principal".
+ *
* @param principal the principal to test
- * @return <code>false</code> if the Principal is of type
- * {@link GroupPrincipal} or
- * {@link org.apache.wiki.auth.authorize.Role},
- * <code>true</code> otherwise
+ * @return {@code false} if the Principal is of type {@link GroupPrincipal} or {@link Role}, {@code true} otherwise.
*/
- public static boolean isUserPrincipal( final Principal principal ) {
+ static boolean isUserPrincipal( final Principal principal ) {
return !isRolePrincipal( principal );
}
/**
- * Instantiates and executes a single JAAS
- * {@link javax.security.auth.spi.LoginModule}, and returns a Set of
- * Principals that results from a successful login. The LoginModule is instantiated,
- * then its {@link javax.security.auth.spi.LoginModule#initialize(Subject, CallbackHandler, Map, Map)}
- * method is called. The parameters passed to <code>initialize</code> is a
- * dummy Subject, an empty shared-state Map, and an options Map the caller supplies.
- *
- * @param clazz the LoginModule class to instantiate
- * @param handler the callback handler to supply to the LoginModule
- * @param options a Map of key/value strings for initializing the LoginModule
- * @return the set of Principals returned by the JAAS method {@link Subject#getPrincipals()}
- * @throws WikiSecurityException if the LoginModule could not be instantiated for any reason
- */
- protected Set<Principal> doJAASLogin(Class<? extends LoginModule> clazz, CallbackHandler handler, Map<String,String> options) throws WikiSecurityException
- {
- // Instantiate the login module
- final LoginModule loginModule;
- try {
- loginModule = clazz.getDeclaredConstructor().newInstance();
- } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
- throw new WikiSecurityException(e.getMessage(), e );
- }
-
- // Initialize the LoginModule
- Subject subject = new Subject();
- loginModule.initialize( subject, handler, EMPTY_MAP, options );
-
- // Try to log in:
- boolean loginSucceeded = false;
- boolean commitSucceeded = false;
- try
- {
- loginSucceeded = loginModule.login();
- if (loginSucceeded)
- {
- commitSucceeded = loginModule.commit();
- }
- }
- catch (LoginException e)
- {
- // Login or commit failed! No principal for you!
- }
-
- // If we successfully logged in & committed, return all the principals
- if (loginSucceeded && commitSucceeded)
- {
- return subject.getPrincipals();
- }
- return NO_PRINCIPALS;
- }
-
- /**
- * Looks up and obtains a configuration file inside the WEB-INF folder of a
- * wiki webapp.
- * @param engine the wiki engine
- * @param name the file to obtain, <em>e.g.</em>, <code>jspwiki.policy</code>
- * @return the URL to the file
- */
- protected static URL findConfigFile( WikiEngine engine, String name )
- {
- log.info( "looking for " + name + " inside WEB-INF " );
- // Try creating an absolute path first
- File defaultFile = null;
- if( engine.getRootPath() != null )
- {
- defaultFile = new File( engine.getRootPath() + "/WEB-INF/" + name );
- }
- if ( defaultFile != null && defaultFile.exists() )
- {
- try
- {
- return defaultFile.toURI().toURL();
- }
- catch ( MalformedURLException e)
- {
- // Shouldn't happen, but log it if it does
- log.warn( "Malformed URL: " + e.getMessage() );
- }
-
- }
-
-
- // Ok, the absolute path didn't work; try other methods
-
- URL path = null;
-
- if( engine.getServletContext() != null ) {
- final File tmpFile;
- try {
- tmpFile = File.createTempFile( "temp." + name, "" );
- } catch( final IOException e ) {
- log.error( "unable to create a temp file to load onto the policy", e );
- return null;
- }
- tmpFile.deleteOnExit();
- log.info( "looking for /" + name + " on classpath" );
- // create a tmp file of the policy loaded as an InputStream and return the URL to it
- try( final InputStream is = AuthenticationManager.class.getResourceAsStream( "/" + name );
- final OutputStream os = new FileOutputStream( tmpFile ) ) {
- if( is == null ) {
- throw new FileNotFoundException( name + " not found" );
- }
- final URL url = engine.getServletContext().getResource( "/WEB-INF/" + name );
- if( url != null ) {
- return url;
- }
-
- final byte[] buff = new byte[1024];
- int bytes;
- while( ( bytes = is.read( buff ) ) != -1 ) {
- os.write( buff, 0, bytes );
- }
-
- path = tmpFile.toURI().toURL();
- } catch( final MalformedURLException e ) {
- // This should never happen unless I screw up
- log.fatal( "Your code is b0rked. You are a bad person.", e );
- } catch( final IOException e ) {
- log.error( "failed to load security policy from file " + name + ",stacktrace follows", e );
- }
- }
- return path;
- }
-
- /**
- * Returns the first Principal in a set that isn't a {@link org.apache.wiki.auth.authorize.Role} or
- * {@link org.apache.wiki.auth.GroupPrincipal}.
+ * Returns the first Principal in a set that isn't a {@link Role} or {@link GroupPrincipal}.
+ *
* @param principals the principal set
* @return the login principal
*/
- protected Principal getLoginPrincipal( final Set< Principal > principals ) {
+ default Principal getLoginPrincipal( final Set< Principal > principals ) {
for( final Principal principal : principals ) {
if ( isUserPrincipal( principal ) ) {
return principal;
@@ -621,112 +205,31 @@ public class AuthenticationManager {
// events processing .......................................................
/**
- * Registers a WikiEventListener with this instance.
- * This is a convenience method.
+ * Registers a WikiEventListener with this instance. This is a convenience method.
+ *
* @param listener the event listener
*/
- public synchronized void addWikiEventListener( final WikiEventListener listener ) {
- WikiEventManager.addWikiEventListener( this, listener );
- }
+ void addWikiEventListener( WikiEventListener listener );
/**
- * Un-registers a WikiEventListener with this instance.
- * This is a convenience method.
+ * Un-registers a WikiEventListener with this instance. This is a convenience method.
+ *
* @param listener the event listener
*/
- public synchronized void removeWikiEventListener( final WikiEventListener listener ) {
- WikiEventManager.removeWikiEventListener( this, listener );
- }
+ void removeWikiEventListener( final WikiEventListener listener );
/**
- * Fires a WikiSecurityEvent of the provided type, Principal and target Object
- * to all registered listeners.
+ * Fires a WikiSecurityEvent of the provided type, Principal and target Object to all registered listeners.
*
* @see org.apache.wiki.event.WikiSecurityEvent
* @param type the event type to be fired
* @param principal the subject of the event, which may be <code>null</code>
* @param target the changed Object, which may be <code>null</code>
*/
- protected void fireEvent( final int type, final Principal principal, final Object target ) {
+ default void fireEvent( final int type, final Principal principal, final Object target ) {
if ( WikiEventManager.isListening( this ) ) {
WikiEventManager.fireEvent( this, new WikiSecurityEvent( this, type, principal, target ) );
}
}
-
- /**
- * Initializes the options Map supplied to the configured LoginModule every time it is invoked.
- * The properties and values extracted from
- * <code>jspwiki.properties</code> are of the form
- * <code>jspwiki.loginModule.options.<var>param</var> = <var>value</var>, where
- * <var>param</var> is the key name, and <var>value</var> is the value.
- * @param props the properties used to initialize JSPWiki
- * @throws IllegalArgumentException if any of the keys are duplicated
- */
- private void initLoginModuleOptions(Properties props)
- {
- for ( Object key : props.keySet() )
- {
- String propName = key.toString();
- if ( propName.startsWith( PREFIX_LOGIN_MODULE_OPTIONS ) )
- {
- // Extract the option name and value
- String optionKey = propName.substring( PREFIX_LOGIN_MODULE_OPTIONS.length() ).trim();
- if ( optionKey.length() > 0 )
- {
- String optionValue = props.getProperty( propName );
-
- // Make sure the key is unique before stashing the key/value pair
- if ( m_loginModuleOptions.containsKey( optionKey ) )
- {
- throw new IllegalArgumentException( "JAAS LoginModule key " + propName + " cannot be specified twice!" );
- }
- m_loginModuleOptions.put( optionKey, optionValue );
- }
- }
- }
- }
-
- /**
- * After successful login, this method is called to inject authorized role Principals into the WikiSession.
- * To determine which roles should be injected, the configured Authorizer
- * is queried for the roles it knows about by calling {@link org.apache.wiki.auth.Authorizer#getRoles()}.
- * Then, each role returned by the authorizer is tested by calling {@link org.apache.wiki.auth.Authorizer#isUserInRole(WikiSession, Principal)}.
- * If this check fails, and the Authorizer is of type WebAuthorizer, the role is checked again by calling
- * {@link org.apache.wiki.auth.authorize.WebAuthorizer#isUserInRole(javax.servlet.http.HttpServletRequest, Principal)}).
- * Any roles that pass the test are injected into the Subject by firing appropriate authentication events.
- * @param session the user's current WikiSession
- * @param authorizer the WikiEngine's configured Authorizer
- * @param request the user's HTTP session, which may be <code>null</code>
- */
- private void injectAuthorizerRoles( WikiSession session, Authorizer authorizer, HttpServletRequest request )
- {
- // Test each role the authorizer knows about
- for ( Principal role : authorizer.getRoles() )
- {
- // Test the Authorizer
- if ( authorizer.isUserInRole( session, role ) )
- {
- fireEvent( WikiSecurityEvent.PRINCIPAL_ADD, role, session );
- if ( log.isDebugEnabled() )
- {
- log.debug("Added authorizer role " + role.getName() + "." );
- }
- }
-
- // If web authorizer, test the request.isInRole() method also
- else if ( request != null && authorizer instanceof WebAuthorizer )
- {
- WebAuthorizer wa = (WebAuthorizer)authorizer;
- if ( wa.isUserInRole( request, role ) )
- {
- fireEvent( WikiSecurityEvent.PRINCIPAL_ADD, role, session );
- if ( log.isDebugEnabled() )
- {
- log.debug("Added container role " + role.getName() + "." );
- }
- }
- }
- }
- }
}
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/auth/DefaultAuthenticationManager.java b/jspwiki-main/src/main/java/org/apache/wiki/auth/DefaultAuthenticationManager.java
new file mode 100644
index 0000000..547d28b
--- /dev/null
+++ b/jspwiki-main/src/main/java/org/apache/wiki/auth/DefaultAuthenticationManager.java
@@ -0,0 +1,507 @@
+/*
+ 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.auth;
+
+import org.apache.log4j.Logger;
+import org.apache.wiki.WikiEngine;
+import org.apache.wiki.WikiSession;
+import org.apache.wiki.api.core.Engine;
+import org.apache.wiki.api.exceptions.WikiException;
+import org.apache.wiki.auth.authorize.WebAuthorizer;
+import org.apache.wiki.auth.authorize.WebContainerAuthorizer;
+import org.apache.wiki.auth.login.AnonymousLoginModule;
+import org.apache.wiki.auth.login.CookieAssertionLoginModule;
+import org.apache.wiki.auth.login.CookieAuthenticationLoginModule;
+import org.apache.wiki.auth.login.UserDatabaseLoginModule;
+import org.apache.wiki.auth.login.WebContainerCallbackHandler;
+import org.apache.wiki.auth.login.WebContainerLoginModule;
+import org.apache.wiki.auth.login.WikiCallbackHandler;
+import org.apache.wiki.event.WikiEventListener;
+import org.apache.wiki.event.WikiEventManager;
+import org.apache.wiki.event.WikiSecurityEvent;
+import org.apache.wiki.util.TextUtil;
+import org.apache.wiki.util.TimedCounterList;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.Principal;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+
+/**
+ * Default implementation for {@link AuthenticationManager}
+ *
+ * {@inheritDoc}
+ *
+ * @since 2.3
+ */
+public class DefaultAuthenticationManager implements AuthenticationManager {
+
+ /** How many milliseconds the logins are stored before they're cleaned away. */
+ private static final long LASTLOGINS_CLEANUP_TIME = 10 * 60 * 1_000L; // Ten minutes
+
+ private static final long MAX_LOGIN_DELAY = 20 * 1_000L; // 20 seconds
+
+ private static final Logger log = Logger.getLogger( DefaultAuthenticationManager.class );
+
+ /** Empty Map passed to JAAS {@link #doJAASLogin(Class, CallbackHandler, Map)} method. */
+ protected static final Map< String, String > EMPTY_MAP = Collections.unmodifiableMap( new HashMap<>() );
+
+ /** Class (of type LoginModule) to use for custom authentication. */
+ protected Class< ? extends LoginModule > m_loginModuleClass = UserDatabaseLoginModule.class;
+
+ /** Options passed to {@link LoginModule#initialize(Subject, CallbackHandler, Map, Map)};
+ * initialized by {@link #initialize(Engine, Properties)}. */
+ protected Map< String, String > m_loginModuleOptions = new HashMap<>();
+
+ /** The default {@link LoginModule} class name to use for custom authentication. */
+ private static final String DEFAULT_LOGIN_MODULE = "org.apache.wiki.auth.login.UserDatabaseLoginModule";
+
+ /** Empty principal set. */
+ private static final Set<Principal> NO_PRINCIPALS = new HashSet<>();
+
+ /** Static Boolean for lazily-initializing the "allows assertions" flag */
+ private boolean m_allowsCookieAssertions = true;
+
+ private boolean m_throttleLogins = true;
+
+ /** Static Boolean for lazily-initializing the "allows cookie authentication" flag */
+ private boolean m_allowsCookieAuthentication = false;
+
+ private Engine m_engine = null;
+
+ /** If true, logs the IP address of the editor */
+ private boolean m_storeIPAddress = true;
+
+ /** Keeps a list of the usernames who have attempted a login recently. */
+ private TimedCounterList< String > m_lastLoginAttempts = new TimedCounterList<>();
+
+ /**
+ * Creates an AuthenticationManager instance for the given WikiEngine and
+ * the specified set of properties. All initialization for the modules is
+ * done here.
+ * @param engine the wiki engine
+ * @param props the properties used to initialize the wiki engine
+ * @throws WikiException if the AuthenticationManager cannot be initialized
+ */
+ @SuppressWarnings( "unchecked" )
+ public void initialize( final Engine engine, final Properties props ) throws WikiException {
+ m_engine = engine;
+ m_storeIPAddress = TextUtil.getBooleanProperty( props, PROP_STOREIPADDRESS, m_storeIPAddress );
+
+ // Should we allow cookies for assertions? (default: yes)
+ m_allowsCookieAssertions = TextUtil.getBooleanProperty( props, PROP_ALLOW_COOKIE_ASSERTIONS,true );
+
+ // Should we allow cookies for authentication? (default: no)
+ m_allowsCookieAuthentication = TextUtil.getBooleanProperty( props, PROP_ALLOW_COOKIE_AUTH, false );
+
+ // Should we throttle logins? (default: yes)
+ m_throttleLogins = TextUtil.getBooleanProperty( props, PROP_LOGIN_THROTTLING, true );
+
+ // Look up the LoginModule class
+ final String loginModuleClassName = TextUtil.getStringProperty( props, PROP_LOGIN_MODULE, DEFAULT_LOGIN_MODULE );
+ try {
+ m_loginModuleClass = ( Class< ? extends LoginModule > )Class.forName( loginModuleClassName );
+ } catch( final ClassNotFoundException e ) {
+ log.error( e.getMessage(), e );
+ throw new WikiException( "Could not instantiate LoginModule class.", e );
+ }
+
+ // Initialize the LoginModule options
+ initLoginModuleOptions( props );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isContainerAuthenticated() {
+ try {
+ final Authorizer authorizer = m_engine.getManager( AuthorizationManager.class ).getAuthorizer();
+ if ( authorizer instanceof WebContainerAuthorizer ) {
+ return ( ( WebContainerAuthorizer )authorizer ).isContainerAuthorized();
+ }
+ } catch ( final WikiException e ) {
+ // It's probably ok to fail silently...
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean login( final HttpServletRequest request ) throws WikiSecurityException {
+ final HttpSession httpSession = request.getSession();
+ final WikiSession session = SessionMonitor.getInstance( m_engine.adapt( WikiEngine.class ) ).find( httpSession );
+ final AuthenticationManager authenticationMgr = m_engine.getManager( AuthenticationManager.class );
+ final AuthorizationManager authorizationMgr = m_engine.getManager( AuthorizationManager.class );
+ CallbackHandler handler = null;
+ final Map< String, String > options = EMPTY_MAP;
+
+ // If user not authenticated, check if container logged them in, or if there's an authentication cookie
+ if ( !session.isAuthenticated() ) {
+ // Create a callback handler
+ handler = new WebContainerCallbackHandler( m_engine.adapt( WikiEngine.class ), request );
+
+ // Execute the container login module, then (if that fails) the cookie auth module
+ Set< Principal > principals = authenticationMgr.doJAASLogin( WebContainerLoginModule.class, handler, options );
+ if ( principals.size() == 0 && authenticationMgr.allowsCookieAuthentication() ) {
+ principals = authenticationMgr.doJAASLogin( CookieAuthenticationLoginModule.class, handler, options );
+ }
+
+ // If the container logged the user in successfully, tell the WikiSession (and add all of the Principals)
+ if ( principals.size() > 0 ) {
+ fireEvent( WikiSecurityEvent.LOGIN_AUTHENTICATED, getLoginPrincipal( principals ), session );
+ for( final Principal principal : principals ) {
+ fireEvent( WikiSecurityEvent.PRINCIPAL_ADD, principal, session );
+ }
+
+ // Add all appropriate Authorizer roles
+ injectAuthorizerRoles( session, authorizationMgr.getAuthorizer(), request );
+ }
+ }
+
+ // If user still not authenticated, check if assertion cookie was supplied
+ if ( !session.isAuthenticated() && authenticationMgr.allowsCookieAssertions() ) {
+ // Execute the cookie assertion login module
+ final Set< Principal > principals = authenticationMgr.doJAASLogin( CookieAssertionLoginModule.class, handler, options );
+ if ( principals.size() > 0 ) {
+ fireEvent( WikiSecurityEvent.LOGIN_ASSERTED, getLoginPrincipal( principals ), session);
+ }
+ }
+
+ // If user still anonymous, use the remote address
+ if( session.isAnonymous() ) {
+ final Set< Principal > principals = authenticationMgr.doJAASLogin( AnonymousLoginModule.class, handler, options );
+ if( principals.size() > 0 ) {
+ fireEvent( WikiSecurityEvent.LOGIN_ANONYMOUS, getLoginPrincipal( principals ), session );
+ return true;
+ }
+ }
+
+ // If by some unusual turn of events the Anonymous login module doesn't work, login failed!
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean login( final WikiSession session, final HttpServletRequest request, final String username, final String password ) throws WikiSecurityException {
+ if ( session == null ) {
+ log.error( "No wiki session provided, cannot log in." );
+ return false;
+ }
+
+ // Protect against brute-force password guessing if configured to do so
+ if ( m_throttleLogins ) {
+ delayLogin( username );
+ }
+
+ final CallbackHandler handler = new WikiCallbackHandler( m_engine.adapt( WikiEngine.class ), null, username, password );
+
+ // Execute the user's specified login module
+ final Set< Principal > principals = doJAASLogin( m_loginModuleClass, handler, m_loginModuleOptions );
+ if( principals.size() > 0 ) {
+ fireEvent(WikiSecurityEvent.LOGIN_AUTHENTICATED, getLoginPrincipal( principals ), session );
+ for ( final Principal principal : principals ) {
+ fireEvent( WikiSecurityEvent.PRINCIPAL_ADD, principal, session );
+ }
+
+ // Add all appropriate Authorizer roles
+ injectAuthorizerRoles( session, m_engine.getManager( AuthorizationManager.class ).getAuthorizer(), null );
+
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * This method builds a database of login names that are being attempted, and will try to delay if there are too many requests coming
+ * in for the same username.
+ * <p>
+ * The current algorithm uses 2^loginattempts as the delay in milliseconds, i.e. at 10 login attempts it'll add 1.024 seconds to the login.
+ *
+ * @param username The username that is being logged in
+ */
+ private void delayLogin( final String username ) {
+ try {
+ m_lastLoginAttempts.cleanup( LASTLOGINS_CLEANUP_TIME );
+ final int count = m_lastLoginAttempts.count( username );
+
+ final long delay = Math.min( 1 << count, MAX_LOGIN_DELAY );
+ log.debug( "Sleeping for " + delay + " ms to allow login." );
+ Thread.sleep( delay );
+
+ m_lastLoginAttempts.add( username );
+ } catch( final InterruptedException e ) {
+ // FALLTHROUGH is fine
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void logout( final HttpServletRequest request ) {
+ if( request == null ) {
+ log.error( "No HTTP reqest provided; cannot log out." );
+ return;
+ }
+
+ final HttpSession session = request.getSession();
+ final String sid = ( session == null ) ? "(null)" : session.getId();
+ if( log.isDebugEnabled() ) {
+ log.debug( "Invalidating WikiSession for session ID=" + sid );
+ }
+ // Retrieve the associated WikiSession and clear the Principal set
+ final WikiSession wikiSession = WikiSession.getWikiSession( m_engine.adapt( WikiEngine.class ), request );
+ final Principal originalPrincipal = wikiSession.getLoginPrincipal();
+ wikiSession.invalidate();
+
+ // Remove the wikiSession from the WikiSession cache
+ WikiSession.removeWikiSession( m_engine.adapt( WikiEngine.class ), request );
+
+ // We need to flush the HTTP session too
+ if ( session != null ) {
+ session.invalidate();
+ }
+
+ // Log the event
+ fireEvent( WikiSecurityEvent.LOGOUT, originalPrincipal, null );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean allowsCookieAssertions() {
+ return m_allowsCookieAssertions;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean allowsCookieAuthentication() {
+ return m_allowsCookieAuthentication;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Set< Principal > doJAASLogin( final Class< ? extends LoginModule > clazz,
+ final CallbackHandler handler,
+ final Map< String, String > options ) throws WikiSecurityException {
+ // Instantiate the login module
+ final LoginModule loginModule;
+ try {
+ loginModule = clazz.getDeclaredConstructor().newInstance();
+ } catch( final InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e ) {
+ throw new WikiSecurityException( e.getMessage(), e );
+ }
+
+ // Initialize the LoginModule
+ final Subject subject = new Subject();
+ loginModule.initialize( subject, handler, EMPTY_MAP, options );
+
+ // Try to log in:
+ boolean loginSucceeded = false;
+ boolean commitSucceeded = false;
+ try {
+ loginSucceeded = loginModule.login();
+ if( loginSucceeded ) {
+ commitSucceeded = loginModule.commit();
+ }
+ } catch( final LoginException e ) {
+ // Login or commit failed! No principal for you!
+ }
+
+ // If we successfully logged in & committed, return all the principals
+ if( loginSucceeded && commitSucceeded ) {
+ return subject.getPrincipals();
+ }
+ return NO_PRINCIPALS;
+ }
+
+ /**
+ * Looks up and obtains a configuration file inside the WEB-INF folder of a wiki webapp.
+ *
+ * @param engine the wiki engine
+ * @param name the file to obtain, <em>e.g.</em>, <code>jspwiki.policy</code>
+ * @return the URL to the file
+ */
+ protected static URL findConfigFile( final Engine engine, final String name ) {
+ log.info( "looking for " + name + " inside WEB-INF " );
+ // Try creating an absolute path first
+ File defaultFile = null;
+ if( engine.getRootPath() != null ) {
+ defaultFile = new File( engine.getRootPath() + "/WEB-INF/" + name );
+ }
+ if ( defaultFile != null && defaultFile.exists() ) {
+ try {
+ return defaultFile.toURI().toURL();
+ } catch ( final MalformedURLException e ) {
+ // Shouldn't happen, but log it if it does
+ log.warn( "Malformed URL: " + e.getMessage() );
+ }
+ }
+
+
+ // Ok, the absolute path didn't work; try other methods
+ URL path = null;
+
+ if( engine.getServletContext() != null ) {
+ final File tmpFile;
+ try {
+ tmpFile = File.createTempFile( "temp." + name, "" );
+ } catch( final IOException e ) {
+ log.error( "unable to create a temp file to load onto the policy", e );
+ return null;
+ }
+ tmpFile.deleteOnExit();
+ log.info( "looking for /" + name + " on classpath" );
+ // create a tmp file of the policy loaded as an InputStream and return the URL to it
+ try( final InputStream is = DefaultAuthenticationManager.class.getResourceAsStream( "/" + name );
+ final OutputStream os = new FileOutputStream( tmpFile ) ) {
+ if( is == null ) {
+ throw new FileNotFoundException( name + " not found" );
+ }
+ final URL url = engine.getServletContext().getResource( "/WEB-INF/" + name );
+ if( url != null ) {
+ return url;
+ }
+
+ final byte[] buff = new byte[1024];
+ int bytes;
+ while( ( bytes = is.read( buff ) ) != -1 ) {
+ os.write( buff, 0, bytes );
+ }
+
+ path = tmpFile.toURI().toURL();
+ } catch( final MalformedURLException e ) {
+ // This should never happen unless I screw up
+ log.fatal( "Your code is b0rked. You are a bad person.", e );
+ } catch( final IOException e ) {
+ log.error( "failed to load security policy from file " + name + ",stacktrace follows", e );
+ }
+ }
+ return path;
+ }
+
+ // events processing .......................................................
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public synchronized void addWikiEventListener( final WikiEventListener listener ) {
+ WikiEventManager.addWikiEventListener( this, listener );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public synchronized void removeWikiEventListener( final WikiEventListener listener ) {
+ WikiEventManager.removeWikiEventListener( this, listener );
+ }
+
+ /**
+ * Initializes the options Map supplied to the configured LoginModule every time it is invoked. The properties and values extracted from
+ * <code>jspwiki.properties</code> are of the form <code>jspwiki.loginModule.options.<var>param</var> = <var>value</var>, where
+ * <var>param</var> is the key name, and <var>value</var> is the value.
+ *
+ * @param props the properties used to initialize JSPWiki
+ * @throws IllegalArgumentException if any of the keys are duplicated
+ */
+ private void initLoginModuleOptions( final Properties props ) {
+ for( final Object key : props.keySet() ) {
+ final String propName = key.toString();
+ if( propName.startsWith( PREFIX_LOGIN_MODULE_OPTIONS ) ) {
+ // Extract the option name and value
+ final String optionKey = propName.substring( PREFIX_LOGIN_MODULE_OPTIONS.length() ).trim();
+ if( optionKey.length() > 0 ) {
+ final String optionValue = props.getProperty( propName );
+
+ // Make sure the key is unique before stashing the key/value pair
+ if ( m_loginModuleOptions.containsKey( optionKey ) ) {
+ throw new IllegalArgumentException( "JAAS LoginModule key " + propName + " cannot be specified twice!" );
+ }
+ m_loginModuleOptions.put( optionKey, optionValue );
+ }
+ }
+ }
+ }
+
+ /**
+ * After successful login, this method is called to inject authorized role Principals into the WikiSession. To determine which roles
+ * should be injected, the configured Authorizer is queried for the roles it knows about by calling {@link Authorizer#getRoles()}.
+ * Then, each role returned by the authorizer is tested by calling {@link Authorizer#isUserInRole(WikiSession, Principal)}. If this
+ * check fails, and the Authorizer is of type WebAuthorizer, the role is checked again by calling
+ * {@link WebAuthorizer#isUserInRole(HttpServletRequest, Principal)}). Any roles that pass the test are injected into the Subject by
+ * firing appropriate authentication events.
+ *
+ * @param session the user's current WikiSession
+ * @param authorizer the WikiEngine's configured Authorizer
+ * @param request the user's HTTP session, which may be <code>null</code>
+ */
+ private void injectAuthorizerRoles( final WikiSession session, final Authorizer authorizer, final HttpServletRequest request ) {
+ // Test each role the authorizer knows about
+ for( final Principal role : authorizer.getRoles() ) {
+ // Test the Authorizer
+ if( authorizer.isUserInRole( session, role ) ) {
+ fireEvent( WikiSecurityEvent.PRINCIPAL_ADD, role, session );
+ if( log.isDebugEnabled() ) {
+ log.debug( "Added authorizer role " + role.getName() + "." );
+ }
+ // If web authorizer, test the request.isInRole() method also
+ } else if ( request != null && authorizer instanceof WebAuthorizer ) {
+ final WebAuthorizer wa = ( WebAuthorizer )authorizer;
+ if ( wa.isUserInRole( request, role ) ) {
+ fireEvent( WikiSecurityEvent.PRINCIPAL_ADD, role, session );
+ if ( log.isDebugEnabled() ) {
+ log.debug( "Added container role " + role.getName() + "." );
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/jspwiki-main/src/main/resources/ini/classmappings.xml b/jspwiki-main/src/main/resources/ini/classmappings.xml
index 160761c..ff4ad5a 100644
--- a/jspwiki-main/src/main/resources/ini/classmappings.xml
+++ b/jspwiki-main/src/main/resources/ini/classmappings.xml
@@ -65,11 +65,11 @@
</mapping>
<mapping>
<requestedClass>org.apache.wiki.attachment.AttachmentManager</requestedClass>
- <mappedClass>org.apache.wiki.attachment.AttachmentManager</mappedClass>
+ <mappedClass>org.apache.wiki.attachment.DefaultAttachmentManager</mappedClass>
</mapping>
<mapping>
<requestedClass>org.apache.wiki.auth.AuthenticationManager</requestedClass>
- <mappedClass>org.apache.wiki.auth.AuthenticationManager</mappedClass>
+ <mappedClass>org.apache.wiki.auth.DefaultAuthenticationManager</mappedClass>
</mapping>
<mapping>
<requestedClass>org.apache.wiki.auth.AuthorizationManager</requestedClass>