Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

two bugfixes and paged LDAP queries #9

Merged
merged 5 commits into from

2 participants

@huancz

I have tried to put enough information into individual commit messages.

SearchPaged function probably looks bit weird - I didn't want to disrupt your API too much, otherwise it could be merged into Search itself (I'll gladly do it myself if we agree breaking interface is not an issue).

I tested it on AD (running on Server 2008 R2).

@jeremycx jeremycx merged commit 9625f5d into from
@jeremycx
Owner

It all looks great! Thanks for the work.

There's a request open now for 0.6 support - feel like taking a stab at it? I'm swamped and likely will not have any time for porting to 0.6 for a couple of weeks, at least.

@huancz

Thanks for the merge.

Bad thing is, I only tried if it works in the good case, all LDAP servers I have access to DO support paged queries. Now that I have read the patch again, I found that I left couple of "TODO: throw" comments on the error paths (not to mention that throw is not correct response there, it should call the callback with an error flag). I'll fix it tomorrow.

As for the 0.6, I have loads of work too, and we don't aim for >0.5 for now, we still have some code that relies on the old behavior of require.paths. I may get to it, but in similar timeframe to yours - somewhere between couple of weeks and when debian forces us to move to 0.6.

@jeremycx
Owner

Can't blame me for trying to get someone else to do my work for me! :-)

Look forward to the proper throws, etc. I'll merge as soon as I get a pull req. Thanks again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Nov 12, 2011
  1. allow async wait on anonymous bind

    Petr Běhan authored
  2. SIGSEGV on unknown/unexpected server message

    Petr Běhan authored
  3. rewrite search to use non-deprecated ldap interface

    Petr Běhan authored
Commits on Nov 14, 2011
  1. change: support for paged queries (wip)

    Petr Běhan authored
    missing more testing in the field
Commits on Dec 1, 2011
  1. update documentation

    Petr Běhan authored
This page is out of date. Refresh to see the latest.
Showing with 139 additions and 10 deletions.
  1. +28 −1 LDAP.js
  2. +20 −2 README.md
  3. +91 −7 src/LDAP.cc
