Issue Details (XML | Word | Printable)

Key: SEC-832
Type: Bug Bug
Status: Closed Closed
Resolution: Fixed
Priority: Major Major
Assignee: Luke Taylor
Reporter: Jakub Jozwicki
Votes: 0
Watchers: 0
Operations

If you were logged in you would be able to see more operations.
Spring Security

NamingEnumeration.hasMore fails on MS AD with PartialResultException

Created: 14/May/08 09:24 AM   Updated: 28/Aug/08 06:22 AM
Component/s: Core
Affects Version/s: 2.0.1
Fix Version/s: 2.0.4

Time Tracking:
Not Specified

File Attachments: 1. Zip Archive mylyn-context.zip (4 kB)

Environment: MS AD @ Windows 2003 R2
Issue Links:
Related
 


 Description  « Hide
Implementation of org.springframework.security.ldap.searchForSingleEntry doesn't work with MS AD.
When calling results.hasMore with base="" a PartialResultException is thrown. The call hasMore should
be replaced with hasMoreElements() and whole method should look like this:

public DirContextOperations searchForSingleEntry(final String base, final String filter, final Object[] params) {

        return (DirContextOperations) executeReadOnly(new ContextExecutor() {
public Object executeWithContext(DirContext ctx)
throws NamingException {

try {
NamingEnumeration results = ctx.search(base, filter, params, searchControls);

if (!results.hasMoreElements())
throw new IncorrectResultSizeDataAccessException(1, 0);

SearchResult searchResult = (SearchResult) results.next();
logger.debug("searchResult="+searchResult);

if (results.hasMoreElements()) {
if (!searchResult.equals((SearchResult) results.next()))
throw new IncorrectResultSizeDataAccessException(1, 2);
}

// Work out the DN of the matched entry
StringBuffer dn = new StringBuffer(searchResult.getName());

if (base.length() > 0) {
dn.append(",");
dn.append(base);
}

return new DirContextAdapter(searchResult.getAttributes(),
new DistinguishedName(dn.toString()), new DistinguishedName(ctx.getNameInNamespace()));
}
catch (Exception e) {
NamingException ne = new NamingException(e.getMessage());
ne.setStackTrace(e.getStackTrace());
throw ne;
}
}
});
    }


 All   Comments   Work Log   Change History   FishEye   Builds      Sort Order: Ascending order - Click to sort in descending order
Jakub Jozwicki added a comment - 14/May/08 09:26 AM
org.springframework.security.ldap.SpringSecurityLdapTemplate

Luke Taylor added a comment - 14/May/08 05:35 PM
This sounds like the recurring issue related to AD and LDAP referrals. Could you explain the reasoning behind your proposed changes? i.e. hasMore() can potentially raise an exception (as it does here with AD) - can we justify hiding those exceptions in all possible cases that might arise. Also why have you inserted the try/catch(Exception ) block?

Luke Taylor added a comment - 14/May/08 05:36 PM
Also, have you tried the various workarounds you can find for issues with AD and referrals?

Jakub Jozwicki added a comment - 15/May/08 03:50 AM
The workaround is to use 'ou=container,dc=example,dc=com' instead of 'dc=example,dc=com'.
However I've got many organizational units at the root level and I need to search inside all of them.
PartialResultException is thrown on accessing domainDNS objects located at the root level.
This happens always with W2K3 acting as a ActiveDirectory and DNS server.
The only workaround in this case is to use hasMoreElements instead of hasMore.

Catching and ignoring PartialResultException causes following results.hasMore() and results.next()
to return object already returned in previous step:

hasMore, next() -> object1
hasMore -> exception, ignored
hasMore -> object1

It is also a solution provided that the code checking for single result looks like this:

if (results.hasMoreElements()) {
if (!searchResult.equals((SearchResult) results.next()))
throw new IncorrectResultSizeDataAccessException(1, 2);
}

However we might not detect here real incorrect results.

---
Maybe there could be some boolean switch for different behavior on AD
(to use hasMoreElements instead of hasMore).

I'm also not happy with making hacks because of badly written MS software.

The try/catch was for debugging purposes and can be deleted.

