You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jspwiki.apache.org by aj...@apache.org on 2008/04/01 05:44:26 UTC

svn commit: r643255 - in /incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth: AuthenticationManager.java SecurityVerifier.java

Author: ajaquith
Date: Mon Mar 31 20:44:22 2008
New Revision: 643255

URL: http://svn.apache.org/viewvc?rev=643255&view=rev
Log:
Re-factored the authentication subsystem to remove the need for JAAS configuration files. WEB-INF/jspwiki.jaas goes away, as does the need for PolicyLoader. Also, responsibilities for web authentication move to WikiServletFilter. Authentication is now configured via jspwiki.properties -- see that file for details. WikiSession API change: getLoginContext() vanishes.

Modified:
    incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/AuthenticationManager.java
    incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/SecurityVerifier.java

Modified: incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/AuthenticationManager.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/AuthenticationManager.java?rev=643255&r1=643254&r2=643255&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/AuthenticationManager.java (original)
+++ incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/AuthenticationManager.java Mon Mar 31 20:44:22 2008
@@ -1,34 +1,36 @@
 /*
- * JSPWiki - a JSP-based WikiWiki clone. Copyright (C) 2001-2003 Janne Jalkanen
- * (Janne.Jalkanen@iki.fi) This program is free software; you can redistribute
- * it and/or modify it under the terms of the GNU Lesser General Public License
- * as published by the Free Software Foundation; either version 2.1 of the
- * License, or (at your option) any later version. This program is distributed
- * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
- * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU Lesser General Public License for more details. You should have
- * received a copy of the GNU Lesser General Public License along with this
- * program; if not, write to the Free Software Foundation, Inc., 59 Temple
- * Place, Suite 330, Boston, MA 02111-1307 USA
+    JSPWiki - a JSP-based WikiWiki clone.
+
+    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 com.ecyrd.jspwiki.auth;
 
 import java.io.File;
 import java.net.MalformedURLException;
 import java.net.URL;
-import java.security.AccessController;
 import java.security.Principal;
-import java.security.PrivilegedAction;
-import java.util.Properties;
+import java.util.*;
 
+import javax.security.auth.Subject;
 import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.login.AccountExpiredException;
-import javax.security.auth.login.AppConfigurationEntry;
-import javax.security.auth.login.Configuration;
-import javax.security.auth.login.CredentialExpiredException;
-import javax.security.auth.login.FailedLoginException;
-import javax.security.auth.login.LoginContext;
 import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpSession;
 
@@ -40,10 +42,7 @@
 import com.ecyrd.jspwiki.WikiSession;
 import com.ecyrd.jspwiki.auth.authorize.Role;
 import com.ecyrd.jspwiki.auth.authorize.WebContainerAuthorizer;
-import com.ecyrd.jspwiki.auth.login.CookieAssertionLoginModule;
-import com.ecyrd.jspwiki.auth.login.CookieAuthenticationLoginModule;
-import com.ecyrd.jspwiki.auth.login.WebContainerCallbackHandler;
-import com.ecyrd.jspwiki.auth.login.WikiCallbackHandler;
+import com.ecyrd.jspwiki.auth.login.*;
 import com.ecyrd.jspwiki.event.WikiEventListener;
 import com.ecyrd.jspwiki.event.WikiEventManager;
 import com.ecyrd.jspwiki.event.WikiSecurityEvent;
@@ -58,64 +57,76 @@
  */
 public final class AuthenticationManager
 {
-
     /** The name of the built-in cookie assertion module */
     public static final 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();
 
-    /** The JAAS application name for the web container authentication stack. */
-    public static final String                 LOGIN_CONTAINER     = "JSPWiki-container";
-
-    /** The JAAS application name for the JSPWiki custom authentication stack. */
-    public static final String                 LOGIN_CUSTOM        = "JSPWiki-custom";
-
     /** 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";
+    
+    /** 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";
+    
+    /**
+     *  This property determines whether we use JSPWiki authentication or not.
+     *  Possible values are AUTH_JAAS or AUTH_CONTAINER.
+     */
+    public  static final String                PROP_SECURITY       = "jspwiki.security";
 
-    protected static final Logger              log                 = Logger.getLogger( AuthenticationManager.class );
+    /** Value specifying that the user wants to use the container-managed security, just like in JSPWiki 2.2. */
+    public static final String                SECURITY_OFF      = "off";
 
-    /** Was JAAS login config already set before we startd up? */
-    protected boolean m_isJaasConfiguredAtStartup = false;
+    /** Value specifying that the user wants to use the built-in JAAS-based system */
+    public static final String                SECURITY_JAAS     = "jaas";
 
-    /** Static Boolean for lazily-initializing the "allows assertions" flag */
-    private static Boolean                     c_allowsAssertions  = null;
+    protected static final Logger              log                 = Logger.getLogger( AuthenticationManager.class );
 
-    /** Static Boolean for lazily-initializing the "allows cookie authentication" flag */
-    private static Boolean                     c_allowsAuthentication = null;
+    /** Prefix for LoginModule options key/value pairs. */
+    protected static final String                 PREFIX_LOGIN_MODULE_OPTIONS = "jspwiki.loginModule.options.";
 
-    private WikiEngine                         m_engine            = null;
+    /** 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";
 
-    /** If true, logs the IP address of the editor */
-    private boolean                            m_storeIPAddress    = true;
-
-    /** Value specifying that the user wants to use the container-managed security, just like
-     *  in JSPWiki 2.2.
-     */
-    public static final String                SECURITY_OFF      = "off";
+    /** 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>();
 
     /** Just to provide compatibility with the old versions.  The same
      *  as SECURITY_OFF.
      *
-     *  @deprecated
+     *  @deprecated use {@link #SECURITY_OFF} instead
      */
     protected static final String             SECURITY_CONTAINER = "container";
 
-    /** Value specifying that the user wants to use the built-in JAAS-based system */
-    public static final String                SECURITY_JAAS     = "jaas";
+    /** The default {@link javax.security.auth.spi.LoginModule} class name to use for custom authentication. */
+    private static final String                 DEFAULT_LOGIN_MODULE = "com.ecyrd.jspwiki.auth.login.UserDatabaseLoginModule";
+    
+    /** Empty principal set. */
+    private static final Set<Principal> NO_PRINCIPALS = new HashSet<Principal>();
 
-    /**
-     *  This property determines whether we use JSPWiki authentication or not.
-     *  Possible values are AUTH_JAAS or AUTH_CONTAINER.
-     *
-     */
+    /** Static Boolean for lazily-initializing the "allows assertions" flag */
+    private boolean                     m_allowsCookieAssertions  = true;
 
-    public  static final String                PROP_SECURITY       = "jspwiki.security";
-    private static final String                PROP_JAAS_CONFIG    = "java.security.auth.login.config";
-    private static final String                DEFAULT_JAAS_CONFIG = "jspwiki.jaas";
+    /** Static Boolean for lazily-initializing the "allows cookie authentication" flag */
+    private boolean                     m_allowsCookieAuthentication = false;
 
-    private static       boolean               c_useJAAS = true;
+    private WikiEngine                         m_engine            = null;
+    
+    /** If true, logs the IP address of the editor */
+    private boolean                            m_storeIPAddress    = true;
+
+    private boolean               m_useJAAS = true;
 
     /**
      * Creates an AuthenticationManager instance for the given WikiEngine and
@@ -125,43 +136,39 @@
      * @param props the properties used to initialize the wiki engine
      * @throws WikiException if the AuthenticationManager cannot be initialized
      */
+    @SuppressWarnings("unchecked")
     public final void initialize( WikiEngine engine, Properties props ) throws WikiException
     {
         m_engine = engine;
         m_storeIPAddress = TextUtil.getBooleanProperty( props, PROP_STOREIPADDRESS, m_storeIPAddress );
-        m_isJaasConfiguredAtStartup = PolicyLoader.isJaasConfigured();
-
-        // Yes, writing to a static field is done here on purpose.
-        c_useJAAS = SECURITY_JAAS.equals(props.getProperty( PROP_SECURITY, SECURITY_JAAS ));
 
-        if( !c_useJAAS ) return;
-
-        //
-        //  The rest is JAAS implementation
-        //
-
-        log.info( "Checking JAAS configuration..." );
-
-        if (! m_isJaasConfiguredAtStartup )
+        // Should J2SE policies be used for authorization?
+        m_useJAAS = SECURITY_JAAS.equals(props.getProperty( PROP_SECURITY, SECURITY_JAAS ));
+        
+        // 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 );
+        
+        // Look up the LoginModule class
+        String loginModuleClassName = TextUtil.getStringProperty( props, PROP_LOGIN_MODULE, DEFAULT_LOGIN_MODULE );
+        try
         {
-            URL config = findConfigFile( engine, DEFAULT_JAAS_CONFIG );
-            log.info("JAAS not configured. Installing default configuration: " + config
-                + ". You can set the "+PROP_JAAS_CONFIG+" system property to point to your "
-                + "jspwiki.jaas file, or add the entries from jspwiki.jaas to your own "
-                + "JAAS configuration file.");
-            try
-            {
-                PolicyLoader.setJaasConfiguration( config );
-            }
-            catch ( SecurityException e)
-            {
-                log.error("Could not configure JAAS: " + e.getMessage());
-            }
+            m_loginModuleClass = (Class<? extends LoginModule>) Class.forName( loginModuleClassName );
         }
-        else
+        catch (ClassNotFoundException e)
         {
-            log.info("JAAS already configured by some other application (leaving it alone...)");
+            e.printStackTrace();
+            throw new WikiException(e.getMessage());
         }
+        
+        // Initialize the LoginModule options
+        initLoginModuleOptions( props );
     }
 
     /**
@@ -175,7 +182,7 @@
      */
     public final boolean isContainerAuthenticated()
     {
-        if( !c_useJAAS ) return true;
+        if( !m_useJAAS ) return true;
 
         try
         {
@@ -194,58 +201,129 @@
 
     /**
      * <p>Logs in the user by attempting to populate a WikiSession Subject from
-     * a web servlet request. This method leverages container-managed authentication.
-     * This method logs in the user if the user's status is "unknown" to the
-     * WikiSession, or if the Http servlet container's authentication status has
-     * changed. This method assumes that the HttpServletRequest is not null; otherwise,
-     * an IllegalStateException is thrown. This method is a <em>privileged</em> action;
-     * the caller must posess the (name here) permission.</p>
-     * <p>If <code>request</code> is <code>null</code>, or the WikiSession
-     * cannot be located for this request, this method throws an {@link IllegalStateException}.</p>
-     *             methods return null
+     * 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 com.ecyrd.jspwiki.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 com.ecyrd.jspwiki.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 com.ecyrd.jspwiki.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 com.ecyrd.jspwiki.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 the result of the login operation: <code>true</code> if the user logged in
-     * successfully; <code>false</code> otherwise
-     * @throws com.ecyrd.jspwiki.auth.WikiSecurityException if the Authorizer or UserManager cannot be obtained
+     * @return always returns <code>true</code> (because anonymous login, at least, will always succeed)
+     * @throws com.ecyrd.jspwiki.auth.WikiSecurityException if the user cannot be logged in for any reason
      * @since 2.3
      */
     public final boolean login( HttpServletRequest request ) throws WikiSecurityException
     {
-        if ( request == null )
+        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() )
         {
-            throw new IllegalStateException( "Wiki context's HttpRequest may not be null" );
+            // Create a callback handler
+            try
+            {
+                handler = new WebContainerCallbackHandler( m_engine, request, authorizationMgr.getAuthorizer() );
+            }
+            catch ( WikiSecurityException e )
+            {
+                e.printStackTrace();
+                throw new WikiSecurityException( e.getMessage() );
+             }
+            if ( handler == null )
+            {
+                throw new WikiSecurityException("Callback handler returned null for some reason... this is very unusual.");
+            }
+
+            // 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 );
+                }
+            }
         }
 
-        WikiSession wikiSession = WikiSession.getWikiSession( m_engine, request );
-        if ( wikiSession == null )
+        // If user still not authenticated, check if assertion cookie was supplied
+        if ( !session.isAuthenticated() && authenticationMgr.allowsCookieAssertions() )
         {
-            throw new IllegalStateException( "Wiki context's WikiSession may not be null" );
+            // 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 using JAAS, try to log in; otherwise logins "always" succeed
-        boolean login = true;
-        if( c_useJAAS )
+        // If user still anonymous, use the remote address
+        if (session.isAnonymous() )
         {
-            AuthorizationManager authMgr = m_engine.getAuthorizationManager();
-            CallbackHandler handler = new WebContainerCallbackHandler(
-                    m_engine,
-                    request,
-                    authMgr.getAuthorizer() );
-            login = doLogin( wikiSession, handler, LOGIN_CONTAINER );
+            Set<Principal> principals = authenticationMgr.doJAASLogin( AnonymousLoginModule.class, handler, options );
+            if ( principals.size() > 0 )
+            {
+                fireEvent( WikiSecurityEvent.LOGIN_ANONYMOUS, getLoginPrincipal( principals ), session );
+                return true;
+            }
         }
-        return login;
+        
+        // If by some unusual turn of events the Anonymous login module doesn't work, login failed!
+        return false;
     }
-
+    
     /**
      * Attempts to perform a WikiSession login for the given username/password
-     * combination. This is custom authentication.
+     * 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 com.ecyrd.jspwiki.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 null.
      * @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
+     * @param password the password
      * @return true, if the username/password is valid
      * @throws com.ecyrd.jspwiki.auth.WikiSecurityException if the Authorizer or UserManager cannot be obtained
+     * @throws ServletException if the specified LoginModule cannot be executed for any reason other than authentication failures
      */
     public final boolean login( WikiSession session, String username, String password ) throws WikiSecurityException
     {
@@ -260,7 +338,19 @@
                 userMgr.getUserDatabase(),
                 username,
                 password );
-        return doLogin( session, handler, LOGIN_CUSTOM );
+        
+        // Execute the user's specified login module
+        Set<Principal> principals = doJAASLogin( UserDatabaseLoginModule.class, handler, m_loginModuleOptions );
+        if (principals.size() > 0)
+        {
+            fireEvent(WikiSecurityEvent.LOGIN_AUTHENTICATED, getLoginPrincipal( principals ), session );
+            for ( Principal principal : principals )
+            {
+                fireEvent( WikiSecurityEvent.PRINCIPAL_ADD, principal, session );
+            }
+            return true;
+        }
+        return false;
     }
 
     /**
@@ -307,92 +397,26 @@
     /**
      * Determines whether this WikiEngine allows users to assert identities using
      * cookies instead of passwords. This is determined by inspecting
-     * the LoginConfiguration for application <code>JSPWiki-container</code>.
+     * the WikiEngine property {@link #PROP_ALLOW_COOKIE_ASSERTIONS}.
      * @return <code>true</code> if cookies are allowed
      */
