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 2009/08/20 05:30:26 UTC

svn commit: r806031 [1/2] - in /incubator/jspwiki/trunk: ./ doc/ etc/ src/java/org/apache/wiki/ src/java/org/apache/wiki/auth/ src/java/org/apache/wiki/auth/authorize/ src/java/org/apache/wiki/auth/login/ src/java/org/apache/wiki/auth/user/ tests/java/...

Author: ajaquith
Date: Thu Aug 20 03:30:25 2009
New Revision: 806031

URL: http://svn.apache.org/viewvc?rev=806031&view=rev
Log:
LDAP support for user databases, authentication and authorization has landed in the trunk. It has been tested with Active Directory and OpenLDAP, and is customizable for other LDAP servers. See jspwiki.properties for more details. Note that a user-accessible way of configuring the "Keychain" for storing bind DN passwords is still not available, but will be coming soon in a revamped installer screen. LdapLoginModule was removed, because it's actually much cleaner to use the UserDatabaseLoginModule instead.

Added:
    incubator/jspwiki/trunk/doc/JSPWIKI-551 Synergy
Removed:
    incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/login/LdapLoginModule.java
    incubator/jspwiki/trunk/tests/java/org/apache/wiki/auth/login/LdapLoginModuleTest.java
Modified:
    incubator/jspwiki/trunk/ChangeLog
    incubator/jspwiki/trunk/etc/jspwiki.properties.tmpl
    incubator/jspwiki/trunk/src/java/org/apache/wiki/Release.java
    incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/AuthenticationManager.java
    incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/Authorizer.java
    incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/LdapConfig.java
    incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/authorize/GroupManager.java
    incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/authorize/LdapAuthorizer.java
    incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/authorize/WebContainerAuthorizer.java
    incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/login/UserDatabaseLoginModule.java
    incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/user/LdapUserDatabase.java
    incubator/jspwiki/trunk/tests/java/org/apache/wiki/TestAuthorizer.java
    incubator/jspwiki/trunk/tests/java/org/apache/wiki/TestEngine.java
    incubator/jspwiki/trunk/tests/java/org/apache/wiki/auth/AuthenticationManagerTest.java
    incubator/jspwiki/trunk/tests/java/org/apache/wiki/auth/authorize/LdapAuthorizerTest.java
    incubator/jspwiki/trunk/tests/java/org/apache/wiki/auth/login/AllTests.java

Modified: incubator/jspwiki/trunk/ChangeLog
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/ChangeLog?rev=806031&r1=806030&r2=806031&view=diff
==============================================================================
--- incubator/jspwiki/trunk/ChangeLog (original)
+++ incubator/jspwiki/trunk/ChangeLog Thu Aug 20 03:30:25 2009
@@ -1,3 +1,16 @@
+2009-08-19 Andrew Jaquith <ajaquith AT apache DOT org>
+
+        * 3.0.0-svn-144
+
+        * LDAP support for user databases, authentication and authorization
+        has landed in the trunk. It has been tested with Active Directory
+        and OpenLDAP, and is customizable for other LDAP servers. See
+        jspwiki.properties for more details. Note that a user-accessible
+        way of configuring the "Keychain" for storing bind DN passwords
+        is still not available, but will be coming soon in a revamped
+        installer screen. LdapLoginModule was removed, because it's actually
+        much cleaner to use the UserDatabaseLoginModule instead.
+
 2009-08-19 Harry Metske <me...@apache.org>
 
         * 3.0.0-svn-143
@@ -114,8 +127,7 @@
         of the WikiEngine unit tests now run, though some of the old ones
         were removed.  Please see doc/README - JCR Changes.txt for details
         about the versioning implementation.
-                 
-
+        
 2009-06-07 Janne Jalkanen <ja...@apache.org>
 
         * 3.0.0-svn-132

Added: incubator/jspwiki/trunk/doc/JSPWIKI-551 Synergy
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/doc/JSPWIKI-551%20Synergy?rev=806031&view=auto
==============================================================================
--- incubator/jspwiki/trunk/doc/JSPWIKI-551 Synergy (added)
+++ incubator/jspwiki/trunk/doc/JSPWIKI-551 Synergy Thu Aug 20 03:30:25 2009
@@ -0,0 +1,24 @@
+JSPWiki relies on Apache code for many important functions, notably for file uploading (commons-fileupload), content indexing (Lucene), HTML element construction (ECS), HTTP client request and response processing (commons-httpclient), and XML request processing (XMLRPC).
+
+Apache is by far the single most prominent source of third-party JARs that JSPWiki uses. The complete list of Apache packages the JSPWiki runtime WAR includes is:
+
+Apache Log4J:                 log4j-1.2.14.jar
+Apache Lucene:                lucene.jar, lucene-highlighter.jar
+Apache XML-RPC:               xmlrpc.jar
+Jakarta Taglibs:              jakarta-tablibs-standard-1.1.2.jar, jakarta-taglibs-jstl-1.1.2.jar
+Jakarta ECS:                  ecs.jar
+Commons Lang:                 commons-lang-2.3.jar
+Commons IO:                   commons-io-1.4.jar
+Commons HTTPClient:           commons-httpclient-3.0.1.jar
+Commons FileUpload:           commons-fileupload-1.2.1.jar
+Commons Codec:                commons-codec-1.3.jar
+
+In addition to these libraries, unit and web unit tests use these Apache libraries:
+
+Jakarata Jasper JSP compiler: jasper-compiler-5.5.25.jar jasper-runtime-5.5.25.jar
+Xerces XML parser:            xercesImpl-2.6.2.jar
+XMLCommons:                   xml-apis-1.0.b2.jar
+
+The JSPWiki project has also demonstrated synergy with Apache by working with helping to fix bugs in Apache software that affected JSPWiki. For example, a bug in Apache Tomcat's package naming restrictions code, which forbade loading of code with the package prefix org.apache.jsp* prevented JSPWiki from using its initial desired package name, org.apache.jspwiki. JSPWiki committer worked with the Tomcat dev team to fix the bug, which was fixed in Tomcat 6.0.19.
+
+Finally, the development team has developed JSPWiki 3.0 to use JSR-170 (Java Content Repository) back-ends for information storage. Although JSPWiki by necessity ships with a limited JCR implementation called Priha (developed by Janne Jalkanen), our intention is to ensure that JSPWiki 3.0 also works smoothly with Apache Jackrabbit.
\ No newline at end of file

Modified: incubator/jspwiki/trunk/etc/jspwiki.properties.tmpl
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/etc/jspwiki.properties.tmpl?rev=806031&r1=806030&r2=806031&view=diff
==============================================================================
--- incubator/jspwiki/trunk/etc/jspwiki.properties.tmpl (original)
+++ incubator/jspwiki/trunk/etc/jspwiki.properties.tmpl Thu Aug 20 03:30:25 2009
@@ -473,22 +473,17 @@
 #  Supply the JAAS LoginModule class used for custom authentication here.
 #  The implementation MUST have a zero-argument constructor (as noted in the
 #  javax.security.auth.spi.LoginModule Javadocs).
-#jspwiki.loginModule.class = org.apache.wiki.auth.login.LdapLoginModule
 jspwiki.loginModule.class = org.apache.wiki.auth.login.UserDatabaseLoginModule
 
 #
-# JAAS LoginContext parameters used to initialize the LoginModule. Note that 'param1'
+#  JAAS LoginContext parameters used to initialize the LoginModule. Note that 'param1'
 #  etc. should be replaced with the actual parameter names. The parameter names and
-# values will be loaded to a Map and passed to the LoginModule as the 'options' parameter
-# when its initialize() method is called. The default UserDatabaseLoginModule class does
-# not need any options. The example below is for LdapLoginModule, configured for OpenLDAP.
-#
-#jspwiki.loginModule.options.ldap.authentication = DIGEST-MD5
-#jspwiki.loginModule.options.ldap.connectionURL = ldap://127.0.0.1:4890/
-#jspwiki.loginModule.options.ldap.loginIdPattern = uid\={0},ou\=people,dc\=jspwiki,dc\=org
-#jspwiki.loginModule.options.ldap.userBase = ou\=people,dc\=jspwiki,dc\=org
-#jspwiki.loginModule.options.ldap.userPattern = (&(objectClass\=inetOrgPerson)(uid\={0}))
-#jspwiki.loginModule.options.ldap.ssl = false
+#  values will be loaded to a Map and passed to the LoginModule as the 'options' parameter
+#  when its initialize() method is called. The default UserDatabaseLoginModule class does
+#  not need any options. The example below is for a fictitious LoginModule.
+#
+#jspwiki.loginModule.options.foo = foosetting
+#jspwiki.loginModule.options.bar = barsetting
 
 # 
 #  Cookie authentication & assertion
@@ -542,19 +537,14 @@
 #  belongs to the roles listed in web.xml using <security-role>/<role-name> or
 #  <auth-constraint>/<role-name> elements. However, you can use another
 #  Authorizer if you wish; specify that class here.
-
+#
 jspwiki.authorizer = org.apache.wiki.auth.authorize.WebContainerAuthorizer
 
 # In addition to the built-in web container authorizer, JSPWiki also supplies
-# an Authorizer that works with LDAP. If the LdapAuthorizer is specified,
-# these settings used to describe how roles are looked up. The connection URL is
-# the same specified above in Custom Authentication. The 'role base' supplies
-# the DN containing role entries.
-#
-#jspwiki.authorizer = org.apache.wiki.auth.authorize.WebContainerAuthorizer
-#ldap.roleBase = ou\=roles,dc\=jspwiki,dc\=org
-#ldap.isInRolePattern = (&(objectClass\=groupOfUniqueNames)(cn\={0})(uniqueMember\={1}))
-#ldap.bindDN = cn\=Manager,dc\=jspwiki,dc\=org
+# an Authorizer that works with LDAP. See the "LDAP" section below for
+# configuration options.
+#jspwiki.authorizer = org.apache.wiki.auth.authorize.LdapAuthorizer
+
 
 #  B) GROUPS
 #  As an additional source of authorization, users can belong to discretionary
@@ -564,6 +554,10 @@
 
 jspwiki.groupdatabase = org.apache.wiki.auth.authorize.XMLGroupDatabase
 
+#  JSPWiki provides an alternative UserDatabase implementation that looks up
+#  user profiles from an LDAP directory. It is read-only. See the "LDAP"
+#  section below for configuration options.
+
 #  The default group database implementation stores member lists
 #  in an XML file. The location of this file should be in a secure directory
 #  in the filesystem; for example, in /etc or your servlet container's
@@ -578,10 +572,11 @@
 #jspwiki.xmlGroupDatabaseFile = /etc/tomcat/groupdatabase.xml
 
 #  USER DATABASE
