Improve Active Directory compatibility of the JNDIRealm.
authorrjung <rjung@13f79535-47bb-0310-9956-ffa450edef68>
Fri, 19 Sep 2008 21:23:22 +0000 (21:23 +0000)
committerrjung <rjung@13f79535-47bb-0310-9956-ffa450edef68>
Fri, 19 Sep 2008 21:23:22 +0000 (21:23 +0000)
AD often returns referrals and when iterating through
NamingEnumerations those produce PartialResultsException
we need to ignore. Since there is no robust way of detecting
whether they are actually thrown because of AD referrals,
we keep the handling configurable.

git-svn-id: https://svn.apache.org/repos/asf/tomcat/trunk@697248 13f79535-47bb-0310-9956-ffa450edef68

java/org/apache/catalina/realm/JNDIRealm.java
webapps/docs/config/realm.xml
webapps/docs/realm-howto.xml

index 150738b..f28c03f 100644 (file)
@@ -23,6 +23,7 @@ import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Hashtable;
+import java.util.Iterator;
 import java.util.List;
 
 import javax.naming.Context;
@@ -35,6 +36,7 @@ import javax.naming.NamingException;
 import javax.naming.NameParser;
 import javax.naming.Name;
 import javax.naming.AuthenticationException;
+import javax.naming.PartialResultException;
 import javax.naming.ServiceUnavailableException;
 import javax.naming.directory.Attribute;
 import javax.naming.directory.Attributes;