-    public static final boolean allowsCookieAssertions()
+    public final boolean allowsCookieAssertions()
     {
-        if( !c_useJAAS ) return true;
-
-        // Lazily initialize
-        if( c_allowsAssertions == null )
-        {
-            c_allowsAssertions = Boolean.FALSE;
-
-            // Figure out whether cookie assertions are allowed
-            Configuration loginConfig = (Configuration)AccessController.doPrivileged(new PrivilegedAction()
-              {
-                  public Object run()
-                  {
-                      return Configuration.getConfiguration();
-                  }
-              });
-
-            if (loginConfig != null)
-            {
-                AppConfigurationEntry[] configs = loginConfig.getAppConfigurationEntry( LOGIN_CONTAINER );
-                if( configs != null )
-                {
-                    for ( int i = 0; i < configs.length; i++ )
-                    {
-                        AppConfigurationEntry config = configs[i];
-                        if ( COOKIE_MODULE.equals( config.getLoginModuleName() ) )
-                        {
-                            c_allowsAssertions = Boolean.TRUE;
-                        }
-                    }
-                }
-            }
-        }
-
-        return c_allowsAssertions.booleanValue();
+        return m_allowsCookieAssertions;
     }
 
     /**
      *  Determines whether this WikiEngine allows users to authenticate using
      *  cookies instead of passwords. This is determined by inspecting
-     *  the LoginConfiguration for application <code>JSPWiki-container</code>.
+     * the WikiEngine property {@link #PROP_ALLOW_COOKIE_AUTH}.
      *  @return <code>true</code> if cookies are allowed for authentication
      *  @since 2.5.62
      */
