incubator-jspwiki-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ajaqu...@apache.org
Subject svn commit: r682143 [1/2] - in /incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki: attachment/ auth/ auth/acl/
Date Sun, 03 Aug 2008 12:15:58 GMT
Author: ajaquith
Date: Sun Aug  3 05:15:57 2008
New Revision: 682143

URL: http://svn.apache.org/viewvc?rev=682143&view=rev
Log:
Massive re-factoring to make Stripes less invasive.

Added:
    incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/acl/package.html
    incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/package.html
Removed:
    incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/PolicyLoader.java
Modified:
    incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/attachment/AttachmentManager.java
    incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/attachment/AttachmentServlet.java
    incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/AuthenticationManager.java
    incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/AuthorizationManager.java
    incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/Authorizer.java
    incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/GroupPrincipal.java
    incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/NoSuchPrincipalException.java
    incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/PrincipalComparator.java
    incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/SecurityVerifier.java
    incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/SessionMonitor.java
    incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/UserManager.java
    incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/WikiPrincipal.java
    incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/WikiSecurityException.java
    incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/acl/Acl.java
    incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/acl/AclEntry.java
    incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/acl/AclEntryImpl.java
    incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/acl/AclImpl.java
    incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/acl/AclManager.java
    incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/acl/DefaultAclManager.java
    incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/acl/UnresolvedPrincipal.java

Modified: incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/attachment/AttachmentManager.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/attachment/AttachmentManager.java?rev=682143&r1=682142&r2=682143&view=diff
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/attachment/AttachmentManager.java (original)
+++ incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/attachment/AttachmentManager.java Sun Aug  3 05:15:57 2008
@@ -26,6 +26,7 @@
 import java.io.InputStream;
 import java.util.*;
 
+import org.apache.commons.lang.StringUtils;
 import org.apache.log4j.Logger;
 
 import com.ecyrd.jspwiki.*;