+#
 #  User's wiki profiles are stored in a UserDatabase. The default user database
 #  uses an XML file for persistent storage.
 #  Override with your own UserDatabase implementation with this property:
-
+#
 jspwiki.userdatabase = org.apache.wiki.auth.user.XMLUserDatabase
 
 #  The default user database implementation stores usernames and passwords
@@ -595,21 +590,27 @@
 #  something useful as soon as you can. But for test wikis, it's probably
 #  ok to leave this un-set, as long as users know that their profiles could
 #  "disappear" if the wiki app is ever redeployed.
-
+#
 #jspwiki.xmlUserDatabaseFile = /etc/tomcat/userdatabase.xml
 
-# You can also use a JDBC database for storing user profiles.
-# See the online AuthenticationAndAuthorization2.3 docs for details on
-# how to configure it.
-
+#  You can also use a JDBC database for storing user profiles.
+#  See the online AuthenticationAndAuthorization2.3 docs for details on
+#  how to configure it.
+#
 #jspwiki.userdatabase = org.apache.wiki.auth.user.JDBCUserDatabase
 
+#  You can also use a read-only LDAP database for obtaining user
+#  profiles. See the "LDAP" section below for configuration details.
+#
+#jspwiki.userdatabase = org.apache.wiki.auth.user.LdapUserDatabase
+
 #  If your user database is read-only, set this property to true.
 #  LdapUserDatabase, for example, should almost always be read-only.
 
 jspwiki.userdatabase.readOnlyProfiles = false
 
 #  ACCESS CONTROL LISTS
+#
 #  Last but not least, JSPWiki needs a way of reading and persisting page
 #  access control lists. The default implementation reads these from the page
 #  markup. For example: "[{ALLOW edit Charlie}]". If using a custom
@@ -617,6 +618,38 @@
 
 jspwiki.aclManager = org.apache.wiki.auth.acl.DefaultAclManager
 
+# LDAP CONFIGURATION
+#
+#  JSPWiki can use LDAP for authentication, role lookups and user lookups.
+#  You must set these first three properties. The 'ldap.config' property sets
+#  sensible defaults for OpenLDAP and Active Directory. Valid values
+#  (unsurprisingly) are OPEN_LDAP and ACTIVE_DIRECTORY.
+#
+#ldap.config = ACTIVE_DIRECTORY
+#ldap.connectionURL = ldap://127.0.0.1:4890/
+#ldap.userBase = ou\=people,dc\=jspwiki,dc\=org
+
+#  If the LdapAuthorizer is specified for role lookups, these settings 
+#  describe how roles are looked up. The connection URL, SSL and authentication
+#  properties are the same specified above. The role base specifies where roles
+#  (LDAP group objects) reside. The 'ldap.bindDN' specifies the user identity used
+#  to bind to LDAP to perform searches. The password for this identity is stored
+#  in the keychain (see KEYCHAIN above) under the entry name 'ldap.bindDNPassword'.
+#
+#ldap.roleBase = ou\=roles,dc\=jspwiki,dc\=org
+#ldap.bindDN = cn\=Manager,dc\=jspwiki,dc\=org
+
+#  For fine-tuning LDAP connection and search properties, you can modify the settings
+#  below. See the Javadoc for the org.apache.wiki.auth.LdapConfig class for more details.
+#
+#ldap.authentication = DIGEST-MD5
+#ldap.ssl = false
+#ldap.loginIdPattern = {0}
+#ldap.isInRoleFilter = (&(&(objectClass\=group)(cn\={0}))(member\={1}))
+#ldap.userFilter = (&(objectClass\=person)(sAMAccountName\={0}))
+#ldap.user.loginName = sAMAccountName
+#ldap.user.objectClass = person
+
 #############################################################################
 #
 # InterWiki links

Modified: incubator/jspwiki/trunk/src/java/org/apache/wiki/Release.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/java/org/apache/wiki/Release.java?rev=806031&r1=806030&r2=806031&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/java/org/apache/wiki/Release.java (original)
+++ incubator/jspwiki/trunk/src/java/org/apache/wiki/Release.java Thu Aug 20 03:30:25 2009
@@ -77,7 +77,7 @@
      *  <p>
      *  If the build identifier is empty, it is not added.
      */
-    public static final String     BUILD         = "143";
+    public static final String     BUILD         = "144";
 
     /**
      *  This is the generic version string you should use

Modified: incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/AuthenticationManager.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/AuthenticationManager.java?rev=806031&r1=806030&r2=806031&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/AuthenticationManager.java (original)
+++ incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/AuthenticationManager.java Thu Aug 20 03:30:25 2009
@@ -110,7 +110,7 @@
     protected static final Logger              log                 = LoggerFactory.getLogger( AuthenticationManager.class );
 
     /** Prefix for LoginModule options key/value pairs. */
-    public static final String                 PREFIX_LOGIN_MODULE_OPTIONS = "jspwiki.loginModule.options.";
+    protected static final String                 PREFIX_LOGIN_MODULE_OPTIONS = "jspwiki.loginModule.options.";
 
     /** If this jspwiki.properties property is <code>true</code>, allow cookies to be used to assert identities. */
     protected static final String                 PROP_ALLOW_COOKIE_ASSERTIONS = "jspwiki.cookieAssertions";