-    public static final boolean allowsCookieAuthentication()
+    public final boolean allowsCookieAuthentication()
     {
-        if( !c_useJAAS ) return true;
-
-        // Lazily initialize
-        if( c_allowsAuthentication == null )
-        {
-            c_allowsAuthentication = Boolean.FALSE;
-
-            // Figure out whether cookie assertions are allowed
-            Configuration loginConfig = (Configuration)AccessController.doPrivileged(new PrivilegedAction()
-              {
-                  public Object run()
-                  {
-                      return Configuration.getConfiguration();
-                  }
-              });
-
-            if (loginConfig != null)
-            {
-                AppConfigurationEntry[] configs = loginConfig.getAppConfigurationEntry( LOGIN_CONTAINER );
-
-                if( configs != null )
-                {
-                    for ( int i = 0; i < configs.length; i++ )
-                    {
-                        AppConfigurationEntry config = configs[i];
-                        if ( COOKIE_AUTHENTICATION_MODULE.equals( config.getLoginModuleName() ) )
-                        {
-                            c_allowsAuthentication = Boolean.TRUE;
-                        }
-                    }
-                }
-            }
-        }
-
-        return c_allowsAuthentication.booleanValue();
+        return m_allowsCookieAuthentication;
     }
+    
     /**
      * Determines whether the supplied Principal is a "role principal".
      * @param principal the principal to test
@@ -420,99 +444,67 @@
     }
 
     /**
-     * Log in to the application using a given JAAS LoginConfiguration. Any
-     * configuration error
-     * @param wikiSession the current wiki session, to which the Subject will be associated
-     * @param handler handles callbacks sent by the LoginModules in the configuration
-     * @param application the name of the application whose LoginConfiguration should be used
-     * @return the result of the login
-     * @throws WikiSecurityException
+     * 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 ServletException
+     *             if the LoginModule could not be instantiated for any reason
+     *             (the root cause exception is wrapped)
      */