View
29 LDAP.js
@@ -43,9 +43,22 @@ var Connection = function() {
self.setCallback(msgid, CB);
};
+ self.searchPaged = function(base, scope, filter, attrs, pageSize, CB, cookie) {
+ // leave cookie empty when calling from client code
+ var msgid = binding.search(base, scope, filter, attrs, pageSize, cookie);
+ self.setCallback(msgid, CB);
+ var cb = callbacks[msgid];
+ cb.base = base;
+ cb.scope = scope;
+ cb.filter = filter;
+ cb.attrs = attrs;
+ cb.pageSize = pageSize;
+ }
+
self.simpleBind = function(binddn, password, CB) {
var msgid;
- if (arguments.length === 0) {
+ if (arguments.length === 1 && typeof arguments[0] == 'function') {
+ CB = arguments[0];
msgid = binding.simpleBind();
} else {
msgid = binding.simpleBind(binddn, password);
@@ -80,6 +93,20 @@ var Connection = function() {
}
});
+ binding.addListener("searchresultpaged", function(msgid, result, data, cookie) {
+ var cb = callbacks[msgid];
+ if (cb) {
+ clearTimeout(cb.tm);
+ cb(msgid, null, data);
+ if (cookie) {
+ self.searchPaged(cb.base, cb.scope, cb.filter, cb.attrs, cb.pageSize, cb, cookie);
+ } else {
+ cb(msgid, null, null);
+ }
+ delete(callbacks[msgid]);
+ }
+ });
+
binding.addListener("result", function(msgid, result) {
// result contains the LDAP response type. It's unused.
if (callbacks[msgid]) {
View
22 README.md
@@ -47,18 +47,20 @@ Open Example
throw new Error("Unable to connect to server");
}
-Connection.SimpleBind(dn, password, callback(msgid, err));
+Connection.SimpleBind([dn, password,] callback(msgid, err));
-----------------------------------
Authenticates to the server. When the response is ready, or the
timeout occurs, will execute the callback with the error value set.
+dn and password must be omited, when doing anonymous bind.
+
Connection.Search(base, scope, filter, attrs, function(msgid, err, data))
---------------------------------------------
Searches LDAP within the given base for entries matching the given
filter, and returns all attrs for matching entries. To get all
-available attrs, use "*".
+available attrs, use "\*".
Scopes are specified as one of the following integers:
@@ -74,6 +76,22 @@ Connection.Open() will return -1.
See also "man 3 ldap" for details.
+Connection.SearchPaged(base, scope, filter, attrs, pageSize, function(msgid, err, data) [, cookie])
+---------------------------------------------------------------------------------------------------
+
+LDAP servers are usually limited in how many items they are willing to return - 1024 or 4096 are
+some typical values. For larger LDAP directories, you need to either partition your results
+with filter, or use paged search.
+
+Note that it's only extension to the protocol, server doesn't have to support it. In such
+case, callback will be called with nonzero err (actually, it would be nice if someone could
+verify this, the server it was tested on had this feature).
+
+Cookie parameter is only for internal use, leave it undefined in your calls.
+
+Results are passed to callback function as they arrive in the same format as for simple Search.
+Request for next page is sent only after the callback returns. After all data has arrived,
+callback is called once more, with data equal to null.
Search Example
--------------
View
98 src/LDAP.cc
@@ -15,10 +15,13 @@ using namespace v8;
static Persistent<String> symbol_connected;
static Persistent<String> symbol_disconnected;
static Persistent<String> symbol_search;
+static Persistent<String> symbol_search_paged;
static Persistent<String> symbol_error;
static Persistent<String> symbol_result;
static Persistent<String> symbol_unknown;
+static Persistent<ObjectTemplate> cookie_template;
+
struct timeval ldap_tv = { 0, 0 }; // static struct used to make ldap_result non-blocking
#define REQ_FUN_ARG(I, VAR) \
@@ -95,8 +98,13 @@ class LDAPConnection : public EventEmitter
symbol_connected = NODE_PSYMBOL("connected");
symbol_disconnected = NODE_PSYMBOL("disconnected");
symbol_search = NODE_PSYMBOL("searchresult");
+ symbol_search_paged = NODE_PSYMBOL("searchresultpaged");
symbol_error = NODE_PSYMBOL("error");
symbol_result = NODE_PSYMBOL("result");
+ symbol_unknown = NODE_PSYMBOL("unknown");
+
+ cookie_template = Persistent<ObjectTemplate>::New( ObjectTemplate::New() );
+ cookie_template->SetInternalFieldCount(1);
target->Set(String::NewSymbol("LDAPConnection"), s_ct->GetFunction());
}
@@ -165,9 +173,13 @@ class LDAPConnection : public EventEmitter
NODE_METHOD(Search) {
HandleScope scope;
GETOBJ(c);
- int fd, msgid;
+ int fd, msgid, rc;
char * attrs[255];
char ** ap;
+ LDAPControl* serverCtrls[2] = { NULL, NULL };
+ int page_size = 0;
+ v8::Local<v8::Object> cookieObj;
+ struct berval* cookie = NULL;
//base scope filter attrs
ENFORCE_ARG_LENGTH(4, "Invalid number of arguments to Search()");
@@ -181,8 +193,31 @@ class LDAPConnection : public EventEmitter
ARG_STR(filter, 2);
ARG_STR(attrs_str, 3);
+ if (args.Length() >= 5) {
+ ENFORCE_ARG_NUMBER(4);
+ page_size = args[4]->Int32Value();
+ if (args.Length() >= 6) {
+ if (args[5]->IsObject()) {
+ cookieObj = args[5]->ToObject();
+ if (cookieObj->InternalFieldCount() != 1
+ || (cookie = reinterpret_cast<berval*>(cookieObj->GetPointerFromInternalField(0)) ) == NULL)
+ {
+ // TODO throw
+ }
+ // TODO: check that parameters match to those passed to first call
+ cookieObj->SetPointerInInternalField(0, NULL);
+ } else {
+ // TODO throw
+ }
+ }
+ }
+
if (c->ld == NULL) {
c->Emit(symbol_disconnected, 0, NULL);
+ if (cookie) {
+ ber_bvfree(cookie);
+ cookie = NULL;
+ }
RETURN_INT(LDAP_SERVER_DOWN);
}
@@ -194,7 +229,30 @@ class LDAPConnection : public EventEmitter
if (++ap >= &attrs[255])
break;
- if ((msgid = ldap_search(c->ld, *base, searchscope, *filter, attrs, 0)) >= 0) {
+ if (page_size > 0) {
+ rc = ldap_create_page_control(c->ld, page_size, cookie, 'F',
+ &serverCtrls[0]);
+
+ if (cookie) {
+ ber_bvfree(cookie);
+ cookie = NULL;
+ }
+ if (rc != LDAP_SUCCESS) {
+ abort();
+ // TODO: free other stuff, throw ?
+ }
+
+ }
+
+ rc = ldap_search_ext(c->ld, *base, searchscope, *filter, attrs, 0,
+ serverCtrls, NULL, NULL, 0, &msgid);
+
+ if (serverCtrls[0]) {
+ ldap_control_free(serverCtrls[0]);
+ }
+ if (LDAP_API_ERROR(rc)) {
+ msgid = -1;
+ } else {
ldap_get_option(c->ld, LDAP_OPT_DESC, &fd);
ev_io_set(&(c->read_watcher_), fd, EV_READ);
ev_io_start(EV_DEFAULT_ &(c->read_watcher_));
@@ -474,7 +532,9 @@ class LDAPConnection : public EventEmitter
HandleScope scope;
LDAPConnection *c = static_cast<LDAPConnection*>(w->data);
LDAPMessage *ldap_res;
+ LDAPControl** srv_controls;
Handle<Value> args[3];
+ int op;
int res;
int msgid;
int error;
@@ -494,19 +554,21 @@ class LDAPConnection : public EventEmitter
c->Emit(symbol_disconnected, 0, NULL);
return;
}
+ op = res;
msgid = ldap_msgid(ldap_res);
- error = ldap_result2error(c->ld, ldap_res, 0);
+ res = ldap_parse_result(c->ld, ldap_res, &error, NULL, NULL, NULL,
+ &srv_controls, 0);
args[0] = Integer::New(msgid);
args[1] = Local<Value>::New(Integer::New(res));
- if (error) {
+ if (res != LDAP_SUCCESS || error) {
args[1] = Integer::New(error);
args[2] = Local<Value>::New(String::New(ldap_err2string(error)));
c->Emit(symbol_error, 3, args);
} else {
- switch(res) {
+ switch(op) {
case LDAP_RES_BIND:
case LDAP_RES_MODIFY:
case LDAP_RES_MODDN:
@@ -516,7 +578,26 @@ class LDAPConnection : public EventEmitter
case LDAP_RES_SEARCH_RESULT:
args[2] = c->parseReply(c, ldap_res);
- c->Emit(symbol_search, 3, args);
+ if (!srv_controls) {
+ c->Emit(symbol_search, 3, args);
+ break;
+ }
+ {
+ struct berval* cookie = NULL;
+ ldap_parse_page_control(c->ld, srv_controls, NULL, &cookie);
+ if (!cookie || cookie->bv_val == NULL || !*cookie->bv_val) {
+ if (cookie) {
+ ber_bvfree(cookie);
+ }
+ args[3] = v8::Undefined();
+ } else {
+ Local<Object> cookieObj(cookie_template->NewInstance());
+ cookieObj->SetPointerInInternalField(0, cookie);
+ cookieObj->Set(v8::String::New("cookie"), v8::String::New("cookie") );
+ args[3] = cookieObj;
+ }
+ c->Emit(symbol_search_paged, 4, args);
+ }
break;
default:
@@ -524,7 +605,10 @@ class LDAPConnection : public EventEmitter
break;
}
}
-
+
+ if (srv_controls) {
+ ldap_controls_free(srv_controls);
+ }
ldap_msgfree(ldap_res);
}
Something went wrong with that request. Please try again.