Issue Details (XML | Word | Printable)

Key: DIRSERVER-228
Type: Bug Bug
Status: Closed Closed
Resolution: Fixed
Priority: Major Major
Assignee: Unassigned
Reporter: Luke Taylor
Votes: 0
Watchers: 1
Operations

If you were logged in you would be able to see more operations.
Directory ApacheDS

ClassCastException when performing 'compare' on userPassword

Created: 08/Dec/05 07:54 AM   Updated: 21/Apr/07 11:20 AM
Return to search
Component/s: ldap
Affects Version/s: pre-1.0
Fix Version/s: pre-1.0

Time Tracking:
Not Specified

Resolution Date: 20/Jan/06 07:41 AM


 Description  « Hide
Using the latest 0.9.4 snapshots and also code checked out and built today - (but had to include an older version of Mina with the latter due to ClassNotFoundExceptions for MessageHandler ):

I'm making a call to apache-ds to perform a comparison operation on a user's password and I'm get a ClassCastException at line 369 of DefaultDirectoryPartitionNexus:

            String attrVal = ( String ) normalizer.normalize( attr.get( ii ) );

when attr is userPassword (a byte array) and the normalizer is a no-op, so the cast to String fails.

javax.naming.NamingException: [LDAP: error code 1 - failed to compare entry cn=Bob,ou=people,dc=acegisecurity,dc=org:
org.apache.ldap.server.interceptor.InterceptorException: Unexpected exception. [Root exception is java.lang.ClassCastException: [B]
at org.apache.ldap.server.interceptor.InterceptorChain.throwInterceptorException(InterceptorChain.java:1368)
at org.apache.ldap.server.interceptor.InterceptorChain.access$700(InterceptorChain.java:49)
at org.apache.ldap.server.interceptor.InterceptorChain$Entry$1.compare(InterceptorChain.java:983)
at org.apache.ldap.server.interceptor.BaseInterceptor.compare(BaseInterceptor.java:210)
at org.apache.ldap.server.interceptor.InterceptorChain$Entry$1.compare(InterceptorChain.java:975)
at org.apache.ldap.server.interceptor.BaseInterceptor.compare(BaseInterceptor.java:210)
at org.apache.ldap.server.interceptor.InterceptorChain$Entry$1.compare(InterceptorChain.java:975)
at org.apache.ldap.server.interceptor.BaseInterceptor.compare(BaseInterceptor.java:210)
at org.apache.ldap.server.interceptor.InterceptorChain$Entry$1.compare(InterceptorChain.java:975)
at org.apache.ldap.server.interceptor.BaseInterceptor.compare(BaseInterceptor.java:210)
at org.apache.ldap.server.interceptor.InterceptorChain$Entry$1.compare(InterceptorChain.java:975)
at org.apache.ldap.server.interceptor.BaseInterceptor.compare(BaseInterceptor.java:210)
at org.apache.ldap.server.interceptor.InterceptorChain$Entry$1.compare(InterceptorChain.java:975)
at org.apache.ldap.server.interceptor.BaseInterceptor.compare(BaseInterceptor.java:210)
at org.apache.ldap.server.interceptor.InterceptorChain$Entry$1.compare(InterceptorChain.java:975)
at org.apache.ldap.server.interceptor.BaseInterceptor.compare(BaseInterceptor.java:210)
at org.apache.ldap.server.interceptor.InterceptorChain$Entry$1.compare(InterceptorChain.java:975)
at org.apache.ldap.server.authz.AuthorizationService.compare(AuthorizationService.java:917)
at org.apache.ldap.server.interceptor.InterceptorChain$Entry$1.compare(InterceptorChain.java:975)
at org.apache.ldap.server.interceptor.BaseInterceptor.compare(BaseInterceptor.java:210)
at org.apache.ldap.server.interceptor.InterceptorChain$Entry$1.compare(InterceptorChain.java:975)
at org.apache.ldap.server.normalization.NormalizationService.compare(NormalizationService.java:236)
at org.apache.ldap.server.interceptor.InterceptorChain.compare(InterceptorChain.java:564)
at org.apache.ldap.server.partition.DirectoryPartitionNexusProxy.compare(DirectoryPartitionNexusProxy.java:232)
at org.apache.ldap.server.partition.DirectoryPartitionNexusProxy.compare(DirectoryPartitionNexusProxy.java:221)
at org.apache.ldap.server.jndi.ServerLdapContext.compare(ServerLdapContext.java:168)
at org.apache.ldap.server.protocol.support.CompareHandler.messageReceived(CompareHandler.java:61)
at org.apache.mina.handler.DemuxingIoHandler.messageReceived(DemuxingIoHandler.java:95)
at org.apache.ldap.server.protocol.LdapProtocolProvider$LdapProtocolHandler.messageReceived(LdapProtocolProvider.java:396)
at org.apache.mina.common.support.AbstractIoFilterChain$2.messageReceived(AbstractIoFilterChain.java:189)
at org.apache.mina.common.support.AbstractIoFilterChain.callNextMessageReceived(AbstractIoFilterChain.java:494)
at org.apache.mina.common.support.AbstractIoFilterChain.access$1000(AbstractIoFilterChain.java:52)
at org.apache.mina.common.support.AbstractIoFilterChain$EntryImpl$1.messageReceived(AbstractIoFilterChain.java:761)
at org.apache.mina.filter.LoggingFilter.messageReceived(LoggingFilter.java:87)
at org.apache.mina.common.support.AbstractIoFilterChain.callNextMessageReceived(AbstractIoFilterChain.java:494)
at org.apache.mina.common.support.AbstractIoFilterChain.access$1000(AbstractIoFilterChain.java:52)
at org.apache.mina.common.support.AbstractIoFilterChain$EntryImpl$1.messageReceived(AbstractIoFilterChain.java:761)
at org.apache.mina.filter.codec.ProtocolCodecFilter.messageReceived(ProtocolCodecFilter.java:91)
at org.apache.mina.common.support.AbstractIoFilterChain.callNextMessageReceived(AbstractIoFilterChain.java:494)
at org.apache.mina.common.support.AbstractIoFilterChain.access$1000(AbstractIoFilterChain.java:52)
at org.apache.mina.common.support.AbstractIoFilterChain$EntryImpl$1.messageReceived(AbstractIoFilterChain.java:761)
at org.apache.mina.filter.ThreadPoolFilter.processEvent(ThreadPoolFilter.java:665)
at org.apache.mina.filter.ThreadPoolFilter$Worker.processEvents(ThreadPoolFilter.java:421)
at org.apache.mina.filter.ThreadPoolFilter$Worker.run(ThreadPoolFilter.java:376)
Caused by: java.lang.ClassCastException: [B
at org.apache.ldap.server.partition.DefaultDirectoryPartitionNexus.compare(DefaultDirectoryPartitionNexus.java:369)
at org.apache.ldap.server.interceptor.InterceptorChain$1.compare(InterceptorChain.java:71)
at org.apache.ldap.server.interceptor.InterceptorChain$Entry$1.compare(InterceptorChain.java:975)
... 41 more
]; remaining name 'cn=Bob,ou=people'
at com.sun.jndi.ldap.LdapCtx.mapErrorCode(LdapCtx.java:3025)
at com.sun.jndi.ldap.LdapCtx.processReturnCode(LdapCtx.java:2931)
at com.sun.jndi.ldap.LdapCtx.processReturnCode(LdapCtx.java:2737)
at com.sun.jndi.ldap.LdapCtx.searchAux(LdapCtx.java:1803)
at com.sun.jndi.ldap.LdapCtx.c_search(LdapCtx.java:1731)
at com.sun.jndi.ldap.LdapCtx.c_search(LdapCtx.java:1748)
at com.sun.jndi.toolkit.ctx.ComponentDirContext.p_search(ComponentDirContext.java:394)
at com.sun.jndi.toolkit.ctx.PartialCompositeDirContext.search(PartialCompositeDirContext.java:376)
at com.sun.jndi.toolkit.ctx.PartialCompositeDirContext.search(PartialCompositeDirContext.java:358)
at javax.naming.directory.InitialDirContext.search(InitialDirContext.java:267)
at org.acegisecurity.providers.ldap.authenticator.PasswordComparisonAuthenticator.doPasswordCompare(PasswordComparisonAuthenticator.java:117)
at org.acegisecurity.providers.ldap.authenticator.PasswordComparisonAuthenticator.authenticate(PasswordComparisonAuthenticator.java:81)
at org.acegisecurity.providers.ldap.authenticator.PasswordComparisonAuthenticatorTests.testLdapCompareSucceedsWithCorrectPassword(PasswordComparisonAuthenticatorTests.java:35)

Client code is:

        SearchControls ctls = new SearchControls();
        ctls.setReturningAttributes(new String[0]);
        ctls.setSearchScope(SearchControls.OBJECT_SCOPE);

        String filter = "(userPassword={0})";
        NamingEnumeration results = ctx.search(dn, filter, new Object[]{password.getBytes()}, ctls);




 All   Comments   Work Log   Change History   Subversion Commits      Sort Order: Ascending order - Click to sort in descending order
Emmanuel Lecharny added a comment - 08/Dec/05 08:30 AM
Hi !

could you change the lines :

/// Line 360, file org.apache.ldap.server.partition.DefaultDirectoryPartitionNexus.java :

        /*
         * Get ahold of the normalizer for the attribute and normalize the request
         * assertion value for comparisons with normalized attribute values. Loop
         * through all values looking for a match.
         */
        Normalizer normalizer = attrType.getEquality().getNormalizer();
        String reqVal = ( String ) normalizer.normalize( value );
        for ( int ii = 0; ii < attr.size(); ii++ )
        {
            String attrVal = ( String ) normalizer.normalize( attr.get( ii ) );
            if ( attrVal.equals( reqVal ) )
            {
                return true;
            }
        }


by those lines in the class :

...
        /*
         * Get ahold of the normalizer for the attribute and normalize the request
         * assertion value for comparisons with normalized attribute values. Loop
         * through all values looking for a match.
         */
        Normalizer normalizer = attrType.getEquality().getNormalizer();
        Object reqVal = normalizer.normalize( value );
        
        for ( int ii = 0; ii < attr.size(); ii++ )
        {
         Object attrValObj = normalizer.normalize( attr.get( ii ) );
        
         if ( attrValObj instanceof String )
         {
         String attrVal = ( String ) attrValObj;
        
if ( attrVal.equals( reqVal ) )
{
return true;
}
         }
         else
         {
         byte[] attrVal = (byte[])attrValObj;
        
         return Arrays.equals( attrVal, (byte[])reqVal );
         }
        }
...


Dont' forget to add the import java.util.Arrays;


and tell us if it fixes your pb?

Tahnks a lot for the report !

Emmanuel Lecharny added a comment - 08/Dec/05 08:35 AM
Sorry, this was not perfect. Here is the code that should be substituted to the existing one :

...
        /*
         * Get ahold of the normalizer for the attribute and normalize the request
         * assertion value for comparisons with normalized attribute values. Loop
         * through all values looking for a match.
         */
        Normalizer normalizer = attrType.getEquality().getNormalizer();
        Object reqVal = normalizer.normalize( value );
        
        for ( int ii = 0; ii < attr.size(); ii++ )
        {
         Object attrValObj = normalizer.normalize( attr.get( ii ) );
        
         if ( attrValObj instanceof String )
         {
         String attrVal = ( String ) attrValObj;
        
if ( ( reqVal instanceof String) && attrVal.equals( reqVal ) )
{
return true;
}
         }
         else
         {
         byte[] attrVal = (byte[])attrValObj;
        
         if ( reqVal instanceof byte[] )
         {
         return Arrays.equals( attrVal, (byte[])reqVal );
         }
         }
        }
...

Luke Taylor added a comment - 08/Dec/05 11:03 PM
Not quite, 'cos reqVal is a String, so no comparison is made. If I covert it by changing the second block to
...
else
{
     byte[] attrVal = (byte[])attrValObj;

     if(reqVal instanceof String)
     {
          reqVal = ((String)reqVal).getBytes();
     }

     if ( reqVal instanceof byte[] )
     {
          return Arrays.equals( attrVal, (byte[])reqVal );
     }
}

then it works.

Thanks for the speedy response!

Emmanuel Lecharny added a comment - 08/Dec/05 11:48 PM
ok, I thought that reqVal was a byte[].

However, by bad.

But this is not enough ;) We have to get byte[] from the String supposing it's UTF-8 encoded :

...
         else
         {
         byte[] attrVal = (byte[])attrValObj;
        
         if ( reqVal instanceof byte[] )
         {
         return Arrays.equals( attrVal, (byte[])reqVal );
         }
         else if ( reqVal instanceof String )
         {
         return Arrays.equals( attrVal, StringUtils.getBytesUtf8( (String)reqVal ) );
         }
         }
...


Can you test this? It should work ok, even when using striings like "lécharny".

And if it's ok, the next thing I need is a patch, so that I can fix the code and commit it. I don not have any test case, so I can't commit it as is ;)