@@ -881,42 +881,76 @@
     }
     
     /**
-     * After successful login, this method is called to inject authorized role Principals into the WikiSession.
-     * To determine which roles should be injected, the configured Authorizer
-     * is queried for the roles it knows about by calling  {@link org.apache.wiki.auth.Authorizer#getRoles()}.
-     * Then, each role returned by the authorizer is tested by calling {@link org.apache.wiki.auth.Authorizer#isUserInRole(WikiSession, Principal)}.
-     * If this check fails, and the Authorizer is of type WebAuthorizer, the role is checked again by calling
-     * {@link org.apache.wiki.auth.authorize.WebAuthorizer#isUserInRole(javax.servlet.http.HttpServletRequest, Principal)}).
-     * Any roles that pass the test are injected into the Subject by firing appropriate authentication events.
+     * <p>
+     * After successful login, this method is called to inject authorized role
+     * Principals into the WikiSession. To determine which roles should be
+     * injected, the configured Authorizer is queried for the roles the user
+     * posssesses by calling
+     * {@link org.apache.wiki.auth.Authorizer#findRoles(WikiSession)}. If this
+     * method does not throw a {@link WikiSecurityException}, any roles that are
+     * found are injected into the Subject.
+     * </p>
+     * <p>
+     * However, not all Authorizers know how to return roles for a given
+     * WikiSession. In this case, we must resort to a more brute-force approach
+     * to identify which roles the user posssesses. First, we ask the Authorizer
+     * to return the roles it knows about by calling
+     * {@link org.apache.wiki.auth.Authorizer#getRoles()}. Then, each role
+     * returned by the authorizer is tested by calling
+     * {@link org.apache.wiki.auth.Authorizer#isUserInRole(WikiSession, Principal)}
+     * . If this check fails, and the Authorizer is of type WebAuthorizer, the
+     * role is checked again by calling
+     * {@link org.apache.wiki.auth.authorize.WebAuthorizer#isUserInRole(javax.servlet.http.HttpServletRequest, Principal)}
+     * ). Any roles that pass the test are injected into the Subject by firing
+     * appropriate authentication events.
+     * 
      * @param session the user's current WikiSession
      * @param authorizer the WikiEngine's configured Authorizer
      * @param request the user's HTTP session, which may be <code>null</code>
      */
     private final void injectAuthorizerRoles( WikiSession session, Authorizer authorizer, HttpServletRequest request )
     {
+        // See if the Authorizer allows us to ask directly what roles the user has
+        try
+        {
+            Role[] roles = authorizer.findRoles( session );
+            for ( Role role : roles )
+            {
+                fireEvent( WikiSecurityEvent.PRINCIPAL_ADD, role, session );
+                if( log.isDebugEnabled() )
+                {
+                    log.debug( "Added authorizer role " + role.getName() + "." );
+                }
+            }
+            return;
+        }
+        catch( WikiSecurityException e )
+        {
+            // No worries; the authorizer doesn't support it
+        }
+        
         // Test each role the authorizer knows about
-        for ( Principal role : authorizer.getRoles() )
+        for( Principal role : authorizer.getRoles() )
         {
-            // Test the Authorizer
-            if ( authorizer.isUserInRole( session, role ) )
+            if( authorizer.isUserInRole( session, role ) )
             {
                 fireEvent( WikiSecurityEvent.PRINCIPAL_ADD, role, session );
-                if ( log.isDebugEnabled() )
+                if( log.isDebugEnabled() )
                 {
-                    log.debug("Added authorizer role " + role.getName() + "." );
+                    log.debug( "Added authorizer role " + role.getName() + "." );
                 }
             }
-            
+
             // If web authorizer, test the request.isInRole() method also
-            else if ( request != null && authorizer instanceof WebAuthorizer )
+            else if( request != null && authorizer instanceof WebAuthorizer )
             {
-                WebAuthorizer wa = (WebAuthorizer)authorizer;
-                if ( wa.isUserInRole( request, role ) )
+                WebAuthorizer wa = (WebAuthorizer) authorizer;
+                if( wa.isUserInRole( request, role ) )
                 {
                     fireEvent( WikiSecurityEvent.PRINCIPAL_ADD, role, session );
-                    if ( log.isDebugEnabled() )
+                    if( log.isDebugEnabled() )
                     {
-                        log.debug("Added container role " + role.getName() + "." );
+                        log.debug( "Added container role " + role.getName() + "." );
                     }
                 }
             }

Modified: incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/Authorizer.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/Authorizer.java?rev=806031&r1=806030&r2=806031&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/Authorizer.java (original)
+++ incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/Authorizer.java Thu Aug 20 03:30:25 2009
@@ -25,23 +25,22 @@
 
 import org.apache.wiki.WikiEngine;
 import org.apache.wiki.WikiSession;
-
+import org.apache.wiki.auth.authorize.Role;
 
 /**
  * Interface for service providers of authorization information. After a user
- * successfully logs in, the
- * {@link org.apache.wiki.auth.AuthenticationManager} consults the configured
- * Authorizer to determine which additional
- * {@link org.apache.wiki.auth.authorize.Role} principals should be added to
- * the user's WikiSession. To determine which roles should be injected, the
+ * successfully logs in, the {@link org.apache.wiki.auth.AuthenticationManager}
+ * consults the configured Authorizer to determine which additional
+ * {@link org.apache.wiki.auth.authorize.Role} principals should be added to the
+ * user's WikiSession. To determine which roles should be injected, the
  * Authorizer is queried for the roles it knows about by calling
- * {@link org.apache.wiki.auth.Authorizer#getRoles()}. Then, each role
- * returned by the Authorizer is tested by calling
+ * {@link org.apache.wiki.auth.Authorizer#getRoles()}. Then, each role returned
+ * by the Authorizer is tested by calling
  * {@link org.apache.wiki.auth.Authorizer#isUserInRole(WikiSession, Principal)}.
  * If this check fails, and the Authorizer is of type WebAuthorizer,
  * AuthenticationManager checks the role again by calling
- * {@link org.apache.wiki.auth.authorize.WebAuthorizer#isUserInRole(javax.servlet.http.HttpServletRequest, Principal)}).
- * Any roles that pass the test are injected into the Subject by firing
+ * {@link org.apache.wiki.auth.authorize.WebAuthorizer#isUserInRole(javax.servlet.http.HttpServletRequest, Principal)}
+ * ). Any roles that pass the test are injected into the Subject by firing
  * appropriate authentication events.
  * 
  * @author Andrew Jaquith
@@ -62,9 +61,9 @@
 
     /**
      * Looks up and returns a role Principal matching a given String. If a
-     * matching role cannot be found, this method returns <code>null</code>.
-     * Note that it may not always be feasible for an Authorizer implementation
-     * to return a role Principal.
+     * matching role cannot be found, this method returns {@code null}. Note
+     * that it may not always be feasible for an Authorizer implementation to
+     * return a role Principal.
      * 
      * @param role the name of the role to retrieve
      * @return the role Principal
@@ -72,6 +71,20 @@
     public Principal findRole( String role );
 
     /**
+     * Looks up and returns the role Principals for a given WikiSession. If the
+     * user possesses no roles, this method returns a zero-length array. If the
+     * Authorizer implementation cannot look up roles (for example,
+     * {@link org.apache.wiki.auth.authorize.WebContainerAuthorizer} cannot),
+     * this method should throw an {@link WikiSecurityException}.
+     * 
+     * @param session
+     * @return the roles the user possesses
+     * @throws WikiSecurityException if the Authorizer cannot return
+     *             roles for a given WikiSession
+     */
+    public Role[] findRoles( WikiSession session ) throws WikiSecurityException;
+
+    /**
      * Initializes the authorizer.
      * 
      * @param engine the current wiki engine
@@ -84,13 +97,13 @@
      * Determines whether the Subject associated with a WikiSession is in a
      * particular role. This method takes two parameters: the WikiSession
      * containing the subject and the desired role ( which may be a Role or a
-     * Group). If either parameter is <code>null</code>, this method must
-     * return <code>false</code>.
+     * Group). If either parameter is {@code null}, this method must return
+     * {@code false}.
      * 
      * @param session the current WikiSession
      * @param role the role to check
-     * @return <code>true</code> if the user is considered to be in the role,
-     *         <code>false</code> otherwise
+     * @return {@code true} if the user is considered to be in the role, {@code
+     *         false} otherwise
      */
     public boolean isUserInRole( WikiSession session, Principal role );
 

Modified: incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/LdapConfig.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/LdapConfig.java?rev=806031&r1=806030&r2=806031&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/LdapConfig.java (original)
+++ incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/LdapConfig.java Thu Aug 20 03:30:25 2009
@@ -13,49 +13,103 @@
 import javax.naming.directory.SearchResult;
 import javax.naming.ldap.InitialLdapContext;
 
-import org.apache.wiki.auth.login.AbstractLoginModule;
 import org.apache.wiki.util.TextUtil;
 import org.freshcookies.security.Keychain;
 
 /**
- * Immutable holder for configuration of LDAP-related security modules.
+ * <p>
+ * Immutable configuration object that is used to initialize
+ * {@link org.apache.wiki.auth.authorize.LdapAuthorizer} and
+ * {@link org.apache.wiki.auth.user.LdapUserDatabase}. LdapConfig contains
+ * configuration information used to initialize connections to an LDAP server,
+ * search specified base DNs for user and role (LDAP group) objects, and look up
+ * user attributes.
+ * </p>
+ * <p>
+ * LdapConfig objects are initialized via the static factory method
+ * {@link #getInstance(Keychain, Map, String[])}. The parameters passed to the
+ * factory method are:
+ * </p>
+ * <ul>
+ * <li>a Map containing key/value String pairs that supply configuration
+ * property values. Possible properties include all of the static members in
+ * this class prefixed {@code PROPERTY_}, such as
+ * {@link #PROPERTY_AUTHENTICATION}.</li>
+ * <li>a {@link Keychain} object that optionally stores the password used for
+ * binding to the LDAP server, if a "bind DN" property was set by
+ * {@link #PROPERTY_BIND_DN}.</li>
+ * <li>an array of String objects that supply the property names that must be
+ * configured in order for the LdapConfig initialization to succeed. The
+ * required properties are set by the calling program to account for the fact
+ * that different classes need different things. For example, LdapAuthorizer
+ * requires these properties to be set: {@link #PROPERTY_CONNECTION_URL},
+ * {@link #PROPERTY_ROLE_BASE} and {@link #PROPERTY_IS_IN_ROLE_FILTER}.</li>
+ * </ul>
+ * <p>
+ * <p>
+ * For callers who need to set properties directly rather than use one of the
+ * shortcut configs, some of the more important configuration properties
+ * include:
+ * </p>
+ * <ul>
+ * <li>{@link #PROPERTY_CONNECTION_URL} - the connection string for the LDAP
+ * server, for example <code>ldap://ldap.jspwiki.org:389/</code>.</li>
+ * <li>{@link #PROPERTY_LOGIN_ID_PATTERN} - optional string pattern indicating
+ * how the login id should be formatted into a credential the LDAP server will
+ * understand. The exact credential pattern varies by LDAP server. OpenLDAP
+ * expects login IDs that match a distinguished name. Active Directory, on the
+ * other hand, requires just the "short" login ID that is not in DN format. The
+ * user ID supplied during the login will be substituted into the
+ * <code>{0}</code> token in this pattern. Valid examples of login ID patterns
+ * include <code>uid={0},ou=users,dc=jspwiki,dc=org</code> (for OpenLDAP) and
+ * <code>{0}</code> (for Active Directory).</li>
+ * <li>{@link #PROPERTY_USER_BASE} - the distinguished name of the base location
+ * where user objects are located. This is generally an organizational unit (OU)
+ * DN, such as <code>ou=people,dc=jspwiki,dc=org</code>. The user base and all
+ * of its subtrees will be searched. For directories that contain multiple OUs
+ * where users are located, use a higher-level base location (e.g.,
+ * <code>dc=jspwiki,dc=org</code>).</li>
+ * <li>{@link #PROPERTY_USER_FILTER} - an RFC 2254 search filter string used for
+ * locating the actual user object within the user base. The user ID supplied
+ * during the login will be substituted into the <code>{0}</code> token in this
+ * filter, if it contains one. Only the first match will be selected, so it is
+ * important that this filter selects unique objects. For example, if the user
+ * filter is <code>(&(objectClass=inetOrgPerson)(uid={0}))</code> and the user
+ * name supplied during login is <code>fflintstone</code>, the the first object
+ * within {@link #PROPERTY_USER_BASE} that matches the filter
+ * <code>(&(objectClass=inetOrgPerson)(uid=fflintstone))</code> will be
+ * selected. A suitable value for this property that works with Active Directory
+ * 2000 and later is <code>(&(objectClass=person)(sAMAccountName={0}))</code>.</li>
+ * <li>{@link #PROPERTY_SSL} - Optional parameter that specifies whether to use
+ * SSL when connecting to the LDAP server. Values like <code>true</code> or
+ * <code>on</code> indicate that SSL should be used. If this parameter is not
+ * supplied, SSL will not be used.</li>
+ * <li>{@link #PROPERTY_AUTHENTICATION} - Optional parameter that specifies the
+ * type of authentication method to be used. Valid values include
+ * <code>simple</code> for plaintext username/password, and
+ * <code>DIGEST-MD5</code> for digested passwords. Note that if SSL is not used,
+ * for safety reasons this method will default to <code>DIGEST-MD5</code> to
+ * prevent password interception.</li>
+ * </ul>
+ * <p>
+ * LdapConfig objects are immutable and therefore thread-safe.
+ * </p>
  */
 public class LdapConfig
 {
-    public static final Map<String,String> ACTIVE_DIRECTORY_CONFIG;
-    
-    public static final Map<String,String> OPEN_LDAP_CONFIG;
-
-    public static final String KEYCHAIN_BIND_DN_ENTRY = "ldap.bindDNPassword";
-    
-    /**
-     * Property that indicates what LDAP server configuration to use. Valid values include
-     * <code>ad</code> for Active Directory and <code>openldap</code> for OpenLDAP.
-     * If this value is set, the default settings for these configurations will be loaded.
-     */
-    public static final String PROPERTY_CONFIG = "ldap.config";
-    
     /**
-     * Property that indicates whether to use SSL for connecting to the LDAP
-     * server. This property is also used by
-     * {@link org.apache.wiki.auth.login.LdapLoginModule}.
+     * The name of the {@link Keychain} entry that supplies the password used by
+     * the "bind DN", if one was specified by {@link #PROPERTY_BIND_DN}.
      */
-    public static final String PROPERTY_SSL = "ldap.ssl";
+    public static final String KEYCHAIN_BIND_DN_ENTRY = "ldap.bindDNPassword";
 
     /**
-     * Property that supplies the pattern for finding roles within the role base, e.g.
-     * <code>(&(objectClass=groupOfUniqueNames)(cn={0}))</code>
-     * .
-     */
-    public static final String PROPERTY_ROLE_PATTERN = "ldap.rolePattern";
-    
-    /**
-     * Property that supplies the pattern for finding users within the role base
-     * that possess a given role, e.g.
-     * <code>(&(objectClass=groupOfUniqueNames)(cn={0})(uniqueMember={1}))</code>
-     * .
+     * Property that specifies the JNDI authentication type. Valid values are
+     * the same as those for {@link Context#SECURITY_AUTHENTICATION}: {@code
+     * none}, {@code simple}, {@code strong} or {@code DIGEST-MD5}. The default
+     * is {@code simple} if SSL is specified, and {@code DIGEST-MD5} otherwise.
      */
-    public static final String PROPERTY_IS_IN_ROLE_PATTERN = "ldap.isInRolePattern";
+    public static final String PROPERTY_AUTHENTICATION = "ldap.authentication";
 
     /**
      * Property that supplies the DN used to bind to the directory when looking
@@ -64,168 +118,397 @@
     public static final String PROPERTY_BIND_DN = "ldap.bindDN";
 
     /**
+     * Property that indicates what LDAP server configuration to use. Valid
+     * values include {@code ad} for Active Directory and {@code openldap} for
+     * OpenLDAP. If this value is set, the default settings for these
+     * configurations will be loaded.
+     */
+    public static final String PROPERTY_CONFIG = "ldap.config";
+
+    /**
      * Property that supplies the connection URL for the LDAP server, e.g.
-     * <code>ldap://127.0.0.1:4890/</code>. This property is also used by
-     * {@link org.apache.wiki.auth.login.LdapLoginModule}.
+     * {@code ldap://127.0.0.1:4890/}.
      */
     public static final String PROPERTY_CONNECTION_URL = "ldap.connectionURL";
 
     /**
-     * Property that specifies the JNDI authentication type. Valid values are
-     * the same as those for {@link Context#SECURITY_AUTHENTICATION}:
-     * <code>none</code>, <code>simple</code>, <code>strong</code> or
-     * <code>DIGEST-MD5</code>. The default is <code>simple</code> if SSL is
-     * specified, and <code>DIGEST-MD5</code> otherwise. This property is also
-     * used by {@link org.apache.wiki.auth.login.LdapLoginModule}.
+     * Property that supplies the filter for finding users within the role base
+     * that possess a given role, e.g. {@code
+     * (&(objectClass=groupOfUniqueNames)(cn= 0})(uniqueMember={1}))} .
+     */
+    public static final String PROPERTY_IS_IN_ROLE_FILTER = "ldap.isInRoleFilter";
+
+    /**
+     * Property that specifies the pattern for the username used to log in to
+     * the LDAP server. This pattern maps the username supplied at login time by
+     * the user to a username format the LDAP server can recognized. Usually
+     * this is a pattern that produces a full DN, for example {@code uid= 0}
+     * ,ou=people,dc=jspwiki,dc=org}. However, sometimes (as with Active
+     * Directory 2003 and later) only the userid is used, in which case the
+     * principal will simply be {@code 0} . The default value if not supplied is
+     * {@code 0} .
      */
-    public static final String PROPERTY_AUTHENTICATION = "ldap.authentication";
-    
+    public static final String PROPERTY_LOGIN_ID_PATTERN = "ldap.loginIdPattern";
+
+    /**
+     * Property that supplies the base DN where roles are contained, e.g.
+     * {@code ou=roles,dc=jspwiki,dc=org}.
+     */
+    public static final String PROPERTY_ROLE_BASE = "ldap.roleBase";
+
     /**
-     * Property that specifies the login name attribute for user objects.
-     * By default, this is <code>uid</code>.
+     * Property that indicates whether to use SSL for connecting to the LDAP
+     * server.
      */
-    public static final String PROPERTY_USER_LOGIN_NAME_ATTRIBUTE = "ldap.user.loginName";
-    
+    public static final String PROPERTY_SSL = "ldap.ssl";
+
     /**
      * Property that specifies the base DN where users are contained,
-     * <em>e.g.,</em> <code>ou=people,dc=jspwiki,dc=org</code>. This DN is
-     * searched recursively.
+     * <em>e.g.,</em> {@code ou=people,dc=jspwiki,dc=org}. This DN is searched
+     * recursively.
      */
     public static final String PROPERTY_USER_BASE = "ldap.userBase";
-    
+
     /**
-     * Property that specifies the DN pattern for finding users within the
-     * user base, <em>e.g.,</em>
-     * <code>(&(objectClass=inetOrgPerson)(uid={0}))</code>
+     * Property that specifies the login name attribute for user objects. By
+     * default, this is {@code uid}.
      */
-    public static final String PROPERTY_USER_PATTERN = "ldap.userPattern";
+    public static final String PROPERTY_USER_LOGIN_NAME_ATTRIBUTE = "ldap.user.loginName";
 
-    
     /**
-     * Property that specifies the pattern for the username used to log in to the
-     * LDAP server. This pattern maps the username supplied at login time by the
-     * user to a username format the LDAP server can recognized. Usually this is
-     * a pattern that produces a full DN, for example
-     * <code>uid={0},ou=people,dc=jspwiki,dc=org</code>. However, sometimes (as
-     * with Active Directory 2003 and later) only the userid is used, in which
-     * case the principal will simply be <code>{0}</code>. The default value
-     * if not supplied is <code>{0}</code>.
+     * Property that supplies the class name for user objects. By default, this
+     * is {@code inetOrgPerson}.
      */
-    public static final String PROPERTY_LOGIN_ID_PATTERN = "ldap.loginIdPattern";
-    
     public static final String PROPERTY_USER_OBJECT_CLASS = "ldap.user.objectClass";
-    
+
     /**
-     * Property that supplies the base DN where roles are contained, e.g.
-     * <code>ou=roles,dc=jspwiki,dc=org</code>.
+     * Property that specifies the filter for finding users within the user
+     * base, <em>e.g.,</em> {@code (&(objectClass=inetOrgPerson)(uid= 0}))}
      */
-    public static final String PROPERTY_ROLE_BASE = "ldap.roleBase";
-
-    public final String connectionUrl;
-
-    public final String roleBase;
-    
-    public final String rolePattern;
-
-    public final String isInRolePattern;
-
-    public final String bindDN;
+    public static final String PROPERTY_USER_FILTER = "ldap.userFilter";
 
-    public final String ssl;
-
-    public final String authentication;
-    
-    public final String userBase;
-    
-    public final String loginIdPattern;
-    
-    public final String userPattern;
-    
-    public final String userLoginNameAttribute;
+    private static final Map<Default,LdapConfig> CONFIGS = new HashMap<Default,LdapConfig>();
 
-    public final String userObjectClass;
-    
-    private final Set<String> m_configured = new HashSet<String>();
+    private static final SearchControls SEARCH_CONTROLS;
 
-    private Keychain m_keychain;
-    
     static
     {
+        SEARCH_CONTROLS = new SearchControls();
+        SEARCH_CONTROLS.setSearchScope( SearchControls.SUBTREE_SCOPE );
+
         // Active Directory 2000+ defaults
-        Map<String,String> options = new HashMap<String,String>();
-        options.put( PROPERTY_IS_IN_ROLE_PATTERN, "(&(objectClass=group)(cn={0})(member={1}))" );
+        Map<String, String> options = new HashMap<String, String>();
+        options.put( PROPERTY_IS_IN_ROLE_FILTER, "(&(&(objectClass=group)(cn={0}))(member={1}))" );
         options.put( PROPERTY_LOGIN_ID_PATTERN, "{0}" );
-        options.put( PROPERTY_ROLE_PATTERN, "(&(objectClass=group)(cn={0}))" );
         options.put( PROPERTY_USER_LOGIN_NAME_ATTRIBUTE, "sAMAccountName" );
         options.put( PROPERTY_USER_OBJECT_CLASS, "person" );
-        options.put( PROPERTY_USER_PATTERN, "(&(objectClass=person)(sAMAccountName={0}))" );
-        ACTIVE_DIRECTORY_CONFIG = Collections.unmodifiableMap( options );
-        
+        options.put( PROPERTY_USER_FILTER, "(&(objectClass=person)(sAMAccountName={0}))" );
+        LdapConfig config = new LdapConfig( null,options,new String[0] );
+        CONFIGS.put( Default.ACTIVE_DIRECTORY, config );
+
         // OpenLDAP defaults
-        options = new HashMap<String,String>();
-        options.put( PROPERTY_IS_IN_ROLE_PATTERN, "(&(&(objectClass=groupOfUniqueNames)(cn={0}))(uniqueMember={1}))" );
-        options.put( PROPERTY_ROLE_PATTERN, "(&(objectClass=groupOfUniqueNames)(cn={0}))" );
+        options = new HashMap<String, String>();
+        options.put( PROPERTY_IS_IN_ROLE_FILTER, "(&(&(objectClass=groupOfUniqueNames)(cn={0}))(uniqueMember={1}))" );
         options.put( PROPERTY_USER_LOGIN_NAME_ATTRIBUTE, "uid" );
         options.put( PROPERTY_USER_OBJECT_CLASS, "inetOrgPerson" );
-        options.put( PROPERTY_USER_PATTERN, "(&(objectClass=inetOrgPerson)(uid={0}))" );
-        OPEN_LDAP_CONFIG = Collections.unmodifiableMap( options );
+        options.put( PROPERTY_USER_FILTER, "(&(objectClass=inetOrgPerson)(uid={0}))" );
+        config = new LdapConfig( null,options,new String[0] );
+        CONFIGS.put( Default.OPEN_LDAP, config );
     }
 
-    private String getProperty( Map<? extends Object,? extends Object> props, String property, String defaultValue )
+    /**
+     * Escapes a string so that it conforms to an RFC2254-compliant
+     * LDAP search filter. See
+     * http://blogs.sun.com/shankar/entry/what_is_ldap_injection
+     * 
+     * @param dn the DN to escape
+     * @return the escaped DN
+     */
+    public static String escapeFilterString( String dn )
     {
-        String shortProperty = property;
-        String longProperty = AuthenticationManager.PREFIX_LOGIN_MODULE_OPTIONS + property;
-        String value = (String)props.get( property );
-        if ( value == null )
-        {
-            property = longProperty;
-            value = (String)props.get( property );
-        }
-        if( value != null && value.length() > 0 )
+        StringBuilder s = new StringBuilder();
+        for( char c : dn.toCharArray() )
         {
-            m_configured.add( shortProperty );
-            m_configured.add( longProperty );
-            return props.get( property ).toString().trim();
+            switch( c )
+            {
+                case '=': {
+                    s.append( '\\' );
+                    s.append( '3' );
+                    s.append( 'd' );
+                    break;
+                }
+                case '(': {
+                    s.append( '\\' );
+                    s.append( '2' );
+                    s.append( '8' );
+                    break;
+                }
+                case ')': {
+                    s.append( '\\' );
+                    s.append( '2' );
+                    s.append( '9' );
+                    break;
+                }
+                case '&': {
+                    s.append( '\\' );
+                    s.append( '2' );
+                    s.append( '6' );
+                    break;
+                }
+                case '|': {
+                    s.append( '\\' );
+                    s.append( '7' );
+                    s.append( 'c' );
+                    break;
+                }
+                case '>': {
+                    s.append( '\\' );
+                    s.append( '3' );
+                    s.append( 'e' );
+                    break;
+                }
+                case '<': {
+                    s.append( '\\' );
+                    s.append( '3' );
+                    s.append( 'c' );
+                    break;
+                }
+                case '~': {
+                    s.append( '\\' );
+                    s.append( '7' );
+                    s.append( 'e' );
+                    break;
+                }
+                case '*': {
+                    s.append( '\\' );
+                    s.append( '2' );
+                    s.append( 'a' );
+                    break;
+                }
+                case '/': {
+                    s.append( '\\' );
+                    s.append( '2' );
+                    s.append( 'f' );
+                    break;
+                }
+                case '\\': {
+                    s.append( '\\' );
+                    s.append( '5' );
+                    s.append( 'c' );
+                    break;
+                }
+                default: {
+                    s.append( c );
+                }
+            }
         }
-        return defaultValue;
+        return s.toString();
+    }
+
+    /**
+     * Typesafe enumeration indicating which configuration to use.
+     */
+    public enum Default { 
+        /** Active Directory 2000 and higher. */
+        ACTIVE_DIRECTORY, 
+        /** OpenLDAP. */
+        OPEN_LDAP;
     }
     
-    public String getBindDNPassword() throws KeyStoreException
+    /**
+     * For a supplied LDAP user object, returns the user's equivalent JSPWiki
+     * "full name." The full name will be equal to the user's first name (
+     * {@code givenName}) + last name ({@code sn}) attributes, separated by a
+     * space, <em>or</em> the user object's common name ({@code cn}) attribute,
+     * in that order of preference.
+     * 
+     * @param attributes the attributes supplying the common name, surname
+     *            and/or given name
+     * @return the user's JSPWiki full name
+     * @throws NamingException if the attributes cannot be retrieved for any
+     *             reason
+     */
+    public static String getFullName( Attributes attributes ) throws NamingException
     {
-        if( m_keychain == null )
+        boolean hasCommonName = attributes.get( "cn" ) != null;
+        boolean hasSurname = attributes.get( "sn" ) != null;
+        boolean hasGivenName = attributes.get( "givenName" ) != null;
+
+        String fullName = null;
+        if( hasGivenName && hasSurname )
         {
-            throw new KeyStoreException( "LdapConfig was initialized without a keychain!" );
+            fullName = attributes.get( "givenName" ).get( 0 ) + " " + attributes.get( "sn" ).get( 0 );
         }
-        KeyStore.Entry password = m_keychain.getEntry( LdapConfig.KEYCHAIN_BIND_DN_ENTRY );
-        if( password instanceof Keychain.Password )
+        else if( hasCommonName )
         {
-            return ((Keychain.Password) password).getPassword();
+            fullName = attributes.get( "cn" ).get( 0 ).toString();
         }
-        return null;
+        else
+        {
+            throw new NamingException( "User did not have a givenName+sn or cn." );
+        }
+        return fullName;
     }
 
-    public static LdapConfig getInstance( Keychain keychain, Map<? extends Object,? extends Object> props, String[] requiredProperties )
+    /**
+     * Factory method that creates a new LdapConfig object.
+     * 
+     * @param keychain the Keychain that stores the password for the "bind DN",
+     *            if one is used for this config
+     * @param props the properties object containing the initialization
+     *            parameters for the config
+     * @param requiredProperties the properties that are must be set in order
+     *            for configuration to succeed
+     * @return the initialized LdapConfig object
+     */
+    public static LdapConfig getInstance( Keychain keychain, Map<? extends Object, ? extends Object> props,
+                                          String[] requiredProperties )
     {
         return new LdapConfig( keychain, props, requiredProperties );
     }
 
-    private LdapConfig( Keychain keychain, Map<? extends Object,? extends Object> props, String[] requiredProperties )
-    {
+    /**
+     * The configured value that specifies the JNDI connection URL for the LDAP
+     * server.
+     * 
+     * @see #PROPERTY_CONNECTION_URL
+     */
+    public final String connectionUrl;
+
+    /**
+     * The configured base DN to be searched for group objects that supply
+     * roles.
+     * 
+     * @see #PROPERTY_ROLE_BASE
+     */
+    public final String roleBase;
+
+    /**
+     * The configured filter for finding whether a user belongs to a particular
+     * group.
+     * 
+     * @link #PROPERTY_IS_IN_ROLE_FILTER
+     */
+    public final String isInRoleFilter;
+
+    /**
+     * The distinguished name used for connecting to the LDAP server.
+     * 
+     * @link #bindDN
+     */
+    public final String bindDN;
+
+    /**
+     * The configured value of the SSL property.
+     * 
+     * @see #PROPERTY_SSL
+     */
+    public final String ssl;
+
+    /**
+     * The configured value of the authentication protocol to be used.
+     * 
+     * @see #PROPERTY_AUTHENTICATION
+     */
+    public final String authentication;
+
+    /**
+     * The configured base DN to be searched for user objects.
+     * 
+     * @see #PROPERTY_USER_BASE
+     */
+    public final String userBase;
+
+    /**
+     * The configured pattern used to create login IDs for authenticating to
+     * LDAP.
+     * 
+     * @see #PROPERTY_LOGIN_ID_PATTERN
+     */
+    public final String loginIdPattern;
+
+    /**
+     * The configured filter for finding user objects within the user base DN.
+     * 
+     * @see #PROPERTY_USER_FILTER
+     */
+    public final String userFilter;
+
+    /**
+     * The configured attribute name that supplies user login names.
+     * 
+     * @see #PROPERTY_USER_LOGIN_NAME_ATTRIBUTE
+     */
+    public final String userLoginNameAttribute;
+
+    /**
+     * The configured class name of the user object.
+     * 
+     * @see #PROPERTY_USER_OBJECT_CLASS
+     */
+    public final String userObjectClass;
+
+    private final Set<String> m_configured = new HashSet<String>();
+
+    private final Keychain m_keychain;
+
+    private final Map<String, String> m_userDns = new HashMap<String, String>();
+
+    /**
+     * Private constructor that creates an immutable LdapConfig object.
+     * 
+     * @param keychain the Keychain that stores the password for the "bind DN",
+     *            if one is used for this config
+     * @param props the properties object containing the initialization
+     *            parameters for the config
+     * @param requiredProperties the properties that are must be set in order
+     *            for configuration to succeed
+     * @return the initialized LdapConfig object
+     */
+    private LdapConfig( Keychain keychain, Map<? extends Object, ? extends Object> props, String[] requiredProperties )
+    {
+        // Set defaults
+        String defaultIsInRoleFilter = null;
+        String defaultLoginIdPattern = "{0}";
+        String defaultUserLoginNameAttribute = "uid";
+        String defaultUserObjectClass = "inetOrgPerson";
+        String defaultUserFilter = null;
+        
+        // Did user select a config shortcut for AD or OpenLdap?
+        String config = (String) props.get( PROPERTY_CONFIG );
+        if ( config != null )
+        {
+            try
+            {
+                Default configEnum = Default.valueOf( config ); 
+                LdapConfig defaults = CONFIGS.get( configEnum );
+                defaultIsInRoleFilter = defaults.isInRoleFilter;
+                defaultLoginIdPattern = defaults.loginIdPattern;
+                defaultUserLoginNameAttribute = defaults.userLoginNameAttribute;
+                defaultUserObjectClass = defaults.userObjectClass;
+                defaultUserFilter = defaults.userFilter;
+            }
+            catch( IllegalArgumentException e )
+            {
+                throw new IllegalArgumentException( "'" + config + 
+                  "' is not a valid config value for " + PROPERTY_CONFIG + ".", e );
+            }
+        }
+
         // Basic connection properties
         connectionUrl = getProperty( props, PROPERTY_CONNECTION_URL, null );
 
         // Binding DN properties
         m_keychain = keychain;
         bindDN = getProperty( props, PROPERTY_BIND_DN, null );
-        
+
         // User lookup properties
         userBase = getProperty( props, PROPERTY_USER_BASE, null );
-        userPattern = getProperty( props, PROPERTY_USER_PATTERN, null );
+        userFilter = getProperty( props, PROPERTY_USER_FILTER, defaultUserFilter );
 
         // Role lookup properties
         roleBase = getProperty( props, PROPERTY_ROLE_BASE, null );
-        rolePattern = getProperty( props, PROPERTY_ROLE_PATTERN, null );
-        isInRolePattern = getProperty( props, PROPERTY_IS_IN_ROLE_PATTERN, null );
+        isInRoleFilter = getProperty( props, PROPERTY_IS_IN_ROLE_FILTER, defaultIsInRoleFilter );
 
         // Optional security properties
         String parsedSsl = getProperty( props, PROPERTY_SSL, null );
@@ -239,11 +522,11 @@
         {
             authentication = parsedAuthentication;
         }
-        loginIdPattern = getProperty( props, PROPERTY_LOGIN_ID_PATTERN, "{0}" );
+        loginIdPattern = getProperty( props, PROPERTY_LOGIN_ID_PATTERN, defaultLoginIdPattern );
 
         // Optional user object attributes
-        userObjectClass = getProperty( props, PROPERTY_USER_OBJECT_CLASS, "inetOrgPerson" );
-        userLoginNameAttribute = getProperty( props, PROPERTY_USER_LOGIN_NAME_ATTRIBUTE, "uid" );
+        userObjectClass = getProperty( props, PROPERTY_USER_OBJECT_CLASS, defaultUserObjectClass );
+        userLoginNameAttribute = getProperty( props, PROPERTY_USER_LOGIN_NAME_ATTRIBUTE, defaultUserLoginNameAttribute );
 
         // Validate everything
         for( String property : requiredProperties )
@@ -254,10 +537,49 @@
             }
         }
     }
-    
-    public Hashtable<String,String> newJndiEnvironment() throws NamingException
+
+    public String getUserDn( String loginName ) throws NamingException
+    {
+        return getUserDn( loginName, null );
+    }
+
+    public synchronized String getUserDn( String loginName, DirContext ctx ) throws NamingException
     {
-        // If we need a Bind DN and Keychain is loaded, get the bind DN and password
+        String dn = m_userDns.get( loginName );
+        if( dn == null )
+        {
+            String userFinder = userFilter.replace( "{0}", escapeFilterString( loginName ) );
+            Hashtable<String, String> env = newJndiEnvironment();
+
+            // Find the user
+            if( ctx == null )
+            {
+                ctx = new InitialLdapContext( env, null );
+            }
+            NamingEnumeration<SearchResult> users = ctx.search( userBase, userFinder, SEARCH_CONTROLS );
+            if( users.hasMore() )
+            {
+                dn = users.next().getNameInNamespace();
+                m_userDns.put( loginName, dn );
+            }
+        }
+        return dn;
+    }
+
+    /**
+     * Builds a JNDI environment hashtable for performing an operation on the
+     * LDAP server. The hashtable is built using the properties used to
+     * initialize the LdapConfig object. If property {@link #PROPERTY_BIND_DN}
+     * was set, that DN will be used as the authentication principal. The
+     * password will be obtained from the Keychain.
+     * 
+     * @return the constructed hash table
+     * @throws NamingException
+     */
+    public Hashtable<String, String> newJndiEnvironment() throws NamingException
+    {
+        // If we need a Bind DN and Keychain is loaded, get the bind DN and
+        // password
         String username = bindDN;
         String password = null;
         if( username != null )
@@ -274,20 +596,19 @@
         }
         return newJndiEnvironment( username, password );
     }
-    
+
     /**
-     * Builds a JNDI environment hashtable for authenticating to the LDAP
-     * server. The hashtable is built using information extracted from the
-     * options map supplied to the LoginModule via
-     * {@link AbstractLoginModule#initialize(javax.security.auth.Subject, javax.security.auth.callback.CallbackHandler, Map, Map)}
-     * . The username and password parameters supply the LDAP credentials.
+     * Builds a JNDI environment hashtable for performing an operation on the
+     * LDAP server. The hashtable is built using the properties used to
+     * initialize the LdapConfig object. The username and password parameters
+     * supply the LDAP credentials used with the connection.
      * 
      * @param username the user's distinguished name (DN), used for
      *            authentication
      * @param password the password
      * @return the constructed hash table
      */
-    public Hashtable<String,String> newJndiEnvironment( String username, String password )
+    public Hashtable<String, String> newJndiEnvironment( String username, String password )
     {
         Hashtable<String, String> env = new Hashtable<String, String>();
         env.put( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory" );
@@ -296,14 +617,14 @@
         env.put( Context.PROVIDER_URL, connectionUrl );
 
         // Add credentials if supplied
-        if ( username != null )
+        if( username != null )
         {
             env.put( Context.SECURITY_PRINCIPAL, username );
             env.put( Context.SECURITY_CREDENTIALS, password );
         }
         else
         {
-            
+
         }
 
         // Use SSL?
@@ -312,71 +633,56 @@
         // Authentication type (simple, DIGEST-MD5, etc)
         env.put( Context.SECURITY_AUTHENTICATION, authentication );
 
-        return env;
-   }
-    
-    public static String getFullName( Attributes attributes ) throws NamingException
-    {
-        boolean hasCommonName = attributes.get( "cn" ) != null;
-        boolean hasSurname = attributes.get( "sn" ) != null;
-        boolean hasGivenName = attributes.get( "givenName" ) != null;
+        // Follow referrals
+        env.put( Context.REFERRAL, "follow" );
 
-        String fullName = null;
-        if( hasGivenName && hasSurname )
-        {
-            fullName = attributes.get( "givenName" ).get( 0 ) + " " + attributes.get( "sn" ).get( 0 );
-        }
-        else if( hasCommonName )
-        {
-            fullName = attributes.get( "cn" ).get( 0 ).toString();
-        }
-        else
-        {
-            throw new NamingException( "User did not have a givenName+sn or cn." );
-        }
-        return fullName;
+        return env;
     }
 
-    private final Map<String, String> m_userDns = new HashMap<String, String>();
-
-    private static final SearchControls SEARCH_CONTROLS;
-    
-    static
-    {
-        SEARCH_CONTROLS = new SearchControls();
-        SEARCH_CONTROLS.setSearchScope( SearchControls.SUBTREE_SCOPE );
-    }
-    
-    public String getUserDn( String loginName ) throws NamingException
+    /**
+     * Retrieves the password to be used with a bind DN.
+     * 
+     * @return the plaintext password
+     * @throws KeyStoreException if the Keychain was not supplied during
+     *             initialization, or if the lookup fails for any reason
+     */
+    private String getBindDNPassword() throws KeyStoreException
     {
-        String dn = m_userDns.get( loginName );
-        if( dn == null )
+        if( m_keychain == null )
         {
-            loginName = sanitizeDn( loginName );
-            String userFinder = userPattern.replace( "{0}", loginName );
-            Hashtable<String, String> env = newJndiEnvironment();
-
-            // Find the user
-            DirContext ctx = new InitialLdapContext( env, null );
-            NamingEnumeration<SearchResult> users = ctx.search( userBase, userFinder, SEARCH_CONTROLS );
-            if( users.hasMore() )
-            {
-                dn = users.next().getNameInNamespace();
-                m_userDns.put( loginName, dn );
-            }
+            throw new KeyStoreException( "LdapConfig was initialized without a keychain!" );
         }
-        return dn;
+        KeyStore.Entry password = m_keychain.getEntry( LdapConfig.KEYCHAIN_BIND_DN_ENTRY );
+        if( password instanceof Keychain.Password )
+        {
+            return ((Keychain.Password) password).getPassword();
+        }
+        return null;
     }
 
     /**
-     * See http://blogs.sun.com/shankar/entry/what_is_ldap_injection
+     * Looks up and returns an initialization property.
      * 
-     * @param index
-     * @return
+     * @param props the Map used to initialize the LdapConfig object
+     * @param property the property name to search for
+     * @param defaultValue the default value if the property is not found
+     * @return the property value if found, and the default if not
      */
-    private String sanitizeDn( String index )
+    private String getProperty( Map<? extends Object, ? extends Object> props, String property, String defaultValue )
     {
-        index = index.replace( " ", " " );
-        return index.replace( "=", "\\3D" );
+        String shortProperty = property;
+        String value = (String) props.get( property );
+        if( value != null && value.length() > 0 )
+        {
+            m_configured.add( shortProperty );
+            return props.get( property ).toString().trim();
+        }
+        
+        // Return the default
+        if ( defaultValue != null )
+        {
+            m_configured.add( shortProperty );
+        }
+        return defaultValue;
     }
 }

Modified: incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/authorize/GroupManager.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/authorize/GroupManager.java?rev=806031&r1=806030&r2=806031&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/authorize/GroupManager.java (original)
+++ incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/authorize/GroupManager.java Thu Aug 20 03:30:25 2009
@@ -105,6 +105,15 @@
     }
 
     /**
+     * Always throws a WikiSecurityException.
+     */
+    public Role[] findRoles( WikiSession session ) throws WikiSecurityException
+    {
+        // FIXME: at some point this should actually work.
+        throw new WikiSecurityException( "Not supported by this Authorizer." );
+    }
+
+    /**
      * <p>
      * Finds or creates a Group in the GroupDatabase. The Group will either be a
      * copy of an existing Group (if one can be found), or a new, unregistered

Modified: incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/authorize/LdapAuthorizer.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/authorize/LdapAuthorizer.java?rev=806031&r1=806030&r2=806031&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/authorize/LdapAuthorizer.java (original)
+++ incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/authorize/LdapAuthorizer.java Thu Aug 20 03:30:25 2009
@@ -18,14 +18,14 @@
 import org.apache.wiki.auth.Authorizer;
 import org.apache.wiki.auth.LdapConfig;
 import org.apache.wiki.auth.WikiSecurityException;
-import org.apache.wiki.auth.login.LdapLoginModule;
+import org.apache.wiki.auth.user.LdapUserDatabase;
 
 /**
  * <p>
- * Authorizer whose Roles are supplied by LDAP groups. This Authorizer requires
- * that {@link LdapLoginModule} be used for authentication. This can be done
- * either as part of the web container authentication configuration or (more
- * likely) as part of JSPWiki's own native authentication configuration.
+ * Authorizer whose Roles are supplied by LDAP groups. This Authorizer is often
+ * used in conjunction with {@link LdapUserDatabase} for authentication. This can
+ * be done either as part of the web container authentication configuration or
+ * (more likely) as part of JSPWiki's own native authentication configuration.
  * </p>
  * <p>
  * When {@link #initialize(WikiEngine, Properties)} executes, a new instance of
@@ -33,10 +33,9 @@
  * the settings in <code>jspwiki.properties</code>. The properties that are
  * required in order for LdapAuthorizer to function correctly are
  * {@link LdapConfig#PROPERTY_CONNECTION_URL},
- * {@link LdapConfig#PROPERTY_ROLE_BASE},
- * {@link LdapConfig#PROPERTY_ROLE_PATTERN} and
- * {@link LdapConfig#PROPERTY_IS_IN_ROLE_PATTERN}. Additional properties that
- * can be set include {@link LdapConfig#PROPERTY_BIND_DN},
+ * {@link LdapConfig#PROPERTY_ROLE_BASE} and
+ * {@link LdapConfig#PROPERTY_IS_IN_ROLE_FILTER}. Additional properties that can
+ * be set include {@link LdapConfig#PROPERTY_BIND_DN},
  * {@link LdapConfig#PROPERTY_AUTHENTICATION} and
  * {@link LdapConfig#PROPERTY_SSL}. See the documentation for that LdapConfig
  * for more details.
@@ -48,7 +47,22 @@
 
     private LdapConfig m_cfg = null;
 
-    private String m_allRoleFinder = null;
+    /**
+     * Finds all of the LDAP group objects in the role base with at least one
+     * member.
+     */
+    private String m_allRolesFilter = null;
+
+    /**
+     * Finds an LDAP group object matching a supplied role name.
+     */
+    private String m_roleFilter = null;
+
+    /**
+     * Finds all of the LDAP group objects that contain a supplied user as a
+     * member.
+     */
+    private String m_userRolesFilter = null;
 
     /**
      * {@inheritDoc}
@@ -58,9 +72,9 @@
         try
         {
             DirContext ctx = new InitialLdapContext( m_jndiEnv, null );
-            String roleFinder = m_cfg.rolePattern;
-            roleFinder = roleFinder.replace( "{0}", role );
-            NamingEnumeration<SearchResult> roles = ctx.search( m_cfg.roleBase, roleFinder, SEARCH_CONTROLS );
+            String filter = m_roleFilter;
+            filter = filter.replace( "{0}", LdapConfig.escapeFilterString( role ) );
+            NamingEnumeration<SearchResult> roles = ctx.search( m_cfg.roleBase, filter, SEARCH_CONTROLS );
             if( roles.hasMore() )
             {
                 return new Role( role );
@@ -82,7 +96,35 @@
         try
         {
             DirContext ctx = new InitialLdapContext( m_jndiEnv, null );
-            NamingEnumeration<SearchResult> roles = ctx.search( m_cfg.roleBase, m_allRoleFinder, SEARCH_CONTROLS );
+            NamingEnumeration<SearchResult> roles = ctx.search( m_cfg.roleBase, m_allRolesFilter, SEARCH_CONTROLS );
+            while ( roles.hasMore() )
+            {
+                SearchResult foundRole = roles.next();
+                String roleName = (String) foundRole.getAttributes().get( "cn" ).get( 0 );
+                foundRoles.add( new Role( roleName ) );
+            }
+        }
+        catch( NamingException e )
+        {
+            e.printStackTrace();
+        }
+        return foundRoles.toArray( new Role[foundRoles.size()] );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Role[] findRoles( WikiSession session ) throws WikiSecurityException
+    {
+        String loginName = session.getLoginPrincipal().getName();
+        Set<Role> foundRoles = new HashSet<Role>();
+        try
+        {
+            String dn = m_cfg.getUserDn( loginName );
+            dn = LdapConfig.escapeFilterString( dn );
+            String filter = m_userRolesFilter.replace( "{1}", dn );
+            DirContext ctx = new InitialLdapContext( m_jndiEnv, null );
+            NamingEnumeration<SearchResult> roles = ctx.search( m_cfg.roleBase, filter, SEARCH_CONTROLS );
             while ( roles.hasMore() )
             {
                 SearchResult foundRole = roles.next();
@@ -99,8 +141,7 @@
 
     private static final String[] REQUIRED_PROPERTIES = new String[] { LdapConfig.PROPERTY_CONNECTION_URL,
                                                                       LdapConfig.PROPERTY_ROLE_BASE,
-                                                                      LdapConfig.PROPERTY_ROLE_PATTERN,
-                                                                      LdapConfig.PROPERTY_IS_IN_ROLE_PATTERN };
+                                                                      LdapConfig.PROPERTY_IS_IN_ROLE_FILTER };
 
     private static final SearchControls SEARCH_CONTROLS;
 
@@ -116,7 +157,9 @@
     public void initialize( WikiEngine engine, Properties props ) throws WikiSecurityException
     {
         m_cfg = LdapConfig.getInstance( engine.getAuthenticationManager().getKeychain(), props, REQUIRED_PROPERTIES );
-        m_allRoleFinder = m_cfg.rolePattern.replace( "{0}", "*" );
+        m_roleFilter = m_cfg.isInRoleFilter.replace( "{1}", "*" );
+        m_userRolesFilter = m_cfg.isInRoleFilter.replace( "{0}", "*" );
+        m_allRolesFilter = m_userRolesFilter.replace( "{1}", "*" );
 
         // Do a quick connection test, and fail-fast if needed
         try
@@ -138,7 +181,7 @@
      * found in the role-base DN. The login Principal is assumed to be a valid
      * DN. The scope searched is provided by
      * {@link LdapConfig#PROPERTY_ROLE_BASE}, and the filter to match roles is
-     * provided by {@link LdapConfig#PROPERTY_IS_IN_ROLE_PATTERN}.
+     * provided by {@link LdapConfig#PROPERTY_IS_IN_ROLE_FILTER}.
      * </p>
      * <p>
      * For example, consider a WikiSession whose subject contains three user
@@ -156,7 +199,7 @@
      * In this case, the DN
      * <code>uid=biggie.smalls,ou=people,dc=jspwiki,dc=org</code> would be
      * examined for membership in an LDAP group whose common name matches
-     * <code>role</code>. Given an is-in-role pattern of
+     * <code>role</code>. Given an is-in-role filter of
      * <code>(&(objectClass=groupOfUniqueNames)(cn={0})(uniqueMember={1}))</code>
      * , an LDAP search would be constructed to find objects whose
      * <code>objectClass</code> was of type <code>groupOfUniqueNames</code> and
@@ -170,8 +213,10 @@
         try
         {
             String dn = m_cfg.getUserDn( loginName );
+            dn = LdapConfig.escapeFilterString( dn );
             DirContext ctx = new InitialLdapContext( m_jndiEnv, null );
-            String filter = m_cfg.isInRolePattern.replace( "{0}", role.getName() );
+            String roleName = LdapConfig.escapeFilterString( role.getName() );
+            String filter = m_cfg.isInRoleFilter.replace( "{0}", roleName );
             filter = filter.replace( "{1}", dn );
             NamingEnumeration<SearchResult> roles = ctx.search( m_cfg.roleBase, filter, SEARCH_CONTROLS );
             boolean isMember = roles.hasMore();

Modified: incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/authorize/WebContainerAuthorizer.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/authorize/WebContainerAuthorizer.java?rev=806031&r1=806030&r2=806031&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/authorize/WebContainerAuthorizer.java (original)
+++ incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/authorize/WebContainerAuthorizer.java Thu Aug 20 03:30:25 2009
@@ -23,23 +23,20 @@
 import java.io.IOException;
 import java.net.URL;
 import java.security.Principal;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Properties;
-import java.util.Set;
+import java.util.*;
 
 import javax.servlet.http.HttpServletRequest;
 
 import org.apache.wiki.InternalWikiException;
 import org.apache.wiki.WikiEngine;
 import org.apache.wiki.WikiSession;
+import org.apache.wiki.auth.WikiSecurityException;
 import org.apache.wiki.log.Logger;
 import org.apache.wiki.log.LoggerFactory;
 import org.jdom.Document;
 import org.jdom.Element;
-import org.jdom.Namespace;
 import org.jdom.JDOMException;
+import org.jdom.Namespace;
 import org.jdom.input.SAXBuilder;
 import org.jdom.xpath.XPath;
 import org.xml.sax.EntityResolver;
@@ -213,6 +210,15 @@
     }
 
     /**
+     * Always throws a WikiSecurityException
+     */
+    public Role[] findRoles( WikiSession session ) throws WikiSecurityException
+    {
+        // FIXME: at some point this should actually work.
+        throw new WikiSecurityException( "Not supported by this Authorizer." );
+    }
+
+    /**
      * <p>
      * Protected method that identifies whether a particular webapp URL is
      * constrained to a particular Role. The resource is considered constrained

Modified: incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/login/UserDatabaseLoginModule.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/login/UserDatabaseLoginModule.java?rev=806031&r1=806030&r2=806031&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/login/UserDatabaseLoginModule.java (original)
+++ incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/login/UserDatabaseLoginModule.java Thu Aug 20 03:30:25 2009
@@ -29,15 +29,12 @@
 import javax.security.auth.login.FailedLoginException;
 import javax.security.auth.login.LoginException;
 
-import org.apache.wiki.auth.NoSuchPrincipalException;
 import org.apache.wiki.auth.WikiPrincipal;
 import org.apache.wiki.auth.user.UserDatabase;
-import org.apache.wiki.auth.user.UserProfile;
 import org.apache.wiki.i18n.InternationalizationManager;
 import org.apache.wiki.log.Logger;
 import org.apache.wiki.log.LoggerFactory;
 
-
 /**
  * <p>
  * Logs in a user based on a username, password, and static password file
@@ -45,8 +42,7 @@
  * {@link WikiCallbackHandler}) that supports the following Callback types:
  * </p>
  * <ol>
- * <li>{@link javax.security.auth.callback.NameCallback}- supplies the
- * username</li>
+ * <li>{@link javax.security.auth.callback.NameCallback}- supplies the username</li>
  * <li>{@link javax.security.auth.callback.PasswordCallback}- supplies the
  * password</li>
  * <li>{@link org.apache.wiki.auth.login.UserDatabaseCallback}- supplies the
@@ -56,6 +52,7 @@
  * After authentication, a Principals based on the login name will be created
  * and associated with the Subject.
  * </p>
+ * 
  * @author Andrew Jaquith
  * @since 2.3
  */
