Add ldap pagination support
This commit is contained in:
99
src/ldap.js
99
src/ldap.js
@@ -61,12 +61,74 @@ function getUsersWithAccessToApp(req, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
// helper function to deal with pagination
|
||||
function finalSend(results, req, res, next) {
|
||||
var min = 0;
|
||||
var max = results.length;
|
||||
var cookie = null;
|
||||
var pageSize = 0;
|
||||
|
||||
// check if this is a paging request, if so get the cookie for session info
|
||||
req.controls.forEach(function (control) {
|
||||
if (control.type === ldap.PagedResultsControl.OID) {
|
||||
pageSize = control.value.size;
|
||||
cookie = control.value.cookie;
|
||||
}
|
||||
});
|
||||
|
||||
function sendPagedResults(start, end) {
|
||||
start = (start < min) ? min : start;
|
||||
end = (end > max || end < min) ? max : end;
|
||||
var i;
|
||||
|
||||
for (i = start; i < end; i++) {
|
||||
res.send(results[i]);
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
if (cookie && Buffer.isBuffer(cookie)) {
|
||||
// we have pagination
|
||||
var first = min;
|
||||
if (cookie.length !== 0) {
|
||||
first = parseInt(cookie.toString(), 10);
|
||||
}
|
||||
var last = sendPagedResults(first, first + pageSize);
|
||||
|
||||
var resultCookie;
|
||||
if (last < max) {
|
||||
resultCookie = new Buffer(last.toString());
|
||||
} else {
|
||||
resultCookie = new Buffer('');
|
||||
}
|
||||
|
||||
res.controls.push(new ldap.PagedResultsControl({
|
||||
value: {
|
||||
size: pageSize, // correctness not required here
|
||||
cookie: resultCookie
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
// no pagination simply send all
|
||||
results.forEach(function (result) {
|
||||
res.send(result);
|
||||
});
|
||||
}
|
||||
|
||||
// all done
|
||||
res.end();
|
||||
next();
|
||||
}
|
||||
|
||||
function userSearch(req, res, next) {
|
||||
debug('user search: dn %s, scope %s, filter %s (from %s)', req.dn.toString(), req.scope, req.filter.toString(), req.connection.ldap.id);
|
||||
|
||||
getUsersWithAccessToApp(req, function (error, result) {
|
||||
if (error) return next(error);
|
||||
|
||||
var results = [];
|
||||
|
||||
// send user objects
|
||||
result.forEach(function (entry) {
|
||||
// skip entries with empty username. Some apps like owncloud can't deal with this
|
||||
@@ -109,11 +171,11 @@ function userSearch(req, res, next) {
|
||||
if (!lowerCaseFilter) return next(new ldap.OperationsError(safe.error.toString()));
|
||||
|
||||
if ((req.dn.equals(dn) || req.dn.parentOf(dn)) && lowerCaseFilter.matches(obj.attributes)) {
|
||||
res.send(obj);
|
||||
results.push(obj);
|
||||
}
|
||||
});
|
||||
|
||||
res.end();
|
||||
finalSend(results, req, res, next);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -123,6 +185,8 @@ function groupSearch(req, res, next) {
|
||||
getUsersWithAccessToApp(req, function (error, result) {
|
||||
if (error) return next(error);
|
||||
|
||||
var results = [];
|
||||
|
||||
var groups = [{
|
||||
name: 'users',
|
||||
admin: false
|
||||
@@ -149,11 +213,11 @@ function groupSearch(req, res, next) {
|
||||
if (!lowerCaseFilter) return next(new ldap.OperationsError(safe.error.toString()));
|
||||
|
||||
if ((req.dn.equals(dn) || req.dn.parentOf(dn)) && lowerCaseFilter.matches(obj.attributes)) {
|
||||
res.send(obj);
|
||||
results.push(obj);
|
||||
}
|
||||
});
|
||||
|
||||
res.end();
|
||||
finalSend(results, req, res, next);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -193,6 +257,7 @@ function mailboxSearch(req, res, next) {
|
||||
debug('mailbox search: dn %s, scope %s, filter %s (from %s)', req.dn.toString(), req.scope, req.filter.toString(), req.connection.ldap.id);
|
||||
|
||||
if (!req.dn.rdns[0].attrs.cn) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
|
||||
var name = req.dn.rdns[0].attrs.cn.value.toLowerCase();
|
||||
// allow login via email
|
||||
var parts = name.split('@');
|
||||
@@ -220,9 +285,11 @@ function mailboxSearch(req, res, next) {
|
||||
var lowerCaseFilter = safe(function () { return ldap.parseFilter(req.filter.toString().toLowerCase()); }, null);
|
||||
if (!lowerCaseFilter) return next(new ldap.OperationsError(safe.error.toString()));
|
||||
|
||||
if (lowerCaseFilter.matches(obj.attributes)) res.send(obj);
|
||||
|
||||
res.end();
|
||||
if (lowerCaseFilter.matches(obj.attributes)) {
|
||||
finalSend([ obj ], req, res, next);
|
||||
} else {
|
||||
res.end();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -230,6 +297,7 @@ function mailAliasSearch(req, res, next) {
|
||||
debug('mail alias get: dn %s, scope %s, filter %s (from %s)', req.dn.toString(), req.scope, req.filter.toString(), req.connection.ldap.id);
|
||||
|
||||
if (!req.dn.rdns[0].attrs.cn) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
|
||||
mailboxdb.getAlias(req.dn.rdns[0].attrs.cn.value.toLowerCase(), function (error, alias) {
|
||||
if (error && error.reason === DatabaseError.NOT_FOUND) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
if (error) return next(new ldap.OperationsError(error.toString()));
|
||||
@@ -250,9 +318,11 @@ function mailAliasSearch(req, res, next) {
|
||||
var lowerCaseFilter = safe(function () { return ldap.parseFilter(req.filter.toString().toLowerCase()); }, null);
|
||||
if (!lowerCaseFilter) return next(new ldap.OperationsError(safe.error.toString()));
|
||||
|
||||
if (lowerCaseFilter.matches(obj.attributes)) res.send(obj);
|
||||
|
||||
res.end();
|
||||
if (lowerCaseFilter.matches(obj.attributes)) {
|
||||
finalSend([ obj ], req, res, next);
|
||||
} else {
|
||||
res.end();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -260,6 +330,7 @@ function mailingListSearch(req, res, next) {
|
||||
debug('mailing list get: dn %s, scope %s, filter %s (from %s)', req.dn.toString(), req.scope, req.filter.toString(), req.connection.ldap.id);
|
||||
|
||||
if (!req.dn.rdns[0].attrs.cn) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
|
||||
mailboxdb.getGroup(req.dn.rdns[0].attrs.cn.value.toLowerCase(), function (error, group) {
|
||||
if (error && error.reason === DatabaseError.NOT_FOUND) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
if (error) return next(new ldap.OperationsError(error.toString()));
|
||||
@@ -280,9 +351,11 @@ function mailingListSearch(req, res, next) {
|
||||
var lowerCaseFilter = safe(function () { return ldap.parseFilter(req.filter.toString().toLowerCase()); }, null);
|
||||
if (!lowerCaseFilter) return next(new ldap.OperationsError(safe.error.toString()));
|
||||
|
||||
if (lowerCaseFilter.matches(obj.attributes)) res.send(obj);
|
||||
|
||||
res.end();
|
||||
if (lowerCaseFilter.matches(obj.attributes)) {
|
||||
finalSend([ obj ], req, res, next);
|
||||
} else {
|
||||
res.end();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -342,6 +342,35 @@ describe('Ldap', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it ('succeeds with pagination', function (done) {
|
||||
var client = ldap.createClient({ url: 'ldap://127.0.0.1:' + config.get('ldapPort') });
|
||||
|
||||
var opts = {
|
||||
filter: 'objectcategory=person',
|
||||
paged: true
|
||||
};
|
||||
|
||||
client.search('ou=users,dc=cloudron', opts, function (error, result) {
|
||||
expect(error).to.be(null);
|
||||
expect(result).to.be.an(EventEmitter);
|
||||
|
||||
var entries = [];
|
||||
|
||||
result.on('searchEntry', function (entry) { entries.push(entry.object); });
|
||||
result.on('error', done);
|
||||
result.on('end', function (result) {
|
||||
expect(result.status).to.equal(0);
|
||||
expect(entries.length).to.equal(2);
|
||||
entries.sort(function (a, b) { return a.username > b.username; });
|
||||
expect(entries[0].username).to.equal(USER_0.username.toLowerCase());
|
||||
expect(entries[0].mail).to.equal(USER_0.email.toLowerCase());
|
||||
expect(entries[1].username).to.equal(USER_1.username.toLowerCase());
|
||||
expect(entries[1].mail).to.equal(USER_1.email.toLowerCase());
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it ('succeeds with basic filter and email enabled', function (done) {
|
||||
// user settingsdb instead of settings, to not trigger further events
|
||||
settingsdb.set(settings.MAIL_CONFIG_KEY, JSON.stringify({ enabled: true }), function (error) {
|
||||
@@ -617,13 +646,50 @@ describe('Ldap', function () {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it ('succeeds with pagination', function (done) {
|
||||
var client = ldap.createClient({ url: 'ldap://127.0.0.1:' + config.get('ldapPort') });
|
||||
|
||||
var opts = {
|
||||
filter: 'objectclass=group',
|
||||
paged: true
|
||||
};
|
||||
|
||||
client.search('ou=groups,dc=cloudron', opts, function (error, result) {
|
||||
expect(error).to.be(null);
|
||||
expect(result).to.be.an(EventEmitter);
|
||||
|
||||
var entries = [];
|
||||
|
||||
result.on('searchEntry', function (entry) { entries.push(entry.object); });
|
||||
result.on('error', done);
|
||||
result.on('end', function (result) {
|
||||
expect(result.status).to.equal(0);
|
||||
expect(entries.length).to.equal(2);
|
||||
|
||||
// ensure order for testability
|
||||
entries.sort(function (a, b) { return a.username < b.username; });
|
||||
|
||||
expect(entries[0].cn).to.equal('users');
|
||||
expect(entries[0].memberuid.length).to.equal(3);
|
||||
expect(entries[0].memberuid[0]).to.equal(USER_0.id);
|
||||
expect(entries[0].memberuid[1]).to.equal(USER_1.id);
|
||||
expect(entries[0].memberuid[2]).to.equal(USER_2.id);
|
||||
expect(entries[1].cn).to.equal('admins');
|
||||
// if only one entry, the array becomes a string :-/
|
||||
expect(entries[1].memberuid).to.equal(USER_0.id);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function ldapSearch(dn, filter, callback) {
|
||||
var client = ldap.createClient({ url: 'ldap://127.0.0.1:' + config.get('ldapPort') });
|
||||
|
||||
var opts = {
|
||||
filter: filter
|
||||
filter: filter,
|
||||
paged: true
|
||||
};
|
||||
|
||||
client.search(dn, opts, function (error, result) {
|
||||
|
||||
Reference in New Issue
Block a user