yoavs 2004/09/21 16:29:33
Modified: catalina/src/share/org/apache/catalina/realm
JAASCallbackHandler.java JAASRealm.java
LocalStrings.properties
webapps/docs changelog.xml
Log:
Bugzilla 28631: JAASRealm enhancements to allow custom user and group principal
classes (and use commons-logging)
Revision Changes Path
1.5 +35 -14
jakarta-tomcat-catalina/catalina/src/share/org/apache/catalina/realm/JAASCallbackHandler.java
Index: JAASCallbackHandler.java
===================================================================
RCS file:
/home/cvs/jakarta-tomcat-catalina/catalina/src/share/org/apache/catalina/realm/JAASCallbackHandler.java,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -r1.4 -r1.5
--- JAASCallbackHandler.java 23 Jun 2004 13:51:37 -0000 1.4
+++ JAASCallbackHandler.java 21 Sep 2004 23:29:33 -0000 1.5
@@ -25,25 +25,38 @@
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
+import org.apache.catalina.util.StringManager;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
/**
- * <p>Implementation of the JAAS <strong>CallbackHandler</code> interface,
+ * <p>Implementation of the JAAS <code>CallbackHandler</code> interface,
* used to negotiate delivery of the username and credentials that were
* specified to our constructor. No interaction with the user is required
* (or possible).</p>
*
+ * <p>This <code>CallbackHandler</code> will pre-digest the supplied
+ * password, if required by the <code><Realm></code> element in
+ * <code>server.xml</code>.</p>
+ * <p>At present, <code>JAASCallbackHandler</code> knows how to handle callbacks of
+ * type <code>javax.security.auth.callback.NameCallback</code> and
+ * <code>javax.security.auth.callback.PasswordCallback</code>.</p>
+ *
* @author Craig R. McClanahan
+ * @author Andrew R. Jaquith
* @version $Revision$ $Date$
*/
public class JAASCallbackHandler implements CallbackHandler {
-
+ private static Log log = LogFactory.getLog(JAASCallbackHandler.class);
// ------------------------------------------------------------ Constructor
/**
* Construct a callback handler configured with the specified values.
+ * Note that if the <code>JAASRealm</code> instance specifies digested
passwords,
+ * the <code>password</code> parameter will be pre-digested here.
*
* @param realm Our associated JAASRealm instance
* @param username Username to be authenticated with
@@ -55,13 +68,26 @@
super();
this.realm = realm;
this.username = username;
- this.password = password;
+ if (realm.hasMessageDigest()) {
+ this.password = realm.digest(password);
+ if (log.isDebugEnabled()) {
+ log.debug(sm.getString("jaasCallback.digestpassword", password,
this.password));
+ }
+ }
+ else {
+ this.password = password;
+ }
}
// ----------------------------------------------------- Instance Variables
+ /**
+ * The string manager for this package.
+ */
+ protected static final StringManager sm =
+ StringManager.getManager(Constants.Package);
/**
* The password to be authenticated with.
@@ -85,11 +111,11 @@
/**
- * Retrieve the information requested in the provided Callbacks. This
- * implementation only recognizes <code>NameCallback</code> and
+ * Retrieve the information requested in the provided <code>Callbacks</code>.
+ * This implementation only recognizes <code>NameCallback</code> and
* <code>PasswordCallback</code> instances.
*
- * @param callbacks The set of callbacks to be processed
+ * @param callbacks The set of <code>Callback</code>s to be processed
*
* @exception IOException if an input/output error occurs
* @exception UnsupportedCallbackException if the login method requests
@@ -102,11 +128,11 @@
if (callbacks[i] instanceof NameCallback) {
if (realm.getContainer().getLogger().isTraceEnabled())
- realm.getContainer().getLogger().trace("Returning username " +
username);
+
realm.getContainer().getLogger().trace(sm.getString("jaasCallback.username",
username));
((NameCallback) callbacks[i]).setName(username);
} else if (callbacks[i] instanceof PasswordCallback) {
if (realm.getContainer().getLogger().isTraceEnabled())
- realm.getContainer().getLogger().trace("Returning password " +
password);
+
realm.getContainer().getLogger().trace(sm.getString("jaasCallback.password",
password));
final char[] passwordcontents;
if (password != null) {
passwordcontents = password.toCharArray();
@@ -118,11 +144,6 @@
} else {
throw new UnsupportedCallbackException(callbacks[i]);
}
-
-
}
-
}
-
-
}
1.9 +177 -58
jakarta-tomcat-catalina/catalina/src/share/org/apache/catalina/realm/JAASRealm.java
Index: JAASRealm.java
===================================================================
RCS file:
/home/cvs/jakarta-tomcat-catalina/catalina/src/share/org/apache/catalina/realm/JAASRealm.java,v
retrieving revision 1.8
retrieving revision 1.9
diff -u -r1.8 -r1.9
--- JAASRealm.java 20 Sep 2004 15:57:55 -0000 1.8
+++ JAASRealm.java 21 Sep 2004 23:29:33 -0000 1.9
@@ -22,7 +22,10 @@
import java.security.acl.Group;
import java.util.ArrayList;
import java.util.Enumeration;
+import java.util.HashMap;
import java.util.Iterator;
+import java.util.Map;
+import java.util.List;
import javax.security.auth.Subject;
import javax.security.auth.login.AccountExpiredException;
@@ -62,7 +65,7 @@
* this Realm:</p>
* <ul>
* <li>The JAAS <code>LoginModule</code> is assumed to return a
- * <code>Subject with at least one <code>Principal</code> instance
+ * <code>Subject</code> with at least one <code>Principal</code> instance
* representing the user himself or herself, and zero or more separate
* <code>Principals</code> representing the security roles authorized
* for this user.</li>
@@ -87,10 +90,39 @@
* <li>It is a configuration error for the JAAS login method to return a
* validated <code>Subject</code> without a <code>Principal</code> that
* matches the "user classes" list.</li>
- * </ul>
- *
- * @author Craig R. McClanahan
- * @author Yoav Shapira
+ * <li>By default, the enclosing Container's name serves as the
+ * application name used to obtain the JAAS LoginContext ("Catalina" in
+ * a default installation). Tomcat must be able to find an application
+ * with this name in the JAAS configuration file. Here is a hypothetical
+ * JAAS configuration file entry for a database-oriented login module that uses
+ * a Tomcat-managed JNDI database resource:
+ * <blockquote><pre>Catalina {
+org.foobar.auth.DatabaseLoginModule REQUIRED
+ JNDI_RESOURCE=jdbc/AuthDB
+ USER_TABLE=users
+ USER_ID_COLUMN=id
+ USER_NAME_COLUMN=name
+ USER_CREDENTIAL_COLUMN=password
+ ROLE_TABLE=roles
+ ROLE_NAME_COLUMN=name
+ PRINCIPAL_FACTORY=org.foobar.auth.impl.SimplePrincipalFactory;
+};</pre></blockquote></li>
+ * <li>To set the JAAS configuration file
+ * location, set the <code>CATALINA_OPTS</code> environment variable
+ * similar to the following:
+<blockquote><code>CATALINA_OPTS="-Djava.security.auth.login.config=$CATALINA_HOME/conf/jaas.config"</code></blockquote>
+ * </li>
+ * <li>As part of the login process, JAASRealm registers its own
<code>CallbackHandler</code>,
+ * called (unsurprisingly) <code>JAASCallbackHandler</code>. This handler
supplies the
+ * HTTP requests's username and credentials to the user-supplied
<code>LoginModule</code></li>
+ * <li>As with other <code>Realm</code> implementations, digested passwords are
supported if
+ * the <code><Realm></code> element in <code>server.xml</code> contains a
+ * <code>digest</code> attribute; <code>JAASCallbackHandler</code> will digest
the password
+ * prior to passing it back to the <code>LoginModule</code></li>
+* </ul>
+*
+* @author Craig R. McClanahan
+* @author Yoav Shapira
* @version $Revision$ $Date$
*/
@@ -104,20 +136,20 @@
/**
* The application name passed to the JAAS <code>LoginContext</code>,
- * which uses it to select the set of relevant <code>LoginModules</code>.
+ * which uses it to select the set of relevant <code>LoginModule</code>s.
*/
protected String appName = null;
/**
- * Descriptive information about this Realm implementation.
+ * Descriptive information about this <code>Realm</code> implementation.
*/
protected static final String info =
"org.apache.catalina.realm.JAASRealm/1.0";
/**
- * Descriptive information about this Realm implementation.
+ * Descriptive information about this <code>Realm</code> implementation.
*/
protected static final String name = "JAASRealm";
@@ -125,7 +157,7 @@
/**
* The list of role class names, split out for easy processing.
*/
- protected ArrayList roleClasses = new ArrayList();
+ protected List roleClasses = new ArrayList();
/**
@@ -138,7 +170,14 @@
/**
* The set of user class names, split out for easy processing.
*/
- protected ArrayList userClasses = new ArrayList();
+ protected List userClasses = new ArrayList();
+
+ /**
+ * Map associating each user <code>Principal</code> object
+ * with an array of role <code>Principal</code>s.
+ * This Map is read when <code>hasRole</code> is called.
+ */
+ protected Map roleMap = new HashMap();
/**
* Whether to use context ClassLoader or default ClassLoader.
@@ -152,15 +191,15 @@
/**
- * setter for the appName member variable
- * @deprecated JAAS should use the Engine ( domain ) name and webpp/host
overrides
+ * setter for the <code>appName</code> member variable
+ * @deprecated JAAS should use the <code>Engine</code> (domain) name and
webpp/host overrides
*/
public void setAppName(String name) {
appName = name;
}
/**
- * getter for the appName member variable
+ * getter for the <code>appName</code> member variable
*/
public String getAppName() {
return appName;
@@ -201,7 +240,7 @@
}
/**
- * Comma-delimited list of <code>javax.security.Principal</code> classes
+ * Comma-delimited list of <code>java.security.Principal</code> classes
* that represent security roles.
*/
protected String roleClassNames = null;
@@ -210,8 +249,16 @@
return (this.roleClassNames);
}
- public void setRoleClassNames(String roleClassNames) {
- this.roleClassNames = roleClassNames;
+ /**
+ * Sets the list of comma-delimited classes that represent
+ * roles. The classes in the list must implement
<code>java.security.Principal</code>.
+ * When this accessor is called (for example, by a <code>Digester</code>
+ * instance parsing the
+ * configuration file), it will parse the class names and store the resulting
+ * string(s) into the <code>ArrayList</code> field </code>roleClasses</code>.
+ */
+ public void setRoleClassNames(String roleClassNames) {
+ this.roleClassNames = roleClassNames;
roleClasses.clear();
String temp = this.roleClassNames;
if (temp == null) {
@@ -233,7 +280,7 @@
/**
- * Comma-delimited list of <code>javax.security.Principal</code> classes
+ * Comma-delimited list of <code>java.security.Principal</code> classes
* that represent individual users.
*/
protected String userClassNames = null;
@@ -242,6 +289,14 @@
return (this.userClassNames);
}
+ /**
+ * Sets the list of comma-delimited classes that represent individual
+ * users. The classes in the list must implement
<code>java.security.Principal</code>.
+ * When this accessor is called (for example, by a <code>Digester</code>
+ * instance parsing the
+ * configuration file), it will parse the class names and store the resulting
+ * string(s) into the <code>ArrayList</code> field </code>userClasses</code>.
+ */
public void setUserClassNames(String userClassNames) {
this.userClassNames = userClassNames;
userClasses.clear();
@@ -268,7 +323,7 @@
/**
- * Return the Principal associated with the specified username and
+ * Return the <code>Principal</code> associated with the specified username and
* credentials, if there is one; otherwise return <code>null</code>.
*
* If there are any errors with the JDBC connection, executing
@@ -276,7 +331,7 @@
* event is also logged, and the connection will be closed so that
* a subsequent request will automatically re-open it.
*
- * @param username Username of the Principal to look up
+ * @param username Username of the <code>Principal</code> to look up
* @param credentials Password or other credentials to use in
* authenticating this username
*/
@@ -288,7 +343,7 @@
if( appName==null ) appName="Tomcat";
if( log.isDebugEnabled())
- log.debug("Authenticating " + appName + " " + username);
+ log.debug(sm.getString("jaasRealm.beginLogin", username, appName));
// What if the LoginModule is in the container class loader ?
ClassLoader ocl = null;
@@ -345,7 +400,7 @@
}
if( log.isDebugEnabled())
- log.debug("Getting principal " + subject);
+ log.debug(sm.getString("jaasRealm.loginContextCreated", username));
// Return the appropriate Principal for this authenticated Subject
Principal principal = createPrincipal(username, subject);
@@ -363,7 +418,53 @@
return null;
}
}
-
+
+ /**
+ * Returns <code>true</code> if the specified user <code>Principal</code> has
the specified
+ * security role, within the context of this <code>Realm</code>; otherwise
return
+ * <code>false</code>. This will be true when
+ * an associated role <code>Principal</code> can be found whose
<code>getName</code>
+ * method returns a <code>String</code> equalling the specified role.
+ * @param principal <code>Principal</code> for whom the role is to be checked
+ * @param role Security role to be checked
+ */
+ public boolean hasRole(Principal principal, String role) {
+ if (log.isDebugEnabled()) {
+ log.debug(sm.getString("jaasRealm.isInRole.start",
principal.getName(), role));
+ }
+
+ if ((principal == null) || (role == null) ||
+ (roleMap.get(principal) == null)) {
+ if (log.isDebugEnabled()) {
+ log.debug(sm.getString("jaasRealm.isInRole.noPrincipalOrRole"));
+ }
+ return false;
+ }
+
+ List roles = (List)roleMap.get(principal);
+ if (log.isDebugEnabled()) {
+ log.debug(sm.getString("jaasRealm.isInRole.principalCached",
String.valueOf(roles.size())));
+ }
+
+ for (Iterator it = roles.iterator(); it.hasNext();) {
+ Principal possessedRole = (Principal)it.next();
+ String possessedRoleName = possessedRole.getName();
+ if (log.isDebugEnabled()) {
+ log.debug(sm.getString("jaasRealm.isInRole.possessesRole",
possessedRole.getName()));
+ }
+
+ if (possessedRoleName.equals(role)) {
+ if (log.isDebugEnabled()) {
+ log.debug(sm.getString("jaasRealm.isInRole.match"));
+ }
+ return true;
+ }
+ }
+ if (log.isDebugEnabled()) {
+ log.debug(sm.getString("jaasRealm.isInRole.noMatch"));
+ }
+ return false;
+ }
// -------------------------------------------------------- Package Methods
@@ -372,7 +473,7 @@
/**
- * Return a short name for this Realm implementation.
+ * Return a short name for this <code>Realm</code> implementation.
*/
protected String getName() {
@@ -392,7 +493,7 @@
/**
- * Return the Principal associated with the given user name.
+ * Return the <code>Principal</code> associated with the given user name.
*/
protected Principal getPrincipal(String username) {
@@ -402,58 +503,76 @@
/**
- * Construct and return a <code>java.security.Principal</code> instance
- * representing the authenticated user for the specified Subject. If no
- * such Principal can be constructed, return <code>null</code>.
- *
- * @param subject The Subject representing the logged in user
+ * Identify and return a <code>java.security.Principal</code> instance
+ * representing the authenticated user for the specified <code>Subject</code>.
+ * The Principal is constructed by scanning the list of Principals returned
+ * by the JAASLoginModule. The first <code>Principal</code> object that matches
+ * one of the class names supplied as a "user class" is the user Principal.
+ * This object is returned to tha caller.
+ * Any remaining principal objects returned by the LoginModules are mapped to
+ * roles, but only if their respective classes match one of the "role class"
classes.
+ * If a user Principal cannot be constructed, return <code>null</code>.
+ * @param subject The <code>Subject</code> representing the logged-in user
*/
protected Principal createPrincipal(String username, Subject subject) {
// Prepare to scan the Principals for this Subject
String password = null; // Will not be carried forward
- ArrayList roles = new ArrayList();
+
+ List roles = new ArrayList();
+ Principal userPrincipal = null;
// Scan the Principals for this Subject
Iterator principals = subject.getPrincipals().iterator();
while (principals.hasNext()) {
Principal principal = (Principal) principals.next();
- // No need to look further - that's our own stuff
- if( principal instanceof GenericPrincipal ) {
- if( log.isDebugEnabled() )
- log.debug("Found old GenericPrincipal " + principal );
- return principal;
- }
+
String principalClass = principal.getClass().getName();
- if( log.isDebugEnabled() )
- log.info("Principal: " + principalClass + " " + principal);
- if (userClasses.contains(principalClass)) {
- // Override the default - which is the original user, accepted by
- // the friendly LoginManager
- username = principal.getName();
+ if( log.isDebugEnabled() ) {
+ log.debug(sm.getString("jaasRealm.checkPrincipal", principal,
principalClass));
+ }
+
+ if (userPrincipal == null && userClasses.contains(principalClass)) {
+ userPrincipal = principal;
+ if( log.isDebugEnabled() ) {
+ log.debug(sm.getString("jaasRealm.userPrincipalSuccess",
principal.getName()));
+ }
}
+
if (roleClasses.contains(principalClass)) {
roles.add(principal.getName());
}
- // Same as Jboss - that's a pretty clean solution
- if( (principal instanceof Group) &&
- "Roles".equals( principal.getName())) {
- Group grp=(Group)principal;
- Enumeration en=grp.members();
- while( en.hasMoreElements() ) {
- Principal roleP=(Principal)en.nextElement();
- roles.add( roleP.getName());
- }
+ if (roleClasses.contains(principalClass)) {
+ roles.add(principal);
+ if( log.isDebugEnabled() ) {
+ log.debug(sm.getString("jaasRealm.rolePrincipalAdd",
principal.getName()));
+ }
}
}
- // Create the resulting Principal for our authenticated user
- if (username != null) {
- return (new GenericPrincipal(this, username, password, roles));
+ // Print failure message if needed
+ if (userPrincipal == null) {
+ if (log.isDebugEnabled()) {
+ log.debug(sm.getString("jaasRealm.userPrincipalFailure"));
+ log.debug(sm.getString("jaasRealm.rolePrincipalFailure"));
+ }
} else {
- return (null);
+ if (roles.size() == 0) {
+ if (log.isDebugEnabled()) {
+ log.debug(sm.getString("jaasRealm.rolePrincipalFailure"));
+ }
+ } else {
+ roleMap.put(userPrincipal, roles);
+ if (log.isDebugEnabled()) {
+ log.debug(sm.getString("jaasRealm.rolePrincipalSuccess",
String.valueOf(roles.size())));
+ log.debug(sm.getString("jaasRealm.cachePrincipal",
userPrincipal.getName(), String.valueOf(roles.size())));
+ }
+ }
}
+
+ // Return the resulting Principal for our authenticated user
+ return userPrincipal;
}
/**
@@ -488,7 +607,7 @@
/**
*
- * Prepare for active use of the public methods of this Component.
+ * Prepare for active use of the public methods of this <code>Component</code>.
*
* @exception LifecycleException if this component detects a fatal error
* that prevents it from being started
@@ -502,7 +621,7 @@
/**
- * Gracefully shut down active use of the public methods of this Component.
+ * Gracefully shut down active use of the public methods of this
<code>Component</code>.
*
* @exception LifecycleException if this component detects a fatal error
* that needs to be reported
1.7 +27 -6
jakarta-tomcat-catalina/catalina/src/share/org/apache/catalina/realm/LocalStrings.properties
Index: LocalStrings.properties
===================================================================
RCS file:
/home/cvs/jakarta-tomcat-catalina/catalina/src/share/org/apache/catalina/realm/LocalStrings.properties,v
retrieving revision 1.6
retrieving revision 1.7
diff -u -r1.6 -r1.7
--- LocalStrings.properties 3 Jul 2004 04:16:41 -0000 1.6
+++ LocalStrings.properties 21 Sep 2004 23:29:33 -0000 1.7
@@ -4,12 +4,33 @@
# package org.apache.catalina.realm
-jaasRealm.accountExpired=Username {0} NOT authenticated due to expired account
-jaasRealm.authenticateSuccess=Username {0} successfully authenticated
-jaasRealm.credentialExpired=Username {0} NOT authenticated due to expired credential
-jaasRealm.failedLogin=Username {0} NOT authenticated due to failed login
-jaasRealm.loginException=Login exception authenticating username {0}
+jaasRealm.beginLogin=JAASRealm login requested for username "{0}" using
LoginContext for application "{1}"
+jaasRealm.accountExpired=Username "{0}" NOT authenticated due to expired account
+jaasRealm.authenticateFailure=Username "{0}" NOT successfully authenticated
+jaasRealm.authenticateSuccess=Username "{0}" successfully authenticated as
Principal "{1}" -- Subject was created too
+jaasRealm.credentialExpired=Username "{0}" NOT authenticated due to expired
credential
+jaasRealm.failedLogin=Username "{0}" NOT authenticated due to failed login
+jaasRealm.loginException=Login exception authenticating username "{0}"
jaasRealm.unexpectedError=Unexpected error
+jaasRealm.loginContextCreated=JAAS LoginContext created for username "{0}"
+jaasRealm.userPrincipalSuccess=Subject for username "{0}" returned user Principal
"{1}"
+jaasRealm.userPrincipalFailure=Subject for username "{0}" did not return a valid
user Principal
+jaasRealm.cachePrincipal=User Principal "{0}" cached; has {1} role Principal(s)
+jaasRealm.checkPrincipal=Checking Principal "{0}" [{1}]
+jaasRealm.userPrincipalSuccess=Principal "{0}" is a valid user class. We will use
this as the user Principal.
+jaasRealm.userPrincipalFailure=No valid user Principal found
+jaasRealm.rolePrincipalAdd=Adding role Principal "{0}" to this user Principal\'s
roles
+jaasRealm.rolePrincipalSuccess={0} role Principal(s) found
+jaasRealm.rolePrincipalFailure=No valid role Principals found.
+jaasRealm.isInRole.start=Checking if user Principal "{0}" possesses role "{1}"
+jaasRealm.isInRole.noPrincipalOrRole=No roles Principals found. User Principal or
Subject is null, or user Principal not in cache
+jaasRealm.isInRole.principalCached=User Principal has {0} role Principal(s)
+jaasRealm.isInRole.possessesRole=User Principal has a role Principal called "{0}"
+jaasRealm.isInRole.match=Matching role Principal found.
+jaasRealm.isInRole.noMatch=Matching role Principal NOT found.
+jaasCallback.digestpassword=Digested password "{0}" as "{1}"
+jaasCallback.username=Returned username "{0}"
+jaasCallback.password=Returned password "{0}"
jdbcRealm.authenticateFailure=Username {0} NOT successfully authenticated
jdbcRealm.authenticateSuccess=Username {0} successfully authenticated
jdbcRealm.close=Exception closing database connection
1.114 +3 -0 jakarta-tomcat-catalina/webapps/docs/changelog.xml
Index: changelog.xml
===================================================================
RCS file: /home/cvs/jakarta-tomcat-catalina/webapps/docs/changelog.xml,v
retrieving revision 1.113
retrieving revision 1.114
diff -u -r1.113 -r1.114
--- changelog.xml 21 Sep 2004 19:36:11 -0000 1.113
+++ changelog.xml 21 Sep 2004 23:29:33 -0000 1.114
@@ -48,6 +48,9 @@
<fix>
<bug>31277</bug>: Clarified automatic application deployment section of
Host configuration page. (yoavs)
</fix>
+ <fix>
+ <bug>28631</bug>: JAASRealm enhancements to support custom user, role class
names, use Commons-Logging. (yoavs)
+ </fix>
</changelog>
</subsection>
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]