1

I've used the Apache Directory API to load in user data from an Active Directory. It's similar to the example code by 'oers' found in this stackoverflow question: Apache Directory LDAP - Paged searches

Some differences I have are:

  • I'm coding it with in Nashorn (Javascript running in Java)
  • I'm using the PagedResultsImpl class instead of PagedResultsDecorator
  • I'm saving the cookie as a string between calls by using a Base64 encoding of the byte[] cookie.
  • I'm using Apache Directory API with the following maven import:

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

Here's some of the important bits:

...
var pageCursor = "";  /** or some Base64 encoded byte[] cookie if I'm trying to start from where i left off last time. */
...
pagedSearchControl = new PagedResultsImpl();
pagedSearchControl.setSize(pageSize);
pagedSearchControl.setCookie(Base64.getEncoder().encodeToString(pageCursor.getBytes()));
...
var searchRequest = new SearchRequestImpl();
...
searchRequest.addControl(pagedSearchControl);
...
var cursor = new EntryCursorImpl(connection.search(searchRequest));
...
pagingResults = cursor.getSearchResultDone().getControl(PagedResults.OID);
if (pagingResults != null){
    nextPageCursor = Base64.getEncoder().encodeToString(pagingResults.getCookie());
}

When run against Active Directory I can page all the users just fine. When I change my connection to point to an OpenLDAP directory I am able to search for users just fine, EXCEPT for when I set the page control cookie using a non-null value for pageCursor (one I got from base 64 encoding a previous call's cookie). Basically, I can't get the paged results from OpenLDAP. I get no results and there are no exceptions.

  • What is needed in order to get OpenLDAP to page properly?
  • Am I missing something with setting up page control in Apache Directory?
  • Is paging a setting in OpenLDAP I need to turn on?

[UPDATE] I Ported my code outside of Nashorn to just Java and now I can see it is because the paging cookie appears to be only valid within the same connection for OpenLDAP, but for Active Directory it can be different connections. You can see yourself with the code below.:

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Base64;
import java.util.Iterator;
import java.util.Properties;
import java.util.StringJoiner;

import javax.naming.ConfigurationException;

//imports for json
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;

//imports for LDAP
import org.apache.directory.api.ldap.model.cursor.EntryCursor;
import org.apache.directory.api.ldap.model.entry.Attribute;
import org.apache.directory.api.ldap.model.entry.DefaultEntry;
import org.apache.directory.api.ldap.model.entry.Entry;
import org.apache.directory.api.ldap.model.entry.Value;
import org.apache.directory.api.ldap.model.exception.LdapException;
import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
import org.apache.directory.api.ldap.model.message.Control;
import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
import org.apache.directory.api.ldap.model.message.SearchRequest;
import org.apache.directory.api.ldap.model.message.SearchRequestImpl;
import org.apache.directory.api.ldap.model.message.SearchResultDone;
import org.apache.directory.api.ldap.model.message.SearchScope;
import org.apache.directory.api.ldap.model.message.controls.AbstractControl;
import org.apache.directory.api.ldap.model.message.controls.PagedResults;
import org.apache.directory.api.ldap.model.message.controls.PagedResultsImpl;
import org.apache.directory.api.ldap.model.name.Dn;
import org.apache.directory.ldap.client.api.EntryCursorImpl;
import org.apache.directory.ldap.client.api.LdapConnection;
import org.apache.directory.ldap.client.api.LdapNetworkConnection;

//Nashorn
import delight.nashornsandbox.NashornSandbox;
import delight.nashornsandbox.NashornSandboxes;




public class Executor 
{



    public static void main( String[] args )
    {    
        pagingTest();
    }


 private static void pagingTest(){

        //ENTER YOUR CREDENTIALS
        String server = "";
        int port = 0;
        String loginId = "";
        String loginPassword = "";
        String usersBaseDN = "";
        String userObjectClass = "";


            String base64cookie = null;
        LdapNetworkConnection connection = new LdapNetworkConnection(server, port,  true );

        try{
            connection.setTimeOut( 300 );
            connection.bind(loginId,loginPassword);


            /*First Pass*/
            PagedResultsImpl pagedSearchControl = new PagedResultsImpl();
            pagedSearchControl.setSize(5);
            pagedSearchControl.setCookie(Base64.getDecoder().decode(""));
            SearchRequestImpl searchRequest = new SearchRequestImpl();
            searchRequest.setBase(new Dn(usersBaseDN));
            searchRequest.setFilter("(objectClass=" +  userObjectClass +")");
            searchRequest.setScope(SearchScope.SUBTREE);
            searchRequest.addAttributes("*");
            searchRequest.addControl(pagedSearchControl);

            EntryCursorImpl cursor = new EntryCursorImpl(connection.search(searchRequest));
            while (cursor.next()) {
                System.out.println("First Pass User: " + cursor.get().getDn());
            }
            PagedResults pagingResults = (PagedResults)cursor.getSearchResultDone().getControl(PagedResults.OID);
            if (pagingResults != null){
                byte[] cookie = pagingResults.getCookie();
                if (cookie != null && cookie.length > 0) {
                    base64cookie = Base64.getEncoder().encodeToString(cookie);
                    System.out.println("First Pass Cookie: "  + cookie);
                }
            }
            cursor.close();

        }catch(Exception e){
            System.out.println(e);
        }
        finally {

            //COMMENT THIS CODE BLOCK TO SEE IT WORKING (IT WILL USE THE SAME CONNECTION)
            try {
                connection.unBind();
                connection.close();
            } catch (Exception e) {
                System.out.println(e);
            }
            connection = new LdapNetworkConnection( server, port,  true );
        }



        try{
            connection.setTimeOut( 300 );
            connection.bind(loginId,loginPassword);
            /*Second Pass*/
            PagedResultsImpl secondPagedSearchControl = new PagedResultsImpl();
            secondPagedSearchControl.setSize(5);
            secondPagedSearchControl.setCookie(Base64.getDecoder().decode(base64cookie));
            SearchRequestImpl secondSearchRequest = new SearchRequestImpl();
            secondSearchRequest.setBase(new Dn(usersBaseDN));
            secondSearchRequest.setFilter("(objectClass=" +  userObjectClass +")");
            secondSearchRequest.setScope(SearchScope.SUBTREE);
            secondSearchRequest.addAttributes("*");
            secondSearchRequest.addControl(secondPagedSearchControl);

            EntryCursorImpl secondCursor = new EntryCursorImpl(connection.search(secondSearchRequest));
            while (secondCursor.next()) {
                System.out.println("Second Pass User: " + secondCursor.get().getDn());
            }
            secondCursor.close();

        }catch(Exception e){
            System.out.println(e);
        }
        finally {
            try {
                connection.unBind();
                connection.close();
            } catch (Exception e) {
                System.out.println(e);
            }
        }

    }
}

So I guess my new question is:

  • Is anybody aware of a workaround to allow OpenLDAP to accept pagination cookies from previous connections?
fei0x
  • 4,259
  • 4
  • 17
  • 37
  • Can you show the actual (whole) code that runs properly against AD ? – EricLavault Aug 30 '19 at 13:33
  • Unfortunately, it's pretty coupled with the rest of my project which I can't share. I'll see if I can write a smaller test. – fei0x Aug 30 '19 at 17:28
  • hmm... ported the code to Java... and it worked with both directories. i've pasted the code in the post. – fei0x Aug 30 '19 at 18:09
  • Now I've reproduced my error in Java. the OpenLDAP cookie is only good within the same connection. When I reset the connection the cookie no longer works. I wonder if there is a way to make OpenLDAP accept page cookies from old connections? – fei0x Aug 30 '19 at 20:38
  • No there isn't. The operation doesn't make sense. Otherwise the server would have to retain the state of every pagination search that had ever been performed in case somebody came back ten years later with an old search state cookie. Use the same connection. – user207421 Aug 30 '19 at 23:26
  • I wonder why you would stop iterating paged results from the first connection, but if you have a logic for distinguishing page states from previous requests it would be up to the application to implement it, the ldap server cannot guess which request from which user should be kept when the application disconnect, just use a variable (like a hashmap ) from your application, and if set from previous calls just ignore the results until you reach that page state. – EricLavault Aug 31 '19 at 07:31
  • @user207421 - That is quiet the exaggeration. Most modern systems (particularily web services based ones) store either the last created paging token or hold tokens for a few hours. Clearly Microsoft thought it was important to implement it in their solution. – fei0x Sep 01 '19 at 13:35
  • @EricLavault - In our software we've developed generalized paging management to deal with a variety of systems. Usually we're just dealing with web services so there's not much connecting to do. Generally speaking if the 'connection' is cut at any time we can resume off the last page and keep going. Directories can have many thousands of users and groups and if the connection is cut for whatever reason, that would be frustrating to have to start over. Unless I can look at spoofing the connection somehow, I don't think it's worth re-engineering our paging system for one connector at this time. – fei0x Sep 01 '19 at 13:49

0 Answers0