@@ -121,8 +122,8 @@
         //
         try
         {
-            Class providerclass = ClassUtil.findClass( "com.ecyrd.jspwiki.providers",
-                                                       classname );
+            Class<?> providerclass = ClassUtil.findClass( "com.ecyrd.jspwiki.providers",
+                                                          classname );
 
             m_provider = (WikiAttachmentProvider)providerclass.newInstance();
 
@@ -307,22 +308,23 @@
      */
 
     // FIXME: This API should be changed to return a List.
-    public Collection<Attachment> listAttachments( WikiPage wikipage )
+    @SuppressWarnings("unchecked")
+    public Collection listAttachments( WikiPage wikipage )
         throws ProviderException
     {
         if( m_provider == null )
         {
-            return new ArrayList<Attachment>();
+            return new ArrayList();
         }
 
-        Collection<Attachment> atts = m_provider.listAttachments( wikipage );
+        Collection atts = m_provider.listAttachments( wikipage );
 
         //
         //  This is just a sanity check; all of our providers return a Collection.
         //
         if( atts instanceof List )
         {
-            Collections.sort( (List<Attachment>)atts );
+            Collections.sort( (List) atts );
         }
 
         return atts;
@@ -534,7 +536,7 @@
      *          return an empty collection.
      *  @throws ProviderException If something went wrong with the backend
      */
-    public Collection<Attachment> getAllAttachments()
+    public Collection getAllAttachments()
         throws ProviderException
     {
         if( attachmentsEnabled() )
@@ -587,4 +589,53 @@
         m_engine.getReferenceManager().clearPageEntries( att.getName() );
 
     }
+
+    /**
+     *  Validates the filename and makes sure it is legal.  It trims and splits
+     *  and replaces bad characters.
+     *  
+     *  @param filename
+     *  @return A validated name with annoying characters replaced.
+     *  @throws WikiException If the filename is not legal (e.g. empty)
+     */
+    static String validateFileName( String filename )
+        throws WikiException
+    {
+        if( filename == null || filename.trim().length() == 0 )
+        {
+            AttachmentServlet.log.error("Empty file name given.");
+    
+            throw new WikiException("Empty file name given.");
+        }
+    
+        //
+        //  Should help with IE 5.22 on OSX
+        //
+        filename = filename.trim();
+
+        // If file name ends with .jsp, the user is being naughty!
+        if ( filename.endsWith( ".jsp" ) || filename.endsWith( ".JSP" ) )
+        {
+            AttachmentServlet.log.error( "Illegal file name." );
+            
+            throw new WikiException( "Illegal file name." );
+        }
+    
+        //
+        //  Some browser send the full path info with the filename, so we need
+        //  to remove it here by simply splitting along slashes and then taking the path.
+        //
+        
+        String[] splitpath = filename.split( "[/\\\\]" );
+        filename = splitpath[splitpath.length-1];
+        
+        //
+        //  Remove any characters that might be a problem. Most
+        //  importantly - characters that might stop processing
+        //  of the URL.
+        //
+        filename = StringUtils.replaceChars( filename, "#?\"'", "____" );
+    
+        return filename;
+    }
 }

Modified: incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/attachment/AttachmentServlet.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/attachment/AttachmentServlet.java?rev=682143&r1=682142&r2=682143&view=diff
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/attachment/AttachmentServlet.java (original)
+++ incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/attachment/AttachmentServlet.java Sun Aug  3 05:15:57 2008
@@ -1,21 +1,22 @@
 /*
     JSPWiki - a JSP-based WikiWiki clone.
 
-    Copyright (C) 2001-2002 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.attachment;
 
@@ -26,7 +27,7 @@
 import java.net.SocketException;
 import java.security.Permission;
 import java.security.Principal;
-import java.util.Enumeration;
+import java.util.List;
 import java.util.Properties;
 
 import javax.servlet.ServletConfig;
@@ -35,16 +36,15 @@
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import net.iamvegan.multipartrequest.HttpServletMultipartRequest;
-import net.iamvegan.multipartrequest.MultipartFile;
-import net.iamvegan.multipartrequest.ProgressListener;
-
-import org.apache.commons.lang.StringUtils;
+import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.fileupload.FileItemFactory;
+import org.apache.commons.fileupload.FileUploadException;
+import org.apache.commons.fileupload.ProgressListener;
+import org.apache.commons.fileupload.disk.DiskFileItemFactory;
+import org.apache.commons.fileupload.servlet.ServletFileUpload;
 import org.apache.log4j.Logger;
 
 import com.ecyrd.jspwiki.*;
-import com.ecyrd.jspwiki.action.AttachActionBean;
-import com.ecyrd.jspwiki.action.UploadActionBean;
 import com.ecyrd.jspwiki.auth.AuthorizationManager;
 import com.ecyrd.jspwiki.auth.permissions.PermissionFactory;
 import com.ecyrd.jspwiki.dav.AttachmentDavProvider;
@@ -69,7 +69,6 @@
  *  This servlet is also capable of managing dynamically created attachments.
  *
  *  @author Erik Bunn
- *  @author Janne Jalkanen
  *
  *  @since 1.9.45.
  */
@@ -229,15 +228,7 @@
     public void doGet( HttpServletRequest  req, HttpServletResponse res )
         throws IOException, ServletException
     {
-        WikiContext context;
-        try 
-        {
-            context = (WikiContext)m_engine.getWikiActionBeanFactory().newActionBean( req, res, AttachActionBean.class );
-        }
-        catch ( WikiException e )
-        {
-            throw new ServletException( e.getMessage() );
-        }
+        WikiContext context = m_engine.createContext( req, WikiContext.ATTACH );
 
         String version  = req.getParameter( HDR_VERSION );
         String nextPage = req.getParameter( "nextpage" );
@@ -478,7 +469,7 @@
     {
         try
         {
-            String nextPage = upload( req, res );
+            String nextPage = upload( req );
             req.getSession().removeAttribute("msg");
             res.sendRedirect( nextPage );
         }
@@ -498,6 +489,8 @@
     public void doPut( HttpServletRequest req, HttpServletResponse res )
         throws IOException, ServletException
     {
+        String errorPage = m_engine.getURL( WikiContext.ERROR, "", null, false ); // If something bad happened, Upload should be able to take care of most stuff
+
         String p = new String(req.getPathInfo().getBytes("ISO-8859-1"), "UTF-8");
         DavPath path = new DavPath( p );
 
@@ -505,21 +498,11 @@
         {
             InputStream data = req.getInputStream();
 
-            WikiContext context;
-            String errorPage; // If something bad happened, Upload should be able to take care of most stuff
-
-            try 
-            {
-                context = (WikiContext)m_engine.getWikiActionBeanFactory().newActionBean( req, res, UploadActionBean .class );
-            }
-            catch ( WikiException e )
-            {
-                throw new ServletException( e.getMessage() );
-            }
+            WikiContext context = (WikiContext)m_engine.createContext( req, WikiContext.UPLOAD );
 
             String wikipage = path.get( 0 );
 
-            errorPage = context.getContext().getURL( UploadActionBean.class,
+            errorPage = context.getURL( WikiContext.UPLOAD,
                                         wikipage );
 
             String changeNote = null; // FIXME: Does not quite work
@@ -576,77 +559,104 @@
      *  @return The page to which we should go next.
      *  @throws RedirectException If there's an error and a redirection is needed
      *  @throws IOException If upload fails
+     * @throws FileUploadException 
      */
-    protected String upload( HttpServletRequest req, HttpServletResponse res )
+    @SuppressWarnings("unchecked")
+    protected String upload( HttpServletRequest req )
         throws RedirectException,
                IOException
     {
         String msg     = "";
         String attName = "(unknown)";
-        String nextPage;
+        String errorPage = m_engine.getURL( WikiContext.ERROR, "", null, false ); // If something bad happened, Upload should be able to take care of most stuff
+        String nextPage = errorPage;
 
         String progressId = req.getParameter( "progressid" );
 
+        // Check that we have a file upload request
+        if( !ServletFileUpload.isMultipartContent(req) )
+        {
+            throw new RedirectException( "Not a file upload", errorPage );
+        }
+        
         try
         {
-            HttpServletMultipartRequest multi;
-
+            FileItemFactory factory = new DiskFileItemFactory();
+            
             // Create the context _before_ Multipart operations, otherwise
             // strict servlet containers may fail when setting encoding.
-            WikiContext context;
-            try 
-            {
-                context = (WikiContext)m_engine.getWikiActionBeanFactory().newActionBean( req, res, AttachActionBean.class );
-            }
-            catch ( WikiException e )
-            {
-                throw new IOException( e.getMessage() );
-            }
+            WikiContext context = (WikiContext)m_engine.createContext( req, WikiContext.ATTACH );
 
             UploadListener pl = new UploadListener();
 
             m_engine.getProgressManager().startProgress( pl, progressId );
 
-            multi = new HttpServletMultipartRequest( req,
-                                                     Long.MAX_VALUE,
-                                                     HttpServletMultipartRequest.SAVE_TO_TMPDIR,
-                                                     HttpServletMultipartRequest.ABORT_ON_MAX_LENGTH,
-                                                     "UTF-8",
-                                                     pl );
-
-            String errorPage = context.getContext().getURL( UploadActionBean.class, context.getPage().getName() );
-            nextPage        = validateNextPage( multi.getParameter( "nextpage" ), errorPage );
-            String wikipage = multi.getParameter( "page" );
-            String changeNote = multi.getParameter( "changenote" );
-
-            //
-            // FIXME: Kludge alert.  We must end up with the parent page name,
-            //        if this is an upload of a new revision
-            //
-
-            int x = wikipage.indexOf("/");
-
-            if( x != -1 ) wikipage = wikipage.substring(0,x);
+            ServletFileUpload upload = new ServletFileUpload(factory);
+            upload.setHeaderEncoding("UTF-8");
+            upload.setFileSizeMax( m_maxSize );
+            upload.setProgressListener( pl );
+            List<FileItem> items = upload.parseRequest( req );
+            
+            String   wikipage   = null;
+            String   changeNote = null;
+            FileItem actualFile = null;
+            
+            for( FileItem item : items )
+            {
+                if( item.isFormField() )
+                {
+                    if( item.getFieldName().equals("page") )
+                    {
+                        //
+                        // FIXME: Kludge alert.  We must end up with the parent page name,
+                        //        if this is an upload of a new revision
+                        //
+
+                        wikipage = item.getString("UTF-8");
+                        int x = wikipage.indexOf("/");
+
+                        if( x != -1 ) wikipage = wikipage.substring(0,x);
+                    }
+                    else if( item.getFieldName().equals("changenote") )
+                    {
+                        changeNote = item.getString("UTF-8");
+                    }
+                    else if( item.getFieldName().equals( "nextpage" ) )
+                    {
+                        nextPage = validateNextPage( item.getString("UTF-8"), errorPage );
+                    }
+                }
+                else
+                {
+                    actualFile = item;
+                }
+            }
 
+            if( actualFile == null )
+                throw new RedirectException( "Broken file upload", errorPage );
+            
             //
-            //  Go through all files being uploaded.
-            //
-            Enumeration files = multi.getFileParameterNames();
-            long fileSize = 0L;
-            while( files.hasMoreElements() )
-            {
-                String part = (String) files.nextElement();
-                MultipartFile multiFile = multi.getFileParameter(part);
-                fileSize += multiFile.getSize();
-                InputStream in = multiFile.getInputStream();
-
-                String filename = multiFile.getName();
-
+            // FIXME: Unfortunately, with Apache fileupload we will get the form fields in
+            //        order.  This means that we have to gather all the metadata from the
+            //        request prior to actually touching the uploaded file itself.  This
+            //        is because the changenote appears after the file upload box, and we
+            //        would not have this information when uploading.  This also means
+            //        that with current structure we can only support a single file upload
+            //        at a time.
+            //
+            String filename = actualFile.getName();
+            long   fileSize = actualFile.getSize();
+            InputStream in  = actualFile.getInputStream();
+            
+            try
+            {
                 executeUpload( context, in, filename, nextPage, wikipage, changeNote, fileSize );
             }
+            finally
+            {
+                in.close();
+            }
 
-            // Inform the JSP page of which file we are handling:
-            // req.setAttribute( ATTR_ATTACHMENT, wikiname );
         }
         catch( ProviderException e )
         {
@@ -664,6 +674,15 @@
 
             throw e;
         }
+        catch (FileUploadException e)
+        {
+            // Show the submit page again, but with a bit more
+            // intimidating output.
+            msg = "Upload failure: " + e.getMessage();
+            log.warn( msg + " (attachment: " + attName + ")", e );
+
+            throw new IOException( msg );
+        }
         finally
         {
             m_engine.getProgressManager().stopProgress( progressId );
@@ -674,7 +693,6 @@
         return nextPage;
     }
 
-
     /**
      *
      * @param context the wiki context
@@ -699,6 +717,16 @@
     {
         boolean created = false;
 
+        try
+        {
+            filename = AttachmentManager.validateFileName( filename );
+        }
+        catch( WikiException e )
+        {
+            log.error( "Illegal filename given: "+e.getMessage() );
+            throw new RedirectException( e.getMessage(), errorPage );
+        }
+        
         //
         //  FIXME: This has the unfortunate side effect that it will receive the
         //  contents.  But we can't figure out the page to redirect to
@@ -725,26 +753,6 @@
 
         AttachmentManager mgr = m_engine.getAttachmentManager();
 
-        if( filename == null || filename.trim().length() == 0 )
-        {
-            log.error("Empty file name given.");
-
-            throw new RedirectException("Empty file name given.",
-                                        errorPage);
-        }
-
-        //
-        //  Should help with IE 5.22 on OSX
-        //
-        filename = filename.trim();
-
-        //
-        //  Remove any characters that might be a problem. Most
-        //  importantly - characters that might stop processing
-        //  of the URL.
-        //
-        filename = StringUtils.replaceChars( filename, "#?\"'", "____" );
-
         log.debug("file="+filename);
 
         if( data == null )
@@ -812,7 +820,7 @@
      *  
      *  @author Janne Jalkanen
      */
-    private class UploadListener
+    private static class UploadListener
        extends    ProgressItem
        implements ProgressListener
     {

Modified: incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/AuthenticationManager.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/AuthenticationManager.java?rev=682143&r1=682142&r2=682143&view=diff
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/AuthenticationManager.java (original)
+++ incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/AuthenticationManager.java Sun Aug  3 05:15:57 2008
@@ -1,34 +1,35 @@
 /*
- * 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.http.HttpServletRequest;
 import javax.servlet.http.HttpSession;
 
@@ -40,83 +41,113 @@
 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;
+import com.ecyrd.jspwiki.util.TimedCounterList;
 
 /**
  * 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.
+ * 
  * @author Andrew Jaquith
- * @author Janne Jalkanen
  * @author Erik Bunn
  * @since 2.3
  */
 public final 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
 
+    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();
 
     /** 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.
+     *  <p>
+     *  Setting this is now deprecated - we do not guarantee that it works.
+     *  
+     *  @deprecated
+     */
+    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;
 
+    /** 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
@@ -125,43 +156,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 +202,7 @@
      */
     public final boolean isContainerAuthenticated()
     {
-        if( !c_useJAAS ) return true;
+        if( !m_useJAAS ) return true;
 
         try
         {
@@ -194,56 +221,122 @@
 
     /**
      * <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() );
+            }
+            
+            // 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
      */
@@ -255,12 +348,54 @@
             return false;
         }
 
+        delayLogin(username);
+        
         UserManager userMgr = m_engine.getUserManager();
         CallbackHandler handler = new WikiCallbackHandler(
                 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;
+    }
+    
+    /**
+     *  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
+        }
     }
 
     /**
@@ -307,92 +442,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 +489,66 @@
     }
 
     /**
-     * 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
+     * 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
      */
-    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 +607,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 .......................................................
 
@@ -610,5 +663,38 @@
             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/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/AuthorizationManager.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/AuthorizationManager.java?rev=682143&r1=682142&r2=682143&view=diff
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/AuthorizationManager.java (original)
+++ incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/AuthorizationManager.java Sun Aug  3 05:15:57 2008
@@ -1,21 +1,22 @@
-/*
- JSPWiki - a JSP-based WikiWiki clone.
+/* 
+    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
+    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;
 
@@ -102,7 +103,7 @@
     private Authorizer                        m_authorizer      = null;
 
     /** Cache for storing ProtectionDomains used to evaluate the local policy. */
-    private Map                               m_cachedPds       = new WeakHashMap();
+    private Map<Principal, ProtectionDomain>                               m_cachedPds       = new WeakHashMap<Principal, ProtectionDomain>();
 
     private WikiEngine                        m_engine          = null;
 
@@ -231,13 +232,11 @@
 
         log.debug( "Checking ACL entries..." );
         log.debug( "Acl for this page is: " + acl );
-        log.debug( "Checking for principal: " + String.valueOf(aclPrincipals) );
+        log.debug( "Checking for principal: " + String.valueOf( aclPrincipals ) );
         log.debug( "Permission: " + permission );
 
-        for( int i = 0; i < aclPrincipals.length; i++ )
+        for( Principal aclPrincipal : aclPrincipals )
         {
-            Principal aclPrincipal = aclPrincipals[i];
-
             // If the ACL principal we're looking at is unresolved,
             // try to resolve it here & correct the Acl
             if ( aclPrincipal instanceof UnresolvedPrincipal )
@@ -367,9 +366,8 @@
         {
             String principalName = principal.getName();
             Principal[] userPrincipals = session.getPrincipals();
-            for( int i = 0; i < userPrincipals.length; i++ )
+            for( Principal userPrincipal : userPrincipals )
             {
-                Principal userPrincipal = userPrincipals[i];
                 if( userPrincipal.getName().equals( principalName ) )
                 {
                     return true;
@@ -387,6 +385,7 @@
      * @param properties the set of properties used to initialize the wiki engine
      * @throws WikiException if the AuthorizationManager cannot be initialized
      */
+    @SuppressWarnings("deprecation")
     public final void initialize( WikiEngine engine, Properties properties ) throws WikiException
     {
         m_engine = engine;
@@ -411,17 +410,17 @@
                 File policyFile = new File( policyURL.getPath() );
                 m_localPolicy = new LocalPolicy( policyFile, engine.getContentEncoding() );
                 m_localPolicy.refresh();
-                log.info("Initialized default security policy: " + policyFile.getAbsolutePath());
+                log.info( "Initialized default security policy: " + policyFile.getAbsolutePath() );
             }
             else
             {
-                StringBuffer sb = new StringBuffer("JSPWiki was unable to initialize the ");
-                sb.append("default security policy (WEB-INF/jspwiki.policy) file. ");
-                sb.append("Please ensure that the jspwiki.policy file exists in the default location. ");
-                sb.append("This file should exist regardless of the existance of a global policy file. ");
-                sb.append("The global policy file is identified by the java.security.policy variable. ");
-                WikiSecurityException wse = new WikiSecurityException(sb.toString());
-                log.fatal(sb.toString(), wse);
+                StringBuffer sb = new StringBuffer( "JSPWiki was unable to initialize the " );
+                sb.append( "default security policy (WEB-INF/jspwiki.policy) file. " );
+                sb.append( "Please ensure that the jspwiki.policy file exists in the default location. " );
+                sb.append( "This file should exist regardless of the existance of a global policy file. " );
+                sb.append( "The global policy file is identified by the java.security.policy variable. " );
+                WikiSecurityException wse = new WikiSecurityException( sb.toString() );
+                log.fatal( sb.toString(), wse );
                 throw wse;
             }
         }
@@ -463,7 +462,7 @@
         {
             try
             {
-                Class authClass = ClassUtil.findClass( "com.ecyrd.jspwiki.auth.authorize", clazz );
+                Class<?> authClass = ClassUtil.findClass( "com.ecyrd.jspwiki.auth.authorize", clazz );
                 Object impl = authClass.newInstance();
                 return impl;
             }
@@ -498,16 +497,16 @@
      */
     protected boolean allowedByLocalPolicy( Principal[] principals, Permission permission )
     {
-        for ( int i = 0; i < principals.length; i++ )
+        for ( Principal principal : principals )
         {
             // Get ProtectionDomain for this Principal from cache, or create new one
-            ProtectionDomain pd = (ProtectionDomain)m_cachedPds.get( principals[i] );
+            ProtectionDomain pd = m_cachedPds.get( principal );
             if ( pd == null )
             {
                 ClassLoader cl = this.getClass().getClassLoader();
                 CodeSource cs = new CodeSource( null, (Certificate[])null );
-                pd = new ProtectionDomain( cs, null, cl, new Principal[]{ principals[i] } );
-                m_cachedPds.put( principals[i], pd );
+                pd = new ProtectionDomain( cs, null, cl, new Principal[]{ principal } );
+                m_cachedPds.put( principal, pd );
             }
 
             // Consult the local policy and get the answer
@@ -520,16 +519,16 @@
     }
 
     /**
-     * Determines whether a Subject posesses a given "static" Permission as
+     * Determines whether a Subject possesses a given "static" Permission as
      * defined in the security policy file. This method uses standard Java 2
      * security calls to do its work. Note that the current access control
      * context's <code>codeBase</code> is effectively <em>this class</em>,
      * not that of the caller. Therefore, this method will work best when what
      * matters in the policy is <em>who</em> makes the permission check, not
      * what the caller's code source is. Internally, this method works by
-     * excuting <code>Subject.doAsPrivileged</code> with a privileged action
+     * executing <code>Subject.doAsPrivileged</code> with a privileged action
      * that simply calls {@link java.security.AccessController#checkPermission(Permission)}.
-     * @link AccessController#checkPermission(java.security.Permission). A
+     * @see AccessController#checkPermission(java.security.Permission) . A
      *       caught exception (or lack thereof) determines whether the privilege
      *       is absent (or present).
      * @param session the WikiSession whose permission status is being queried
@@ -541,9 +540,9 @@
     {
         if( !m_useJAAS ) return true;
 
-        Boolean allowed = (Boolean)WikiSession.doPrivileged( session, new PrivilegedAction()
+        Boolean allowed = (Boolean) WikiSession.doPrivileged( session, new PrivilegedAction<Boolean>()
         {
-            public Object run()
+            public Boolean run()
             {
                 try
                 {

Modified: incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/Authorizer.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/Authorizer.java?rev=682143&r1=682142&r2=682143&view=diff
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/Authorizer.java (original)
+++ incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/Authorizer.java Sun Aug  3 05:15:57 2008
@@ -1,21 +1,22 @@
-/*
+/* 
     JSPWiki - a JSP-based WikiWiki clone.
 
-    Copyright (C) 2001-2007 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;
 

Modified: incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/GroupPrincipal.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/GroupPrincipal.java?rev=682143&r1=682142&r2=682143&view=diff
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/GroupPrincipal.java (original)
+++ incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/GroupPrincipal.java Sun Aug  3 05:15:57 2008
@@ -1,15 +1,22 @@
-/*
- * 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;
 

Modified: incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/NoSuchPrincipalException.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/NoSuchPrincipalException.java?rev=682143&r1=682142&r2=682143&view=diff
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/NoSuchPrincipalException.java (original)
+++ incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/NoSuchPrincipalException.java Sun Aug  3 05:15:57 2008
@@ -1,21 +1,22 @@
 /* 
     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
+    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;
 

Modified: incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/PrincipalComparator.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/PrincipalComparator.java?rev=682143&r1=682142&r2=682143&view=diff
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/PrincipalComparator.java (original)
+++ incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/PrincipalComparator.java Sun Aug  3 05:15:57 2008
@@ -1,21 +1,22 @@
-/*
-   JSPWiki - a JSP-based WikiWiki clone.
+/* 
+    JSPWiki - a JSP-based WikiWiki clone.
 
-   Copyright (C) 2001-2007 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,7 @@
  * @since 2.3
  */
 public class PrincipalComparator
-    implements Comparator<Principal>, Serializable 
+    implements Comparator<Principal>, Serializable
 {
     private static final long serialVersionUID = 1L;
 



Mime
View raw message