-    private final boolean doLogin( final WikiSession wikiSession, final CallbackHandler handler, final String application ) throws WikiSecurityException
+    protected Set<Principal> doJAASLogin(Class<? extends LoginModule> clazz, CallbackHandler handler, Map<String,String> options) throws WikiSecurityException
     {
+        // Instantiate the login module
+        LoginModule loginModule = null;
         try
         {
-            LoginContext loginContext  = (LoginContext)AccessController.doPrivileged(new PrivilegedAction()
-            {
-                public Object run()
-                {
-                    try
-                    {
-                        return wikiSession.getLoginContext( application, handler );
-                    }
-                    catch( LoginException e )
-                    {
-                        log.error( "Couldn't retrieve login configuration.\nMessage="
-                                   + e.getLocalizedMessage() );
-                        return null;
-                    }
-                }
-            });
-
-            if( loginContext != null )
-            {
-                loginContext.login();
-                fireEvent( WikiSecurityEvent.LOGIN_INITIATED, null, wikiSession );
-            }
-            else
-            {
-                log.error("No login context.  Please double-check that JSPWiki found your 'jspwiki.jaas' file or the contents have been appended to your regular JAAS file.");
-                return false;
-            }
-
-            // Fire event for the correct authentication event
-            if ( wikiSession.isAnonymous() )
-            {
-                fireEvent( WikiSecurityEvent.LOGIN_ANONYMOUS, wikiSession.getLoginPrincipal(), wikiSession );
-            }
-            else if ( wikiSession.isAsserted() )
-            {
-                fireEvent( WikiSecurityEvent.LOGIN_ASSERTED, wikiSession.getLoginPrincipal(), wikiSession );
-            }
-            else if ( wikiSession.isAuthenticated() )
-            {
-                fireEvent( WikiSecurityEvent.LOGIN_AUTHENTICATED, wikiSession.getLoginPrincipal(), wikiSession );
-            }
-
-            return true;
+            loginModule = clazz.newInstance();
         }
-        catch( FailedLoginException e )
+        catch (InstantiationException e)
         {
-            //
-            //  Just a mistyped password or a cracking attempt.  No need to worry
-            //  and alert the admin
-            //
-            log.info("Failed login: "+e.getLocalizedMessage());
-            fireEvent( WikiSecurityEvent.LOGIN_FAILED, wikiSession.getLoginPrincipal(), wikiSession );
-            return false;
+            throw new WikiSecurityException(e.getMessage());
         }
-        catch( AccountExpiredException e )
+        catch (IllegalAccessException e)
         {
-            log.info("Expired account: "+e.getLocalizedMessage());
-            fireEvent( WikiSecurityEvent.LOGIN_ACCOUNT_EXPIRED, wikiSession.getLoginPrincipal(), wikiSession );
-            return false;
+            throw new WikiSecurityException(e.getMessage());
         }
-        catch( CredentialExpiredException 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
         {
-            log.info("Credentials expired: "+e.getLocalizedMessage());
-            fireEvent( WikiSecurityEvent.LOGIN_CREDENTIAL_EXPIRED, wikiSession.getLoginPrincipal(), wikiSession );
-            return false;
+            loginSucceeded = loginModule.login();
+            if (loginSucceeded)
+            {
+                commitSucceeded = loginModule.commit();
+            }
         }
-        catch( LoginException e )
+        catch (LoginException e)
         {
-            //
-            //  This should only be caught if something unforeseen happens,
-            //  so therefore we can log it as an error.
-            //
-            log.error( "Couldn't log in.\nMessage="
-                       + e.getLocalizedMessage() );
-            return false;
+            // Login or commit failed! No principal for you!
         }
-        catch( SecurityException e )
+
+        // If we successfully logged in & committed, return all the principals
+        if (loginSucceeded && commitSucceeded)
         {
-            log.error( "Could not log in.  Please check that your jaas.config file is found.", e );
-            return false;
+            return subject.getPrincipals();
         }
+        return NO_PRINCIPALS;
     }
 
     /**
@@ -571,6 +563,23 @@
         return path;
     }
 
+    /**
+     * Returns the first Principal in a set that isn't a {@link com.ecyrd.jspwiki.auth.authorize.Role} or
+     * {@link com.ecyrd.jspwiki.auth.GroupPrincipal}.
+     * @param principals the principal set
+     * @return the login principal
+     */
+    protected Principal getLoginPrincipal(Set<Principal> principals)
+    {
+        for (Principal principal: principals )
+        {
+            if ( isUserPrincipal( principal ) )
+            {
+                return principal;
+            }
+        }
+        return null;
+    }
 
     // events processing .......................................................
 
@@ -608,6 +617,39 @@
         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 by
+     * {@link #doLoginModule(Class, CallbackHandler)}. 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 );
+                }
+            }
         }
     }
 