@@ -228,9 +230,20 @@ public class JNDIRealm extends RealmBase {
 
 
     /**
-     * How should we handle referrals?  Microsoft Active Directory can't handle
-     * the default case, so an application authenticating against AD must
-     * set referrals to "follow".
+     * Should we ignore PartialResultExceptions when iterating over NamingEnumerations?
+     * Microsoft Active Directory often returns referrals, which lead
+     * to PartialResultExceptions. Unfortunately there's no stable way to detect,
+     * if the Exceptions really come from an AD referral.
+     * Set to true to ignore PartialResultExceptions.
+     */
+    protected boolean adCompat = false;
+
+
+    /**
+     * How should we handle referrals?  Microsoft Active Directory often returns
+     * referrals. If you need to follow them set referrals to "follow".
+     * Caution: if your DNS is not part of AD, the LDAP client lib might try
+     * to resolve your domain name in DNS to find another LDAP server.
      */
     protected String referrals = null;
 
@@ -500,6 +513,23 @@ public class JNDIRealm extends RealmBase {
 
 
     /**
+     * Returns the current settings for handling PartialResultExceptions
+     */
+    public boolean getAdCompat () {
+        return adCompat;
+    }
+
+
+    /**
+     * How do we handle PartialResultExceptions?
+     * True: ignore all PartialResultExceptions.
+     */
+    public void setAdCompat (boolean adCompat) {
+        this.adCompat = adCompat;
+    }
+
+
+    /**
      * Returns the current settings for handling JNDI referrals.
      */
     public String getReferrals () {
@@ -948,6 +978,12 @@ public class JNDIRealm extends RealmBase {
                         if (checkCredentials(context, user, credentials)) {
                             // Search for additional roles
                             List<String> roles = getRoles(context, user);
+                            if (containerLog.isDebugEnabled()) {
+                                Iterator it = roles.iterator();
+                                while (it.hasNext()) {
+                                    containerLog.debug("Found role: " + it.next());
+                                }
+                            }
                             return (new GenericPrincipal(this,
                                                          username,
                                                          credentials,
@@ -976,6 +1012,12 @@ public class JNDIRealm extends RealmBase {
 
             // Search for additional roles
             List<String> roles = getRoles(context, user);
+            if (containerLog.isDebugEnabled()) {
+                Iterator it = roles.iterator();
+                while (it.hasNext()) {
+                    containerLog.debug("Found role: " + it.next());
+                }
+            }
 
             // Create and return a suitable Principal for this user
             return (new GenericPrincipal(this, username, credentials, roles));
@@ -1114,18 +1156,30 @@ public class JNDIRealm extends RealmBase {
 
 
         // Fail if no entries found
-        if (results == null || !results.hasMore()) {
-            return (null);
+        try {
+            if (results == null || !results.hasMore()) {
+                return (null);
+            }
+        } catch (PartialResultException ex) {
+            if (!adCompat)
+                throw ex;
+            else
+                return (null);
         }
 
         // Get result for the first entry found
         SearchResult result = (SearchResult)results.next();
 
         // Check no further entries were found
-        if (results.hasMore()) {
-            if(containerLog.isInfoEnabled())
-                containerLog.info("username " + username + " has multiple entries");
-            return (null);
+        try {
+            if (results.hasMore()) {
+                if(containerLog.isInfoEnabled())
+                    containerLog.info("username " + username + " has multiple entries");
+                return (null);
+            }
+        } catch (PartialResultException ex) {
+            if (!adCompat)
+                throw ex;
         }
 
         // Get the entry's distinguished name
@@ -1412,12 +1466,17 @@ public class JNDIRealm extends RealmBase {
             context.search(roleBase, filter, controls);
         if (results == null)
             return (list);  // Should never happen, but just in case ...
-        while (results.hasMore()) {
-            SearchResult result = (SearchResult) results.next();
-            Attributes attrs = result.getAttributes();
-            if (attrs == null)
-                continue;
-            list = addAttributeValues(roleName, attrs, list);
+        try {
+            while (results.hasMore()) {
+                SearchResult result = (SearchResult) results.next();
+                Attributes attrs = result.getAttributes();
+                if (attrs == null)
+                    continue;
+                list = addAttributeValues(roleName, attrs, list);
+            }
+        } catch (PartialResultException ex) {
+            if (!adCompat)
+                throw ex;
         }
 
 
@@ -1493,9 +1552,14 @@ public class JNDIRealm extends RealmBase {
         if (attr == null)
             return (values);
         NamingEnumeration e = attr.getAll();
-        while(e.hasMore()) {
-            String value = (String)e.next();
-            values.add(value);
+        try {
+            while(e.hasMore()) {
+                String value = (String)e.next();
+                values.add(value);
+            }
+        } catch (PartialResultException ex) {
+            if (!adCompat)
+                throw ex;
         }
         return values;
     }
index ac1e313..58407a1 100644 (file)
     information from the directory:</p>
 
     <attributes>
-       <attribute name="alternateURL" required="false">
-         <p>If a socket connection can not be made to the provider at
-         the <code>connectionURL</code> an attempt will be made to use the
-         <code>alternateURL</code>.</p>
-       </attribute>
-
-       <attribute name="authentication" required="false">
-         <p>A string specifying the type of authentication to use.
-         "none", "simple", "strong" or a provider specific definition
-         can be used. If no value is given the providers default is used.</p>
-       </attribute>
+
+      <attribute name="adCompat" required="false">
+        <p>Microsoft Active Directory often returns referrals.
+        When iterating over NamingEnumerations these lead to
+        PartialResultExceptions. If you want us to ignore those exceptions,
+        set this attribute to "true". Unfortunately there's no stable way
+        to detect, if the Exceptions really come from an AD referral.
+        The default value is "false".</p>
+      </attribute>
+
+      <attribute name="alternateURL" required="false">
+        <p>If a socket connection can not be made to the provider at
+        the <code>connectionURL</code> an attempt will be made to use the
+        <code>alternateURL</code>.</p>
+      </attribute>
+
+      <attribute name="authentication" required="false">
+        <p>A string specifying the type of authentication to use.
+        "none", "simple", "strong" or a provider specific definition
+        can be used. If no value is given the providers default is used.</p>
+      </attribute>
 
       <attribute name="commonRole" required="false">
         <p>A role name assigned to each successfully authenticated user in
         to acquire our JNDI <code>InitialContext</code>.  By default,
         assumes that the standard JNDI LDAP provider will be utilized.</p>
       </attribute>
-      
+
       <attribute name="derefAliases" required="false">
         <p>A string specifying how aliases are to be dereferenced during
         search operations. The allowed values are "always", "never",
          the providers default is used.</p>
       </attribute>
 
+      <attribute name="referrals" required="false">
+        <p>How do we handle JNDI referrals? Allowed values are
+        "ignore", "follow", or "throw"  (see javax.naming.Context.REFERRAL
+        for more information).
+        Microsoft Active Directory often returns referrals.
+        If you need to follow them set referrals to "follow".
+        Caution: if your DNS is not part of AD, the LDAP client lib might try
+        to resolve your domain name in DNS to find another LDAP server.</p>
+      </attribute>
+
       <attribute name="roleBase" required="false">
         <p>The base directory entry for performing role searches. If
         not specified the top-level element in the directory context
index cfd37ba..e2fdab7 100644 (file)
@@ -847,6 +847,15 @@ attributes are supported by this implementation:</p>
     "<code>org.apache.catalina.realm.JNDIRealm</code>" here.</p>
   </attribute>
 
+      <attribute name="adCompat" required="false">
+        <p>Microsoft Active Directory often returns referrals.
+        When iterating over NamingEnumerations these lead to
+        PartialResultExceptions. If you want us to ignore those exceptions,
+        set this attribute to "true". Unfortunately there's no stable way
+        to detect, if the Exceptions really come from an AD referral.
+        The default value is "false".</p>
+      </attribute>
+
       <attribute name="alternateURL" required="false">
         <p>If a socket connection can not be made to the provider at
         the <code>connectionURL</code> an attempt will be made to use the
@@ -904,6 +913,16 @@ attributes are supported by this implementation:</p>
         specified</p>
       </attribute>
 
+      <attribute name="referrals" required="false">
+        <p>How do we handle JNDI referrals? Allowed values are
+        "ignore", "follow", or "throw"  (see javax.naming.Context.REFERRAL
+        for more information).
+        Microsoft Active Directory often returns referrals.
+        If you need to follow them set referrals to "follow".
+        Caution: if your DNS is not part of AD, the LDAP client lib might try
+        to resolve your domain name in DNS to find another LDAP server.</p>
+      </attribute>
+
       <attribute name="protocol" required="false">
          <p>A string specifying the security protocol to use. If not given
          the providers default is used.</p>