Wednesday, February 2, 2011

Resolving javax.naming.PartialResultException thrown by JBoss 5.1 LdapExtLoginModule

I recently hooked JBoss 5.1 to use our LDAP server for authentication and authorization process of the application I'm working on but for whatever reason, Web and EJB user authorization keeps failing though the authentication works beautifully. See JBoss LdapExtLoginModule for more info.


<login-module code="org.jboss.security.auth.spi.LdapExtLoginModule" flag="sufficient">
<module-option name="java.naming.factory.initial">com.sun.jndi.ldap.LdapCtxFactory</module-option>
<module-option name="java.naming.provider.url">ldap://ldaphost.company.com:389</module-option>
<module-option name="java.naming.security.authentication">simple</module-option>
<module-option name="bindDN">userToBindToLDAP</module-option>
<module-option name="bindCredential">password</module-option>

<module-option name="baseCtxDN">dc=company,dc=com</module-option>
<module-option name="baseFilter">(sAMAccountName={0})</module-option>

<module-option name="rolesCtxDN">dc=company,dc=com</module-option>
<module-option name="roleFilter">(sAMAccountName={0})</module-option>
<module-option name="roleAttributeID">memberOf</module-option>
<module-option name="roleAttributeIsDN">true</module-option>
<module-option name="roleNameAttributeID">cn</module-option>
<module-option name="roleRecursion">0</module-option>

<module-option name="searchTimeLimit">5000</module-option>
<module-option name="allowEmptyPasswords">false</module-option>
</login-module>

<login-module code="org.jboss.security.auth.spi.DatabaseServerLoginModule" flag="required">
<module-option name="dsJndiName">java:/DS_SECURITY</module-option>
<module-option name="principalsQuery">SELECT PASSWD_TX FROM REP WHERE upper(USER_ID)=upper(?)</module-option>
<module-option name="rolesQuery">SELECT ROLE_NM_TX,'Roles' FROM REP_ROLE WHERE upper(USER_ID)=upper(?)</module-option>
</login-module>


I turned JBoss logging to TRACE to find the root cause of the issue but unfortunately JBoss is reporting misleading exception error that has nothing to do with the credentials I'm using to bind to our LDAP server (I'm using jxplorer to confirm the user I'm binding to our LDAP is valid).
2011-01-15 08:48:47,804 TRACE [org.jboss.security.plugins.auth.JaasSecurityManagerBase.cap_corp_ldap_connection] (http-0.0.0.0-8080-1) Login failure
javax.security.auth.login.FailedLoginException: Password Incorrect/Password Required
at org.jboss.security.auth.spi.UsernamePasswordLoginModule.login(UsernamePasswordLoginModule.java:252)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at javax.security.auth.login.LoginContext.invoke(LoginContext.java:769)
at javax.security.auth.login.LoginContext.access$000(LoginContext.java:186)
at javax.security.auth.login.LoginContext$4.run(LoginContext.java:683)
at java.security.AccessController.doPrivileged(Native Method)
at javax.security.auth.login.LoginContext.invokePriv(LoginContext.java:680)
at javax.security.auth.login.LoginContext.login(LoginContext.java:579)
at org.jboss.security.plugins.auth.JaasSecurityManagerBase.defaultLogin(JaasSecurityManagerBase.java:553)
at org.jboss.security.plugins.auth.JaasSecurityManagerBase.authenticate(JaasSecurityManagerBase.java:487)
at org.jboss.security.plugins.auth.JaasSecurityManagerBase.isValid(JaasSecurityManagerBase.java:365)
at org.jboss.security.plugins.JaasSecurityManager.isValid(JaasSecurityManager.java:160)
at org.jboss.web.tomcat.security.JBossWebRealm.authenticate(JBossWebRealm.java:399)
at org.apache.catalina.authenticator.BasicAuthenticator.authenticate(BasicAuthenticator.java:181)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:491)
at org.jboss.web.tomcat.security.JaccContextValve.invoke(JaccContextValve.java:95)
at org.jboss.web.tomcat.security.SecurityContextEstablishmentValve.process(SecurityContextEstablishmentValve.java:126)
at org.jboss.web.tomcat.security.SecurityContextEstablishmentValve.invoke(SecurityContextEstablishmentValve.java:70)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
at org.jboss.web.tomcat.service.jca.CachedConnectionValve.invoke(CachedConnectionValve.java:158)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:330)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:829)
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:598)
at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:451)
at java.lang.Thread.run(Thread.java:619)