The best would be that you provide a testcase, so I can check the modification by myself !!

Thanks a lot, Luke.


Luke Taylor added a comment - 09/Dec/05 05:26 AM
Ok, the following test should do it when added to CompareAuthorizationTest in core-tests (probably not the best place but I can't find somewhere that just tests a compare operation):

    public void testPasswordCompare() throws NamingException {
        DirContext adminCtx = getContextAsAdmin();
        Attributes user = new BasicAttributes( "uid", "bob", true );
        user.put( "userPassword", "bobspassword".getBytes() );
        Attribute objectClass = new BasicAttribute( "objectClass" );
        user.put( objectClass );
        objectClass.add( "top" );
        objectClass.add( "person" );
        objectClass.add( "organizationalPerson" );
        objectClass.add( "inetOrgPerson" );
        user.put( "sn", "bob" );
        user.put( "cn", "bob" );
        adminCtx.createSubcontext( "uid=bob,ou=users", user );

        ServerLdapContext ctx = ( ServerLdapContext ) adminCtx.lookup( "" );
        assertTrue(ctx.compare(new LdapName( "uid=bob,ou=users,ou=system"), "userPassword", "bobspassword"));
    }

in the original example, the password *is* being passed as a byte array from the remote client, but somehow it ends up as a String in the compare method of DefaultDirectoryPartitionNexus. I don't know why that is or if it's correct behaviour.

Just running this test though, if I change the last line to

        assertTrue(ctx.compare(new LdapName( "uid=bob,ou=users,ou=system"), "userPassword", "bobspassword".getBytes()));

then it will arrive in the compare method as a byte array. The initial check on whether the attribute contains the value succeeds and none of the additional code we've discussed is called. So..... I don't know exactly what needs to be done :)