@@ -63,20 +60,22 @@
 {
 
     private static final InternationalizationManager I18N = new InternationalizationManager( null );
-    
+
     private static final Logger log = LoggerFactory.getLogger( UserDatabaseLoginModule.class );
 
     /**
-     *      {@inheritDoc}
-     *      <p>Note: this method will throw a
-     *      {@link javax.security.auth.login.FailedLoginException} if the
-     *      username or password does not match what is contained in the
-     *      database. The text of this message will be looked up in the
-     *      {@link org.apache.wiki.i18n.InternationalizationManager#CORE_BUNDLE}
-     *      using the key <code>login.error.password</code>. Any other
-     *      Exceptions thrown by this method will <em>not</em> be localized,
-     *      because they represent exceptional error conditions that should not
-     *      occur unless the wiki is configured incorrectly.</p>
+     * {@inheritDoc}
+     * <p>
+     * Note: this method will throw a
+     * {@link javax.security.auth.login.FailedLoginException} if the username or
+     * password does not match what is contained in the database. The text of
+     * this message will be looked up in the
+     * {@link org.apache.wiki.i18n.InternationalizationManager#CORE_BUNDLE}
+     * using the key <code>login.error.password</code>. Any other Exceptions
+     * thrown by this method will <em>not</em> be localized, because they
+     * represent exceptional error conditions that should not occur unless the
+     * wiki is configured incorrectly.
+     * </p>
      */
     public boolean login() throws LoginException
     {
@@ -91,30 +90,20 @@
             String password = new String( pcb.getPassword() );
             UserDatabase db = ucb.getUserDatabase();
 
-            // Look up the user and compare the password hash
-            if ( db == null )
+            // Look up the user and check the password
+            if( db == null )
             {
                 throw new LoginException( "No user database: check the callback handler code!" );
             }
-            UserProfile profile;
-            try
-            {
-                profile = db.findByLoginName( username );
-            }
-            catch( NoSuchPrincipalException e )
-            {
-                throw new FailedLoginException( I18N.get( InternationalizationManager.CORE_BUNDLE, m_locale, "login.error.password" ) );
-            }
-            String storedPassword = profile.getPassword();
-            if ( storedPassword != null && db.validatePassword( username, password ) )
+            if( db.validatePassword( username, password ) )
             {
-                if ( log.isDebugEnabled() )
+                if( log.isDebugEnabled() )
                 {
                     log.debug( "Logged in user database user " + username );
                 }
 
                 // If login succeeds, commit these principals/roles
-                m_principals.add( new WikiPrincipal( username,  WikiPrincipal.LOGIN_NAME ) );
+                m_principals.add( new WikiPrincipal( username, WikiPrincipal.LOGIN_NAME ) );
 
                 return true;
             }

Modified: incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/user/LdapUserDatabase.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/user/LdapUserDatabase.java?rev=806031&r1=806030&r2=806031&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/user/LdapUserDatabase.java (original)
+++ incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/user/LdapUserDatabase.java Thu Aug 20 03:30:25 2009
@@ -13,11 +13,23 @@
 
 import org.apache.wiki.NoRequiredPropertyException;
 import org.apache.wiki.WikiEngine;
-import org.apache.wiki.auth.*;
+import org.apache.wiki.auth.LdapConfig;
+import org.apache.wiki.auth.NoSuchPrincipalException;
+import org.apache.wiki.auth.WikiPrincipal;
+import org.apache.wiki.auth.WikiSecurityException;
 import org.apache.wiki.util.TextUtil;
+import org.freshcookies.security.Keychain;
 
+/**
+ * Read-only implementation of UserDatabase that uses LDAP as the back-end user repository.
+ */
 public class LdapUserDatabase extends AbstractUserDatabase
 {
+    private LdapConfig m_cfg = null;
+
+    private static final String[] REQUIRED_PROPERTIES = new String[] { LdapConfig.PROPERTY_CONNECTION_URL,
+                                                                       LdapConfig.PROPERTY_USER_BASE };
+    
     /**
      * LdapUserDatabase does not support this operation.
      */
@@ -26,66 +38,42 @@
         throw new WikiSecurityException( "Operation not supported" );
     }
 
+    /**
+     * {@inheritDoc}
+     */
     public UserProfile findByEmail( String index ) throws NoSuchPrincipalException
     {
-        index = sanitize( index );
+        index = LdapConfig.escapeFilterString( index );
         return findLdapUser( "(&(objectClass=" + m_cfg.userObjectClass + ")(mail=" + index + "))" );
     }
-    
-    private UserProfile findLdapUser( String filter ) throws NoSuchPrincipalException
-    {
-        Hashtable<String, String> env = m_cfg.newJndiEnvironment( null, null );
-        try
-        {
-            DirContext ctx = new InitialLdapContext( env, null );
-            SearchControls searchControls = new SearchControls();
-            searchControls.setReturningAttributes( new String[] { m_cfg.userLoginNameAttribute, "cn", "givenName", "sn", "mail" } );
-            searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
-            NamingEnumeration<SearchResult> results = ctx.search( m_cfg.userBase, filter, searchControls );
-            if( results.hasMore() )
-            {
-                SearchResult user = results.next();
-                Attributes attributes = user.getAttributes();
-                Attribute loginName = attributes.get( m_cfg.userLoginNameAttribute );
-                Attribute cn = attributes.get( "cn" );
-                Attribute mail = attributes.get( "mail" );
-                if ( loginName == null || cn == null || mail == null )
-                {
-                    throw new NamingException( "Malformed directory entry; missing cn, mail or uid." );
-                }
-                UserProfile p = newProfile();
-                p.setUid( user.getNameInNamespace() );
-                p.setLoginName( loginName.get( 0 ).toString() );
-                p.setFullname( LdapConfig.getFullName( user.getAttributes() ) );
-                p.setEmail( mail.get( 0 ).toString() );
-                return p;
-            }
-        }
-        catch( NamingException e )
-        {
-            throw new NoSuchPrincipalException( "Could not find object: " + e.getMessage() );
-        }
-        throw new NoSuchPrincipalException( "Could not find object." );
-    }
 
+    /**
+     * {@inheritDoc}
+     */
     public UserProfile findByFullName( String index ) throws NoSuchPrincipalException
     {
-        index = sanitize( index );
+        index = LdapConfig.escapeFilterString( index );
         return findLdapUser( "(&(objectClass=" + m_cfg.userObjectClass + ")(cn=" + index + "))" );
     }
 
+    /**
+     * {@inheritDoc}
+     */
     public UserProfile findByLoginName( String index ) throws NoSuchPrincipalException
     {
-        index = sanitize( index );
+        index = LdapConfig.escapeFilterString( index );
         return findLdapUser( "(&(objectClass=" + m_cfg.userObjectClass + ")(" + m_cfg.userLoginNameAttribute + "=" + index + "))" );
     }
 
+    /**
+     * {@inheritDoc}
+     */
     public UserProfile findByUid( String uid ) throws NoSuchPrincipalException
     {
         String filter = "(objectClass=" + m_cfg.userObjectClass + ")";
-        Hashtable<String, String> env = m_cfg.newJndiEnvironment( null, null );
         try
         {
+            Hashtable<String, String> env = m_cfg.newJndiEnvironment();
             DirContext ctx = new InitialLdapContext( env, null );
             SearchControls searchControls = new SearchControls();
             searchControls.setReturningAttributes( new String[] { m_cfg.userLoginNameAttribute, "cn", "givenName", "sn", "mail" } );
@@ -117,26 +105,26 @@
         throw new NoSuchPrincipalException( "Could not find object." );
     }
 
+    /**
+     * {@inheritDoc}
+     */
     public UserProfile findByWikiName( String index ) throws NoSuchPrincipalException
     {
-        index = sanitize( index );
+        index = LdapConfig.escapeFilterString( index );
         index = TextUtil.beautifyString( index );
         return findLdapUser( "(&(objectClass=" + m_cfg.userObjectClass + ")(cn=" + index + "))" );
     }
-
-    private String sanitize( String index )
-    {
-        index = index.replace( " ", " " );
-        return index.replace( "=", "\\3D" );
-    }
     
+    /**
+     * {@inheritDoc}
+     */
     public Principal[] getWikiNames() throws WikiSecurityException
     {
         String filter = "(objectClass=" + m_cfg.userObjectClass + ")";
-        Hashtable<String, String> env = m_cfg.newJndiEnvironment( null, null );
         Set<Principal> principals = new HashSet<Principal>();
         try
         {
+            Hashtable<String, String> env = m_cfg.newJndiEnvironment();
             DirContext ctx = new InitialLdapContext( env, null );
             SearchControls searchControls = new SearchControls();
             searchControls.setReturningAttributes( new String[]{ "cn", "givenName", "sn" } );
@@ -156,14 +144,13 @@
         return principals.toArray(new Principal[principals.size()]);
     }
 
-    private LdapConfig m_cfg = null;
-
-    private static final String[] REQUIRED_PROPERTIES = new String[] { LdapConfig.PROPERTY_CONNECTION_URL,
-                                                                       LdapConfig.PROPERTY_USER_BASE };
-
+    /**
+     * {@inheritDoc}
+     */
     public void initialize( WikiEngine engine, Properties props ) throws NoRequiredPropertyException
     {
-        m_cfg = LdapConfig.getInstance( null, props, REQUIRED_PROPERTIES );
+        Keychain keychain = engine.getAuthenticationManager().getKeychain();
+        m_cfg = LdapConfig.getInstance( keychain, props, REQUIRED_PROPERTIES );
     }
 
     /**
@@ -211,4 +198,39 @@
         return false;
     }
 
+    private UserProfile findLdapUser( String filter ) throws NoSuchPrincipalException
+    {
+        try
+        {
+            Hashtable<String, String> env = m_cfg.newJndiEnvironment();
+            DirContext ctx = new InitialLdapContext( env, null );
+            SearchControls searchControls = new SearchControls();
+            searchControls.setReturningAttributes( new String[] { m_cfg.userLoginNameAttribute, "cn", "givenName", "sn", "mail" } );
+            searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
+            NamingEnumeration<SearchResult> results = ctx.search( m_cfg.userBase, filter, searchControls );
+            if( results.hasMore() )
+            {
+                SearchResult user = results.next();
+                Attributes attributes = user.getAttributes();
+                Attribute loginName = attributes.get( m_cfg.userLoginNameAttribute );
+                Attribute cn = attributes.get( "cn" );
+                Attribute mail = attributes.get( "mail" );
+                if ( loginName == null || cn == null || mail == null )
+                {
+                    throw new NamingException( "Malformed directory entry; missing cn, mail or uid." );
+                }
+                UserProfile p = newProfile();
+                p.setUid( user.getNameInNamespace() );
+                p.setLoginName( loginName.get( 0 ).toString() );
+                p.setFullname( LdapConfig.getFullName( user.getAttributes() ) );
+                p.setEmail( mail.get( 0 ).toString() );
+                return p;
+            }
+        }
+        catch( NamingException e )
+        {
+            throw new NoSuchPrincipalException( "Could not find object: " + e.getMessage() );
+        }
+        throw new NoSuchPrincipalException( "Could not find object." );
+    }
 }

Modified: incubator/jspwiki/trunk/tests/java/org/apache/wiki/TestAuthorizer.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/tests/java/org/apache/wiki/TestAuthorizer.java?rev=806031&r1=806030&r2=806031&view=diff
==============================================================================
--- incubator/jspwiki/trunk/tests/java/org/apache/wiki/TestAuthorizer.java (original)
+++ incubator/jspwiki/trunk/tests/java/org/apache/wiki/TestAuthorizer.java Thu Aug 20 03:30:25 2009
@@ -25,8 +25,7 @@
 
 import javax.servlet.http.HttpServletRequest;
 
-import org.apache.wiki.WikiEngine;
-import org.apache.wiki.WikiSession;
+import org.apache.wiki.auth.WikiSecurityException;
 import org.apache.wiki.auth.authorize.Role;
 import org.apache.wiki.auth.authorize.WebAuthorizer;
 
@@ -58,6 +57,11 @@
         return null;
     }
 
+    public Role[] findRoles( WikiSession session ) throws WikiSecurityException
+    {
+        throw new WikiSecurityException( "Not supported by this Authorizer." );
+    }
+
     public void initialize( WikiEngine engine, Properties props )
     {
     }

Modified: incubator/jspwiki/trunk/tests/java/org/apache/wiki/TestEngine.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/tests/java/org/apache/wiki/TestEngine.java?rev=806031&r1=806030&r2=806031&view=diff
==============================================================================
--- incubator/jspwiki/trunk/tests/java/org/apache/wiki/TestEngine.java (original)
+++ incubator/jspwiki/trunk/tests/java/org/apache/wiki/TestEngine.java Thu Aug 20 03:30:25 2009
@@ -42,8 +42,10 @@
 import org.apache.wiki.api.WikiException;
 import org.apache.wiki.api.WikiPage;
 import org.apache.wiki.attachment.Attachment;
-import org.apache.wiki.auth.*;
-import org.apache.wiki.auth.user.XMLUserDatabase;
+import org.apache.wiki.auth.AuthenticationManager;
+import org.apache.wiki.auth.SessionMonitor;
+import org.apache.wiki.auth.Users;
+import org.apache.wiki.auth.WikiSecurityException;
 import org.apache.wiki.content.PageAlreadyExistsException;
 import org.apache.wiki.content.PageNotFoundException;
 import org.apache.wiki.content.WikiPath;
@@ -52,6 +54,7 @@
 import org.apache.wiki.log.LoggerFactory;
 import org.apache.wiki.providers.AbstractFileProvider;
 import org.apache.wiki.providers.ProviderException;
+import org.apache.wiki.tags.SpamProtectTag;
 import org.apache.wiki.ui.WikiServletFilter;
 import org.apache.wiki.util.FileUtil;
 import org.apache.wiki.util.TextUtil;
@@ -416,8 +419,6 @@
     {
         props.put( AuthenticationManager.PROP_LOGIN_THROTTLING, "false" );
         props.put( WikiEngine.PROP_URLCONSTRUCTOR, "org.apache.wiki.url.DefaultURLConstructor" );
-        props.put( UserManager.PROP_DATABASE, "org.apache.wiki.auth.user.XMLUserDatabase" );
-        props.put(XMLUserDatabase.PROP_USERDATABASE, "tests/etc/userdatabase.xml");
         return props;
     }
 

Modified: incubator/jspwiki/trunk/tests/java/org/apache/wiki/auth/AuthenticationManagerTest.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/tests/java/org/apache/wiki/auth/AuthenticationManagerTest.java?rev=806031&r1=806030&r2=806031&view=diff
==============================================================================
--- incubator/jspwiki/trunk/tests/java/org/apache/wiki/auth/AuthenticationManagerTest.java (original)
+++ incubator/jspwiki/trunk/tests/java/org/apache/wiki/auth/AuthenticationManagerTest.java Thu Aug 20 03:30:25 2009
@@ -69,6 +69,11 @@
             return null;
         }
 
+        public Role[] findRoles( WikiSession session ) throws WikiSecurityException
+        {
+            throw new WikiSecurityException( "Not supported by this Authorizer." );
+        }
+        
         public Principal[] getRoles()
         {
             return m_roles;