This issue drove me crazy the whole morning in the office trying to pin down the root cause, grrr...

As I'm about to go for lunch, I thought why not one more try but instead use JUnit to see if I'll get more details of the error. It turns out that I'm right. Cool... I'm back in business.

javax.security.auth.login.FailedLoginException: Password Incorrect/Password Required
at org.jboss.security.auth.spi.UsernamePasswordLoginModule.login(UsernamePasswordLoginModule.java:213)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at javax.security.auth.login.LoginContext.invoke(LoginContext.java:769)
at javax.security.auth.login.LoginContext.access$000(LoginContext.java:186)
at javax.security.auth.login.LoginContext$4.run(LoginContext.java:683)
at java.security.AccessController.doPrivileged(Native Method)
at javax.security.auth.login.LoginContext.invokePriv(LoginContext.java:680)
at javax.security.auth.login.LoginContext.login(LoginContext.java:579)
... 18 more
Caused by: javax.naming.PartialResultException: Unprocessed Continuation Reference(s); remaining name 'dc=company,dc=com'
at com.sun.jndi.ldap.LdapCtx.processReturnCode(LdapCtx.java:2820)
at com.sun.jndi.ldap.LdapCtx.processReturnCode(LdapCtx.java:2794)
at com.sun.jndi.ldap.LdapNamingEnumeration.getNextBatch(LdapNamingEnumeration.java:129)
at com.sun.jndi.ldap.LdapNamingEnumeration.hasMoreImpl(LdapNamingEnumeration.java:198)
at com.sun.jndi.ldap.LdapNamingEnumeration.hasMore(LdapNamingEnumeration.java:171)
at org.jboss.security.auth.spi.LdapExtLoginModule.rolesSearch(LdapExtLoginModule.java:424)
at org.jboss.security.auth.spi.LdapExtLoginModule.createLdapInitContext(LdapExtLoginModule.java:351)
at org.jboss.security.auth.spi.LdapExtLoginModule.validatePassword(LdapExtLoginModule.java:232)
at org.jboss.security.auth.spi.UsernamePasswordLoginModule.login(UsernamePasswordLoginModule.java:210)
... 27 more


So the issue has nothing to do with the credentials I'm binding to LDAP (which I already know) but rather a partial return set back to client. To resolve this, I had to inject another line on the config.
<module-option name="java.naming.referral">follow</module-option>


Phew!!! What a morning to start the day... Here's the complete configuration.


<login-module code="org.jboss.security.auth.spi.LdapExtLoginModule" flag="sufficient">
<module-option name="java.naming.factory.initial">com.sun.jndi.ldap.LdapCtxFactory</module-option>
<module-option name="java.naming.provider.url">ldap://ldaphost.company.com:389</module-option>
<module-option name="java.naming.security.authentication">simple</module-option>
<module-option name="java.naming.referral">follow</module-option>
<module-option name="bindDN">userToBindToLDAP</module-option>
<module-option name="bindCredential">password</module-option>

<module-option name="baseCtxDN">dc=company,dc=com</module-option>
<module-option name="baseFilter">(sAMAccountName={0})</module-option>

<module-option name="rolesCtxDN">dc=company,dc=com</module-option>
<module-option name="roleFilter">(sAMAccountName={0})</module-option>
<module-option name="roleAttributeID">memberOf</module-option>
<module-option name="roleAttributeIsDN">true</module-option>
<module-option name="roleNameAttributeID">cn</module-option>
<module-option name="roleRecursion">0</module-option>

<module-option name="searchTimeLimit">5000</module-option>
<module-option name="allowEmptyPasswords">false</module-option>
</login-module>

<login-module code="org.jboss.security.auth.spi.DatabaseServerLoginModule" flag="required">
<module-option name="dsJndiName">java:/DS_SECURITY</module-option>
<module-option name="principalsQuery">SELECT PASSWD_TX FROM REP WHERE upper(USER_ID)=upper(?)</module-option>
<module-option name="rolesQuery">SELECT ROLE_NM_TX,'Roles' FROM REP_ROLE WHERE upper(USER_ID)=upper(?)</module-option>
</login-module>