Emmanuel Lecharny added a comment - 09/Dec/05 06:37 AM
Well, there is another issue. When we decided that ApacheDS was supposed to handle internationnal charset (like French ;), we have changed a lot of things in the code.

Here, the compareMessage store the Attribute Value as a String, which is *BAD*. I have fixed it, but I need to test it a littel bit before committing it.

Consider that it's a side effect of a previous modification, which involved around 230 Kbyte of code ;)

btw, you should never use "xxx".getBytes(), because you will get a byte[] that will *not* necessary be UTF8 encoded. Just use the StringUtils.getBytesUtf8( "xxx" ) method in asn1-coec jar.

Emmanuel Lecharny added a comment - 09/Dec/05 07:19 AM
Ok,

I have patched and committed the code, it should work accordingly to what you are doing.

Could you just check it out and tell me if it's ok?

Thanks !


Luke Taylor added a comment - 09/Dec/05 07:45 AM
Yes it seems OK - my password-comparison tests are working now.

Thanks again for the quick response.

Emmanuel Lecharny added a comment - 20/Jan/06 07:41 AM
Fixed a month ago

Emmanuel Lecharny made changes - 20/Jan/06 07:41 AM
Field Original Value New Value
Status Open [ 1 ] Resolved [ 5 ]
Resolution Fixed [ 1 ]
Alex Karasulu made changes - 07/Feb/06 02:41 PM
Key DIRLDAP-77 DIRSERVER-228
Fix Version/s pre-1.0 [ 12310782 ]
Project Directory LDAP [ 10514 ] ApacheDS [ 12310260 ]
Affects Version/s pre-1.0 [ 12310782 ]
Component/s ldap [ 12310715 ]
Emmanuel Lecharny added a comment - 21/Apr/07 11:20 AM
Closing all issues created in 2005 and before which are marked resolved

Emmanuel Lecharny made changes - 21/Apr/07 11:20 AM
Status Resolved [ 5 ] Closed [ 6 ]