Author: markt Date: Wed Sep 3 15:18:39 2008 New Revision: 691805 URL: http://svn.apache.org/viewvc?rev=691805&view=rev Log: Add a new combined Realm that can be used to try authenticating against multiple realms.
Added: tomcat/trunk/java/org/apache/catalina/realm/CombinedRealm.java (with props) tomcat/trunk/java/org/apache/catalina/startup/RealmRuleSet.java (with props) Modified: tomcat/trunk/java/org/apache/catalina/realm/LocalStrings.properties tomcat/trunk/java/org/apache/catalina/startup/ContextRuleSet.java tomcat/trunk/java/org/apache/catalina/startup/EngineRuleSet.java tomcat/trunk/java/org/apache/catalina/startup/HostRuleSet.java tomcat/trunk/webapps/docs/config/realm.xml tomcat/trunk/webapps/docs/realm-howto.xml Added: tomcat/trunk/java/org/apache/catalina/realm/CombinedRealm.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/realm/CombinedRealm.java?rev=691805&view=auto ============================================================================== --- tomcat/trunk/java/org/apache/catalina/realm/CombinedRealm.java (added) +++ tomcat/trunk/java/org/apache/catalina/realm/CombinedRealm.java Wed Sep 3 15:18:39 2008 @@ -0,0 +1,298 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.catalina.realm; + +import java.security.Principal; + +import java.security.cert.X509Certificate; +import java.util.LinkedList; +import java.util.List; + +import org.apache.catalina.Container; +import org.apache.catalina.Lifecycle; +import org.apache.catalina.LifecycleException; +import org.apache.catalina.Realm; +import org.apache.catalina.util.StringManager; +import org.apache.juli.logging.Log; +import org.apache.juli.logging.LogFactory; + +/** + * Realm implementation that contains one or more realms. Authentication is + * attempted for each realm in the order they were configured. If any realm + * authenticates the user then the authentication succeeds. + */ +public class CombinedRealm extends RealmBase { + + private static Log log = LogFactory.getLog(RealmBase.class); + + /** + * The string manager for this package. + */ + protected static StringManager sm = + StringManager.getManager(Constants.Package); + + /** + * The list of Realms contained by this Realm. + */ + protected List<Realm> realms = new LinkedList<Realm>(); + + + /** + * Add a realm to the list of realms that will be used to authenticate + * users. + */ + public void addRealm(Realm theRealm) { + realms.add(theRealm); + + if (log.isDebugEnabled()) { + sm.getString("combinedRealm.addRealm", theRealm.getInfo(), + Integer.toString(realms.size())); + } + } + + + /** + * Return the Principal associated with the specified username and + * credentials, if there is one; otherwise return <code>null</code>. + * + * @param username Username of the Principal to look up + * @param credentials Password or other credentials to use in + * authenticating this username + */ + public Principal authenticate(String username, byte[] credentials) { + Principal authenticatedUser = null; + + for (Realm realm : realms) { + if (log.isDebugEnabled()) { + log.debug(sm.getString("combinedRealm.authStart", username, realm.getInfo())); + } + + authenticatedUser = realm.authenticate(username, credentials); + + if (authenticatedUser == null) { + if (log.isDebugEnabled()) { + log.debug(sm.getString("combinedRealm.authFail", username, realm.getInfo())); + } + } else { + if (log.isDebugEnabled()) { + log.debug(sm.getString("combinedRealm.authSucess", username, realm.getInfo())); + } + break; + } + } + return authenticatedUser; + } + + + /** + * Return the Principal associated with the specified username, which + * matches the digest calculated using the given parameters using the + * method described in RFC 2069; otherwise return <code>null</code>. + * + * @param username Username of the Principal to look up + * @param clientDigest Digest which has been submitted by the client + * @param nOnce Unique (or supposedly unique) token which has been used + * for this request + * @param realm Realm name + * @param md5a2 Second MD5 digest used to calculate the digest : + * MD5(Method + ":" + uri) + */ + public Principal authenticate(String username, String clientDigest, + String once, String nc, String cnonce, String qop, + String realmName, String md5a2) { + Principal authenticatedUser = null; + + for (Realm realm : realms) { + if (log.isDebugEnabled()) { + log.debug(sm.getString("combinedRealm.authStart", username, realm.getInfo())); + } + + authenticatedUser = realm.authenticate(username, clientDigest, once, + nc, cnonce, qop, realmName, md5a2); + + if (authenticatedUser == null) { + if (log.isDebugEnabled()) { + log.debug(sm.getString("combinedRealm.authFail", username, realm.getInfo())); + } + } else { + if (log.isDebugEnabled()) { + log.debug(sm.getString("combinedRealm.authSucess", username, realm.getInfo())); + } + break; + } + } + return authenticatedUser; + } + + + /** + * Return the Principal associated with the specified username and + * credentials, if there is one; otherwise return <code>null</code>. + * + * @param username Username of the Principal to look up + * @param credentials Password or other credentials to use in + * authenticating this username + */ + public Principal authenticate(String username, String credentials) { + Principal authenticatedUser = null; + + for (Realm realm : realms) { + if (log.isDebugEnabled()) { + log.debug(sm.getString("combinedRealm.authStart", username, realm.getInfo())); + } + + authenticatedUser = realm.authenticate(username, credentials); + + if (authenticatedUser == null) { + if (log.isDebugEnabled()) { + log.debug(sm.getString("combinedRealm.authFail", username, realm.getInfo())); + } + } else { + if (log.isDebugEnabled()) { + log.debug(sm.getString("combinedRealm.authSucess", username, realm.getInfo())); + } + break; + } + } + return authenticatedUser; + } + + + /** + * Set the Container with which this Realm has been associated. + * + * @param container The associated Container + */ + public void setContainer(Container container) { + // Set the container for sub-realms. Mainly so logging works. + for(Realm realm : realms) { + realm.setContainer(container); + } + super.setContainer(container); + } + + + /** + * Prepare for the beginning of active use of the public methods of this + * component. This method should be called before any of the public + * methods of this component are utilized. It should also send a + * LifecycleEvent of type START_EVENT to any registered listeners. + * + * @exception LifecycleException if this component detects a fatal error + * that prevents this component from being used + */ + public void start() throws LifecycleException { + // Start 'sub-realms' then this one + for (Realm realm : realms) { + if (realm instanceof Lifecycle) { + ((Lifecycle) realm).start(); + } + } + super.start(); + } + + + /** + * Gracefully terminate the active use of the public methods of this + * component. This method should be the last one called on a given + * instance of this component. It should also send a LifecycleEvent + * of type STOP_EVENT to any registered listeners. + * + * @exception LifecycleException if this component detects a fatal error + * that needs to be reported + */ + public void stop() throws LifecycleException { + // Stop this realm, then the sub-realms (reverse order to start) + super.stop(); + for (Realm realm : realms) { + if (realm instanceof Lifecycle) { + ((Lifecycle) realm).stop(); + } + } + } + + + /** + * Return the Principal associated with the specified chain of X509 + * client certificates. If there is none, return <code>null</code>. + * + * @param certs Array of client certificates, with the first one in + * the array being the certificate of the client itself. + */ + public Principal authenticate(X509Certificate[] certs) { + Principal authenticatedUser = null; + String username = null; + if (certs != null && certs.length >0) { + username = certs[0].getSubjectDN().getName(); + } + + for (Realm realm : realms) { + if (log.isDebugEnabled()) { + log.debug(sm.getString("combinedRealm.authStart", username, realm.getInfo())); + } + + authenticatedUser = realm.authenticate(certs); + + if (authenticatedUser == null) { + if (log.isDebugEnabled()) { + log.debug(sm.getString("combinedRealm.authFail", username, realm.getInfo())); + } + } else { + if (log.isDebugEnabled()) { + log.debug(sm.getString("combinedRealm.authSucess", username, realm.getInfo())); + } + break; + } + } + return authenticatedUser; + } + + @Override + protected String getName() { + // This method should never be called + // Stack trace will show where this was called from + UnsupportedOperationException uoe = + new UnsupportedOperationException( + sm.getString("combinedRealm.getName")); + log.error(sm.getString("combinedRealm.unexpectedMethod"), uoe); + throw uoe; + } + + @Override + protected String getPassword(String username) { + // This method should never be called + // Stack trace will show where this was called from + UnsupportedOperationException uoe = + new UnsupportedOperationException( + sm.getString("combinedRealm.getPassword")); + log.error(sm.getString("combinedRealm.unexpectedMethod"), uoe); + throw uoe; + } + + @Override + protected Principal getPrincipal(String username) { + // This method should never be called + // Stack trace will show where this was called from + UnsupportedOperationException uoe = + new UnsupportedOperationException( + sm.getString("combinedRealm.getPrincipal")); + log.error(sm.getString("combinedRealm.unexpectedMethod"), uoe); + throw uoe; + } + +} Propchange: tomcat/trunk/java/org/apache/catalina/realm/CombinedRealm.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: tomcat/trunk/java/org/apache/catalina/realm/CombinedRealm.java ------------------------------------------------------------------------------ svn:keywords = Date Revision Author Id Modified: tomcat/trunk/java/org/apache/catalina/realm/LocalStrings.properties URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/realm/LocalStrings.properties?rev=691805&r1=691804&r2=691805&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/realm/LocalStrings.properties (original) +++ tomcat/trunk/java/org/apache/catalina/realm/LocalStrings.properties Wed Sep 3 15:18:39 2008 @@ -85,3 +85,11 @@ dataSourceRealm.getPassword.exception=Exception retrieving password for "{0}" dataSourceRealm.getRoles.exception=Exception retrieving roles for "{0}" dataSourceRealm.open=Exception opening database connection +combinedRealm.unexpectedMethod=An unexpected call was made to a method on the combined realm +combinedRealm.getName=The getName() method should never be called +combinedRealm.getPassword=The getPassword() method should never be called +combinedRealm.getPrincipal=The getPrincipal() method should never be called +combinedRealm.authStart=Attempting to authenticate user "{0}" with realm "{1}" +combinedRealm.authFailed=Failed to authenticate user "{0}" with realm "{1}" +combinedRealm.authSucess=Authenticated user "{0}" with realm "{1}" +combinedRealm.addRealm=Add "{0}" realm, making a total of "{1}" realms \ No newline at end of file Modified: tomcat/trunk/java/org/apache/catalina/startup/ContextRuleSet.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/startup/ContextRuleSet.java?rev=691805&r1=691804&r2=691805&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/startup/ContextRuleSet.java (original) +++ tomcat/trunk/java/org/apache/catalina/startup/ContextRuleSet.java Wed Sep 3 15:18:39 2008 @@ -170,13 +170,7 @@ "addApplicationParameter", "org.apache.catalina.deploy.ApplicationParameter"); - digester.addObjectCreate(prefix + "Context/Realm", - null, // MUST be specified in the element - "className"); - digester.addSetProperties(prefix + "Context/Realm"); - digester.addSetNext(prefix + "Context/Realm", - "setRealm", - "org.apache.catalina.Realm"); + digester.addRuleSet(new RealmRuleSet(prefix + "Context/")); digester.addObjectCreate(prefix + "Context/Resources", "org.apache.naming.resources.FileDirContext", Modified: tomcat/trunk/java/org/apache/catalina/startup/EngineRuleSet.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/startup/EngineRuleSet.java?rev=691805&r1=691804&r2=691805&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/startup/EngineRuleSet.java (original) +++ tomcat/trunk/java/org/apache/catalina/startup/EngineRuleSet.java Wed Sep 3 15:18:39 2008 @@ -121,13 +121,7 @@ "org.apache.catalina.LifecycleListener"); - digester.addObjectCreate(prefix + "Engine/Realm", - null, // MUST be specified in the element - "className"); - digester.addSetProperties(prefix + "Engine/Realm"); - digester.addSetNext(prefix + "Engine/Realm", - "setRealm", - "org.apache.catalina.Realm"); + digester.addRuleSet(new RealmRuleSet(prefix + "Engine/")); digester.addObjectCreate(prefix + "Engine/Valve", null, // MUST be specified in the element Modified: tomcat/trunk/java/org/apache/catalina/startup/HostRuleSet.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/startup/HostRuleSet.java?rev=691805&r1=691804&r2=691805&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/startup/HostRuleSet.java (original) +++ tomcat/trunk/java/org/apache/catalina/startup/HostRuleSet.java Wed Sep 3 15:18:39 2008 @@ -124,13 +124,7 @@ "addLifecycleListener", "org.apache.catalina.LifecycleListener"); - digester.addObjectCreate(prefix + "Host/Realm", - null, // MUST be specified in the element - "className"); - digester.addSetProperties(prefix + "Host/Realm"); - digester.addSetNext(prefix + "Host/Realm", - "setRealm", - "org.apache.catalina.Realm"); + digester.addRuleSet(new RealmRuleSet(prefix + "Host/")); digester.addObjectCreate(prefix + "Host/Valve", null, // MUST be specified in the element Added: tomcat/trunk/java/org/apache/catalina/startup/RealmRuleSet.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/startup/RealmRuleSet.java?rev=691805&view=auto ============================================================================== --- tomcat/trunk/java/org/apache/catalina/startup/RealmRuleSet.java (added) +++ tomcat/trunk/java/org/apache/catalina/startup/RealmRuleSet.java Wed Sep 3 15:18:39 2008 @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.catalina.startup; + + +import org.apache.tomcat.util.digester.Digester; +import org.apache.tomcat.util.digester.RuleSetBase; + + +/** + * <p><strong>RuleSet</strong> for processing the contents of a Realm definition + * element. This <code>RuleSet</code> supports Realms such as the + * <code>CombinedRealm</code> that used nested Realms.</p> + * + * @version $Revision$ $Date$ + */ + +public class RealmRuleSet extends RuleSetBase { + + + // ----------------------------------------------------- Instance Variables + + + /** + * The matching pattern prefix to use for recognizing our elements. + */ + protected String prefix = null; + + + // ------------------------------------------------------------ Constructor + + + /** + * Construct an instance of this <code>RuleSet</code> with the default + * matching pattern prefix. + */ + public RealmRuleSet() { + + this(""); + + } + + + /** + * Construct an instance of this <code>RuleSet</code> with the specified + * matching pattern prefix. + * + * @param prefix Prefix for matching pattern rules (including the + * trailing slash character) + */ + public RealmRuleSet(String prefix) { + + super(); + this.namespaceURI = null; + this.prefix = prefix; + + } + + + // --------------------------------------------------------- Public Methods + + + /** + * <p>Add the set of Rule instances defined in this RuleSet to the + * specified <code>Digester</code> instance, associating them with + * our namespace URI (if any). This method should only be called + * by a Digester instance.</p> + * + * @param digester Digester instance to which the new Rule instances + * should be added. + */ + public void addRuleInstances(Digester digester) { + + digester.addObjectCreate(prefix + "Realm", + null, // MUST be specified in the element, + "className"); + digester.addSetProperties(prefix + "Realm"); + digester.addSetNext(prefix + "Realm", + "setRealm", + "org.apache.catalina.Realm"); + + digester.addObjectCreate(prefix + "Realm/Realm", + null, // MUST be specified in the element + "className"); + digester.addSetProperties(prefix + "Realm/Realm"); + digester.addSetNext(prefix + "Realm/Realm", + "addRealm", + "org.apache.catalina.Realm"); + + } + + +} Propchange: tomcat/trunk/java/org/apache/catalina/startup/RealmRuleSet.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: tomcat/trunk/java/org/apache/catalina/startup/RealmRuleSet.java ------------------------------------------------------------------------------ svn:keywords = Date Revision Author Id Modified: tomcat/trunk/webapps/docs/config/realm.xml URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/config/realm.xml?rev=691805&r1=691804&r2=691805&view=diff ============================================================================== --- tomcat/trunk/webapps/docs/config/realm.xml (original) +++ tomcat/trunk/webapps/docs/config/realm.xml Wed Sep 3 15:18:39 2008 @@ -565,6 +565,30 @@ Guide</a> for more information on setting up container managed security using the JAAS Realm component.</p> + <h3>Combined Realm (org.apache.catalina.realm.CombinedRealm)</h3> + + <p><strong>CombinedRealm</strong> is an implementation of the Tomcat 6 + <code>Realm</code> interface that authenticates users through one or more + sub-Realms.</p> + + <p>Using CombinedRealm gives the developer the ability to combine multiple + Realms of the same or different types. This can be used to authenticate + against different sources, provide fall back in case one Realm fails or for + any other purpose that requires multiple Realms.</p> + + <p>Sub-realms are defined by nesting <code>Realm</code> elements inside the + <code>Realm</code> element that defines the CombinedRealm. Authentication + will be attempted against each <code>Realm</code> in the order they are + listed. Authentication against any Realm will be sufficient to authenticate + the user.</p> + + <p>The Combined Realm implementation does not support any additional + attributes.</p> + + <p>See the <a href="../realm-howto.html">Container-Managed Security + Guide</a> for more information on setting up container managed security + using the Combined Realm component.</p> + </subsection> @@ -573,7 +597,14 @@ <section name="Nested Components"> - <p>No components may be nested inside a <strong>Realm</strong> element.</p> + <h3>Combined Realm Implementation</h3> + + <p>If you are using the <em>Combined Realm Implementation</em> + <strong><Realm></strong> elements may be nested inside it.</p> + + <h3>Other Realm Implementations</h3> + + <p>No other Realm implementation supports nested components.</p> </section> Modified: tomcat/trunk/webapps/docs/realm-howto.xml URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/realm-howto.xml?rev=691805&r1=691804&r2=691805&view=diff ============================================================================== --- tomcat/trunk/webapps/docs/realm-howto.xml (original) +++ tomcat/trunk/webapps/docs/realm-howto.xml Wed Sep 3 15:18:39 2008 @@ -1440,6 +1440,61 @@ </subsection> +<subsection name="CombinedRealm"> + + <h3>Introduction</h3> + + <p><strong>CombinedRealm</strong> is an implementation of the Tomcat 6 + <code>Realm</code> interface that authenticates users through one or more + sub-Realms.</p> + + <p>Using CombinedRealm gives the developer the ability to combine multiple + Realms of the same or different types. This can be used to authenticate + against different sources, provide fall back in case one Realm fails or for + any other purpose that requires multiple Realms.</p> + + <p>Sub-realms are defined by nesting <code>Realm</code> elements inside the + <code>Realm</code> element that defines the CombinedRealm. Authentication + will be attempted against each <code>Realm</code> in the order they are + listed. Authentication against any Realm will be sufficient to authenticate + the user.</p> + + <h3>Realm Element Attributes</h3> + <p>To configure CombinedRealm, you create a <code><Realm></code> + element and nest it in your <code>$CATALINA_BASE/conf/server.xml</code> + file within your <code><Engine></code> or <code><Host></code>. + You can also nest inside a <code><Context></code> node in a + <code>context.xml</code> file. The following attributes are supported by + this implementation:</p> + +<attributes> + + <attribute name="className" required="true"> + <p>The fully qualified Java class name of this Realm implementation. + You <strong>MUST</strong> specify the value + "<code>org.apache.catalina.realm.CombinedRealm</code>" here.</p> + </attribute> + +</attributes> + +<h3>Example</h3> + +<p>Here is an example of how your server.xml snippet should look to use a +UserDatabase Realm and a DataSource Realm.</p> + +<source> +<Realm className="org.apache.catalina.realm.CombinedRealm" > + <Realm className="org.apache.catalina.realm.UserDatabaseRealm" + resourceName="UserDatabase"/> + <Realm className="org.apache.catalina.realm.DataSourceRealm" debug="99" + dataSourceName="jdbc/authority" + userTable="users" userNameCol="user_name" userCredCol="user_pass" + userRoleTable="user_roles" roleNameCol="role_name"/> +<Realm/> +</source> + +</subsection> + </section> </body> --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]