Tuesday, September 7, 2010

Generating Salesforce Enterprise WS client stubs using JAX-WS

I've seen a lot of posts online but found few direct hits that address our technical need to consume Salesforce Enterprise wsdl into our JBoss 5 server. And since JBoss 4.2 (and above) runs JBossWS under the hood, which natively supports JAX-RPC and JAX-WS web service stack, I think it is such a good idea to post the steps I've done to accomplish this as reference for others (and myself the next time I have the need to refresh my application with the updated Salesforce Enterprise wsdl from our org).

I'll be generating the client stubs using the command line approach but on my next blog I'll show how using SoapUI. BTW, I'm using Linux Fedora 12 but you can easily follow and execute these steps on Windows DOS command line.

1) Download JAX-WS from here, if you don't have it yet. I extracted the zip file in /home/gio/Downloads/JAXWS2.2.1-20100617/jaxws-ri

2) Login to Salesforce with your credentials. Go to Setup, App Setup, Develop, API then click Generate Enterprise WSDL. Save your "Salesforce Enterprise wsdl" on the same jaxws-ri directory extracted from above. I named the wsdl file SF_Enterprise.wsdl.

3) Create the JAXWS-JAXB binding xml file in same jaxws-ri directory cotaining the code below (this is to bind XMLSchema to Java). I saved this JAXB binding file sf-jaxws-bindings-enterprise.xml. Change the targetNamespace accordingly based on the version you're using (look at the Salesforce wsdl).

<bindings
xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://java.sun.com/xml/ns/jaxws">
<bindings node="//xsd:schema[@targetNamespace='urn:enterprise.soap.sforce.com']">
<jaxb:globalBindings underscoreBinding="asCharInWord" />
<jaxb:schemaBindings>
<jaxb:nameXmlTransform>
<jaxb:typeName suffix="Type" />
</jaxb:nameXmlTransform>
</jaxb:schemaBindings>
</bindings>
<enableWrapperStyle>false</enableWrapperStyle>
<enableAsyncMapping>false</enableAsyncMapping>
</bindings>

4) Create "gen-src" target directory for your generated client stubs in same jaxws-ri directory.

5) You should endup with the file structure similar to this image.

6) Optionally, change the java heap size to at least 512m so you have a buffer and hopefully never run into heap size error (just in case you have lots of custom objects in Salesforce, which we do in our org). Find the wsimport.sh/wsimport.bat and add -Xmx512m accordingly.
a) On Linux, ../bin/wsimport.sh --> exec "$JAVA" -Xmx512m $WSIMPORT_OPTS -jar "$JAXWS_HOME/lib/jaxws-tools.jar" "$@"

b) On Windows, ../bin/wsimport.bat --> %JAVA% -Xmx512m %WSIMPORT_OPTS% -jar "%JAXWS_HOME%\lib\jaxws-tools.jar" %*

7) Open up command line and type-in the following
bin/wsimport.sh -verbose -keep -p com.company.myapp.ws.sf.client -b sf-jaxws-bindings-enterprise.xml -s gen-src SF_Enterprise.wsdl -XadditionalHeaders -Xnocompile -Xendorsed

You might asked, what? Ok, here's what these parameters are for
-verbose - so that I see the output from the compiler as it runs
-keep - to keep the generated files
-p - the target package of generated java source codes (will be under package com.company.myapp.ws.sf.client)
-b - jaxws/jaxb binding files for binding XML schema to java representation (step #3)
-s - target directory where the compiler will dump all generated java source codes (step #4)
-XadditionalHeaders - map headers not bound to request/response message to java method parameters
-Xnocompile - don't compile the generated java codes into classes.
-Xendorsed - I'm running JDK6 on my Fedora 12 and the JAXWS 2.2.1 I downloaded requires the latest version JAXWS 2.2 API therefore I have to use the "endorsed" API from JAXWS 2.2 rather than from my own JDK6. See this site for more details.

8) Voila, you now have java client stubs you can inject into your JBoss application project.