Modified: incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/SecurityVerifier.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/SecurityVerifier.java?rev=643255&r1=643254&r2=643255&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/SecurityVerifier.java (original)
+++ incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/SecurityVerifier.java Mon Mar 31 20:44:22 2008
@@ -1,21 +1,22 @@
 /*
- JSPWiki - a JSP-based WikiWiki clone.
+    JSPWiki - a JSP-based WikiWiki clone.
 
- Copyright (C) 2001-2005 Janne Jalkanen (Janne.Jalkanen@iki.fi)
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU Lesser General Public License as published by
- the Free Software Foundation; either version 2.1 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- GNU Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+    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 com.ecyrd.jspwiki.auth;
 
@@ -30,7 +31,6 @@
 import java.util.Set;
 
 import javax.security.auth.Subject;
-import javax.security.auth.login.LoginContext;
 
 import org.apache.commons.lang.ArrayUtils;
 import org.apache.log4j.Logger;
@@ -62,10 +62,6 @@
 
     private WikiEngine            m_engine;
 
-    private File                  m_jaasConfig                 = null;
-
-    private boolean               m_isJaasConfigured           = false;
-
     private boolean               m_isSecurityPolicyConfigured = false;
 
     private Principal[]           m_policyPrincipals           = new Principal[0];
@@ -439,50 +435,6 @@
     }
 
     /**
-     * Returns <code>true</code> if JAAS is configured correctly.
-     * @return the result of the configuration check
-     */
-    public final boolean isJaasConfigured()
-    {
-        return m_isJaasConfigured;
-    }
-
-    /**
-     * Returns <code>true</code> if the JAAS login configuration was already
-     * set when JSPWiki started up. We determine this value by consulting a
-     * protected member field of {@link AuthenticationManager}, which was set
-     * at in initialization by {@link PolicyLoader}.
-     * @return <code>true</code> if {@link PolicyLoader} successfully set the
-     *         policy, or <code>false</code> for any other reason.
-     */
-    public final boolean isJaasConfiguredAtStartup()
-    {
-        return m_engine.getAuthenticationManager().m_isJaasConfiguredAtStartup;
-    }
-
-    /**
-     * Returns <code>true</code> if JSPWiki can locate a named JAAS login
-     * configuration.
-     * @param config the name of the application (e.g.,
-     *            <code>JSPWiki-container</code>).
-     * @return <code>true</code> if found; <code>false</code> otherwise
-     */
-    protected final boolean isJaasConfigurationAvailable( String config )
-    {
-        try
-        {
-            m_session.addMessage( INFO_JAAS, "We found the '" + config + "' login configuration." );
-            new LoginContext( config );
-            return true;
-        }
-        catch( Exception e )
-        {
-            m_session.addMessage( ERROR_JAAS, "We could not find the '" + config + "' login configuration.</p>" );
-            return false;
-        }
-    }
-
-    /**
      * Returns <code>true</code> if the Java security policy is configured
      * correctly, and it verifies as valid.
      * @return the result of the configuration check
@@ -657,17 +609,6 @@
                     "be reliable as a result. You should set this to 'jaas' " +
                     "so that security works properly." );
         }
-
-        // Validate the property is set correctly
-        m_jaasConfig = getFileFromProperty( "java.security.auth.login.config" );
-
-        // Look for the JSPWiki-container config
-        boolean foundJaasContainerConfig = isJaasConfigurationAvailable( "JSPWiki-container" );
-
-        // Look for the JSPWiki-custom config
-        boolean foundJaasCustomConfig = isJaasConfigurationAvailable( "JSPWiki-custom" );
-
-        m_isJaasConfigured = m_jaasConfig != null && foundJaasContainerConfig && foundJaasCustomConfig;
     }
 
     /**
@@ -935,17 +876,5 @@
         }
 
         m_session.addMessage( INFO_DB, "The user database configuration looks fine." );
-    }
-
-    /**
-     * Returns the location of the JAAS configuration file if and only if the
-     * <code>java.security.auth.login.config</code> is set <em>and</em> the
-     * file it points to exists in the file system; returns <code>null</code>
-     * in all other cases.
-     * @return the location of the JAAS configuration file
-     */
-    public final File jaasConfiguration()
-    {
-        return m_jaasConfig;
     }
 }