Search code examples
javaldaposgiaemmaven-dependency

Programmatically connect LDAP and authenticate credentials in AEM


I want to connect to LDAP programmatically in AEM using maven dependency which resolves in OSGi

Approaches and subsequent issues faced:-

1. Cannot use

@Reference
private ExternalIdentityProviderManager externalIdentityProviderManager;

final String externalId = request.getParameter("externalId");
final String externalPassword = request.getParameter("externalPassword");

final ExternalIdentityProvider idap = externalIdentityProviderManager.getProvider("ldap");
final SimpleCredentials credentials = new SimpleCredentials(externalId, externalPassword.toCharArray());
final ExternalUser externalUser = idap.authenticate(credentials);

as this Identity provider config is only present in author environment and not in publish servers(as per req).

2. Trying to use

<dependency>
    <groupId>org.apache.directory.api</groupId>
    <artifactId>api-ldap-client-api</artifactId>
    <version>2.0.0.AM4</version>
</dependency>

to resolve dependencies. It resolve my compile time errors but this is not an 'osgi ready' library, hence couldn't be installed in OSGi. If done so manually it has further unresolved dependencies.

Code reference for this approach - https://directory.apache.org/api/user-guide/2.1-connection-disconnection.html & https://directory.apache.org/api/user-guide/2.10-ldap-connection-template.html

3. I've also tried to use

String rootDN = "uid=admin,ou=system";
String rootPWD = "secret";
Hashtable < String, String > environment = new Hashtable < String, String > ();
environment.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
environment.put(Context.PROVIDER_URL, "ldap://localhost:10389");
environment.put(Context.SECURITY_AUTHENTICATION, "simple");
environment.put(Context.SECURITY_PRINCIPAL, rootDN);
environment.put(Context.SECURITY_CREDENTIALS, rootPWD);
DirContext dirContext = null;
NamingEnumeration < ? > results = null;
dirContext = new InitialDirContext(environment);
SearchControls controls = new SearchControls();
controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
String userId = "abhishek";
String userPwd = "{SSHA}ip/DD+zUhv22NH3wE1dvJN7oauYE4TYQ3ziRtg=="; //"apple";
String filter = "(&(objectclass=person)(uid=" + userId + ")(userPassword=" + userPwd + "))";
results = dirContext.search("", filter, controls);
if(results.hasMore()) {
   System.out.println("User found");
} else {
   System.out.println("User not found");
}

It has 2 issues - a) It works fine when tested as plain Java class in main method on class load, but when attempted to integrate in AEM/osgi service class, it throws -

javax.naming.NotContextException: Not an instance of DirContext at javax.naming.directory.InitialDirContext.getURLOrDefaultInitDirCtx(InitialDirContext.java:111) at javax.naming.directory.InitialDirContext.search(InitialDirContext.java:267)

b) Even in plain Java class, i had to provide the hashed password to validate, which would be difficult to integrate.

String userPwd = "{SSHA}ip/DD+zUhv22NH3wE1dvJN7oauYE4TYQ3ziRtg==";//"apple";

Can someone provide me any maven dependency/library that can integrate with osgi and resolve dependency as well as i don't need to provide hashed password to validate user credentials? Any approach that may resolve these issues?


Solution

  • Step 1: Add these dependencies in project pom

    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
        <version>2.6.2</version>
    </dependency>
    <dependency>
        <groupId>org.apache.directory.api</groupId>
        <artifactId>api-all</artifactId>
        <version>1.0.0-RC2</version>
    </dependency>
    <dependency>
        <groupId>org.apache.mina</groupId>
        <artifactId>mina-core</artifactId>
        <version>2.1.3</version>
    </dependency>
    <dependency>
        <groupId>commons-pool</groupId>
        <artifactId>commons-pool</artifactId>
        <version>1.6</version>
    </dependency>
    <dependency>
        <groupId>antlr</groupId>
        <artifactId>antlr</artifactId>
        <version>2.7.7</version>
    </dependency>
    

    Step 2: Add them to bundle pom

    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
    </dependency>
    <dependency>
        <groupId>org.apache.directory.api</groupId>
        <artifactId>api-all</artifactId>
    </dependency>
    <dependency>
        <groupId>org.apache.mina</groupId>
        <artifactId>mina-core</artifactId>
    </dependency>
    <dependency>
        <groupId>commons-pool</groupId>
        <artifactId>commons-pool</artifactId>
    </dependency>
    <dependency>
        <groupId>antlr</groupId>
        <artifactId>antlr</artifactId>
    </dependency>
    

    Step 3: In bundle pom at the plugin description

    <plugin>
        <groupId>org.apache.felix</groupId>
        <artifactId>maven-bundle-plugin</artifactId>
        <extensions>true</extensions>
        <configuration>
            <instructions>
                <Import-Package>!net.sf.cglib.proxy, javax.inject;version=0.0.0,*</Import-Package>
                <Export-Package />
                <Sling-Model-Packages></Sling-Model-Packages>
                <Bundle-SymbolicName></Bundle-SymbolicName>
                 <Embed-Dependency>antlr, mina-core, api-all, commons-pool, commons-pool2</Embed-Dependency>
            </instructions>
        </configuration>
    </plugin>
    

    Use these for the above mentioned plugin

    <Import-Package>!net.sf.cglib.proxy</Import-Package>
    <Embed-Dependency>antlr, mina-core, api-all, commons-pool, commons-pool2</Embed-Dependency>
    

    Step 4: Imports are specifics and works only when

    <dependency>
        <groupId>org.apache.directory.api</groupId>
        <artifactId>api-all</artifactId>
        <version>1.0.0-RC2</version>
    </dependency>
    

    is used. As there are some other dependencies which provides packages/class but they don't work at some point or the other.

    import org.apache.directory.api.ldap.model.message.SearchScope;
    import org.apache.directory.ldap.client.api.DefaultPoolableLdapConnectionFactory;
    import org.apache.directory.ldap.client.api.LdapConnectionConfig;
    import org.apache.directory.ldap.client.api.LdapConnectionPool;
    import org.apache.directory.ldap.client.template.LdapConnectionTemplate;
    import org.apache.directory.ldap.client.template.PasswordWarning;
    import org.apache.directory.ldap.client.template.exception.PasswordException;
    
    private String ldapAuthenticationApacheDsFlow(final SlingHttpServletRequest request) {
        String status = "";
        try {
            LdapConnectionConfig config = new LdapConnectionConfig();
            config.setLdapHost("localhost");
            config.setLdapPort(10389);
            config.setName("uid=admin,ou=system");
            config.setCredentials("secret");
            final DefaultPoolableLdapConnectionFactory factory = new DefaultPoolableLdapConnectionFactory(config);
            final LdapConnectionPool pool = new LdapConnectionPool(factory);
            pool.setTestOnBorrow(true);
            final LdapConnectionTemplate ldapConnectionTemplate = new LdapConnectionTemplate(pool);
            final String uid = request.getParameter("externalId");
            final String password = request.getParameter("externalPassword");
            final PasswordWarning warning = ldapConnectionTemplate.authenticate(
                    "ou=Users,dc=example,dc=com", "(uid=" + uid +")", SearchScope.SUBTREE,  password.toCharArray());
            status = "User credentials authenticated";
            if(warning != null) {
                status = status + " \n Warning!!" +warning.toString();
            }
        } catch(final PasswordException e) {
            status = e.toString();
            e.printStackTrace();
        }
        return status;
    }
    

    If no error is thrown at final PasswordWarning warning = user credentials are successfully validated.