Luke Taylor added a comment - 15/May/08 08:46 AM
There are other workarounds depending on what the exact error is. Setting java.naming.referral=follow is one, editing the /etc/hosts file on the machine:

http://www.mail-archive.com/cas@tp.its.yale.edu/msg00797.html

Spring's LDAP template already has a flag which allows PartialResultExceptions to be ignored, so we might be able to make use of that.

Jakub Jozwicki added a comment - 16/May/08 04:23 AM
With -Djava.naming.referral=follow I still have exception on
'ldap://192.168.1.191:389/dc=ForestDnsZones,dc=pld1,dc=net' domainDNS objectClass:

[28812] authentication.LoggerListener Authentication event AuthenticationFailure
ServiceExceptionEvent: Jozwicki, Jakub; details: org.springframework.security.ui
.WebAuthenticationDetails@fffc7f0c: RemoteIpAddress: 127.0.0.1; SessionId: ybom8
a44alkv; exception: Unprocessed Continuation Reference(s); nested exception is j
avax.naming.PartialResultException: Unprocessed Continuation Reference(s); remai
ning name 'dc=pld1,dc=net'; nested exception is org.springframework.ldap.Partial
ResultException: Unprocessed Continuation Reference(s); nested exception is java
x.naming.PartialResultException: Unprocessed Continuation Reference(s); remainin
g name 'dc=pld1,dc=net'

---
Also code in ldap.core.LdapTemplate is wrong, because while(results.hasMore())
is inside try/catch(PartialResultException), and exception breaks while loop,
but it shouldn't. The try/catch(PartialResultException) should be located inside while loop.
Like this:

public void search(SearchExecutor se, NameClassPairCallbackHandler handler,
            DirContextProcessor processor) {
        DirContext ctx = contextSource.getReadOnlyContext();

        NamingEnumeration results = null;
        RuntimeException ex = null;
        try {
            processor.preProcess(ctx);
            results = se.executeSearch(ctx);
            log.debug("results are="+results);
            
            while(true) {
             try {
             if (!results.hasMore())
             break;
             NameClassPair result = (NameClassPair) results.next();
         log.debug("result="+result);
         log.debug("result="+result.getName()+", "+result.getClassName());
         handler.handleNameClassPair(result);
             }
             catch (PartialResultException e) {
                     if (ignorePartialResultException) {
                         log.debug("PartialResultException encountered and ignored: ["+e.getResolvedName()+"] "+ e.getRemainingName());
                     } else {
                         ex = LdapUtils.convertLdapException(e);
                     }
                     continue;
             }
             catch (NullPointerException npe) {
             break;
             }
            }
        }
        catch (NameNotFoundException e) {
            // The base context was not found, which basically means
            // that the search did not return any results. Just clean up and
            // exit.
            // Note that this may present problems if a DirContextProcessor was
            // supplied - there's no guarantee that the postProcess() operation
            // will go well after a NamingException has been thrown. It is
            // however quite possible that information will be available for
            // retrieval either way.
         e.printStackTrace();
        }
        catch (javax.naming.NamingException e) {
            ex = LdapUtils.convertLdapException(e);
        }
        finally {
            try {
                processor.postProcess(ctx);
            } catch (javax.naming.NamingException e) {
                if (ex == null) {
                    ex = LdapUtils.convertLdapException(e);
                } else {
                    // We already had an exception from above and should ignore
                    // this one.
                    log.debug("Ignoring Exception from postProcess, "
                            + "main exception thrown instead", e);
                }
            }
            closeContextAndNamingEnumeration(ctx, results);
            // If we got an exception it should be thrown.
            if (ex != null) {
                throw ex;
            }
        }
    }

Luke Taylor added a comment - 20/May/08 04:54 AM
Please raise any issues with Spring LDAP in that project or in the Spring LDAP forum.

Luke Taylor added a comment - 26/Aug/08 07:54 AM
I've modified the searchForSingleEntry method to ignore a PartialResultException if thrown, in the same way that LdapTemplate does if ignorePartialResultException is set to true. Given your comment in issue LDAP-113 about there being no further results after such an exception (and my own tests) I've used the existing pattern rather than catching the exception within the while loop, ignoring NPE etc.