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 <[email protected]> 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>
