Schema support for add/mofify
This commit is contained in:
parent
3e423e5b1e
commit
11fbda69e7
|
@ -130,7 +130,7 @@ function Client(options) {
|
|||
|
||||
this.__defineGetter__('log', function() {
|
||||
if (!self._log)
|
||||
self._log = self.log4js.getLogger('LDAPClient');
|
||||
self._log = self.log4js.getLogger('Client');
|
||||
|
||||
return self._log;
|
||||
});
|
||||
|
|
23
lib/index.js
23
lib/index.js
|
@ -53,21 +53,24 @@ module.exports = {
|
|||
return new Server(options);
|
||||
},
|
||||
|
||||
dn: dn,
|
||||
DN: dn.DN,
|
||||
RDN: dn.RDN,
|
||||
parseDN: dn.parse,
|
||||
|
||||
filters: filters,
|
||||
parseFilter: filters.parseString,
|
||||
|
||||
Attribute: Attribute,
|
||||
Change: Change,
|
||||
Control: Control,
|
||||
|
||||
DN: dn.DN,
|
||||
RDN: dn.RDN,
|
||||
parseDN: dn.parse,
|
||||
dn: dn,
|
||||
|
||||
filters: filters,
|
||||
parseFilter: filters.parseString,
|
||||
|
||||
log4js: logStub,
|
||||
url: url,
|
||||
parseURL: url.parse
|
||||
parseURL: url.parse,
|
||||
|
||||
loadSchema: schema.load,
|
||||
createSchemaAddHandler: schema.createAddHandler,
|
||||
createSchemaModifyHandler: schema.createModifyHandler
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -79,7 +79,8 @@ SearchEntry.prototype.fromObject = function(obj) {
|
|||
if (Array.isArray(obj[k])) {
|
||||
obj[k].forEach(function(v) {
|
||||
if (typeof(v) !== 'string')
|
||||
throw new TypeError(k + ' -> ' + v + ' is not a string');
|
||||
v = '' + v;
|
||||
|
||||
attr.vals.push(v);
|
||||
});
|
||||
} else if (typeof(obj[k]) === 'string') {
|
||||
|
|
106
lib/schema.js
106
lib/schema.js
|
@ -1,106 +0,0 @@
|
|||
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
|
||||
|
||||
var assert = require('assert');
|
||||
var fs = require('fs');
|
||||
|
||||
var Protocol = require('./protocol');
|
||||
var errors = require('./errors');
|
||||
|
||||
|
||||
|
||||
///--- API
|
||||
|
||||
module.exports = {
|
||||
|
||||
/**
|
||||
*
|
||||
* Supports definition of objectclasses like so:
|
||||
* {
|
||||
* person: {
|
||||
* required: ['cn', 'sn'],
|
||||
* optional: ['email']
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
loadSchema: function(file, callback) {
|
||||
if (!file || typeof(file) !== 'string')
|
||||
throw new TypeError('file (string) required');
|
||||
if (typeof(callback) !== 'function')
|
||||
throw new TypeError('callback (function) required');
|
||||
|
||||
return fs.readFile(file, 'utf8', function(err, data) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
try {
|
||||
return callback(null, JSON.parse(data));
|
||||
} catch (e) {
|
||||
return callback(e);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
newInterceptor: function(schema) {
|
||||
if (typeof(schema) !== 'object')
|
||||
throw new TypeError('schema (object) required');
|
||||
|
||||
// Add/Modify request already have attributes sorted
|
||||
return function(req, res, next) {
|
||||
switch (req.protocolOp) {
|
||||
case Protocol.LDAP_REQ_ADD:
|
||||
var ocNdx = req.indexOf('objectclass');
|
||||
if (ocNdx === -1)
|
||||
return next(new errors.ConstraintViolation('objectclass'));
|
||||
var reqOC = req.attributes[ocNdx];
|
||||
|
||||
// First make the "set" of required/optional attributes for all OCs in
|
||||
// the union of all OCs. We destroy these arrays after the fact. Note
|
||||
// that optional will get the set of attributes also not already in
|
||||
// required, since we figure this out by destructively changing the
|
||||
// list of attribute names.
|
||||
var required = [];
|
||||
var optional = [];
|
||||
var i, j;
|
||||
|
||||
for (i = 0; i < reqOC.vals.length; i++) {
|
||||
var oc = schema[reqOC.vals[i]];
|
||||
if (!oc)
|
||||
return next(new errors.UndefinedAttributeType(reqOC.vals[i]));
|
||||
|
||||
for (j = 0; j < oc.required.length; j++) {
|
||||
if (required.indexOf(oc.required[j]) === -1)
|
||||
required.push(oc.required[j]);
|
||||
}
|
||||
for (j = 0; j < oc.optional.length; j++) {
|
||||
if (optional.indexOf(oc.optional[j]) === -1 &&
|
||||
required.indexOf(oc.optional[j]) === -1)
|
||||
optional.push(oc.optional[j]);
|
||||
}
|
||||
}
|
||||
|
||||
// Make a copy of just the attribute names
|
||||
var attrs = req.attributeNames();
|
||||
for (i = 0; i < attrs.length; i++) {
|
||||
var ndx = required.indexOf(attrs[i]);
|
||||
if (ndx === -1) {
|
||||
ndx = optional.indexOf(attrs[i]);
|
||||
if (ndx == -1)
|
||||
return next(new errors.ConstraintViolation(attrs[i]));
|
||||
}
|
||||
attrs.splice(i, 1);
|
||||
}
|
||||
|
||||
if (attrs.length)
|
||||
return next(new errors.ConstraintViolation(attrs.join()));
|
||||
|
||||
break;
|
||||
case Protocol.LDAP_REQ_MODIFY:
|
||||
|
||||
break;
|
||||
default:
|
||||
return next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
|
@ -0,0 +1,115 @@
|
|||
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
|
||||
|
||||
var assert = require('assert');
|
||||
|
||||
var dn = require('../dn');
|
||||
var errors = require('../errors');
|
||||
var logStub = require('../log_stub');
|
||||
|
||||
var getTransformer = require('./transform').getTransformer;
|
||||
|
||||
|
||||
|
||||
function createAddHandler(options) {
|
||||
if (!options || typeof(options) !== 'object')
|
||||
throw new TypeError('options (object) required');
|
||||
if (!options.schema || typeof(options.schema) !== 'object')
|
||||
throw new TypeError('options.schema (object) required');
|
||||
|
||||
var log4js = options.log4js || logStub;
|
||||
var log = log4js.getLogger('SchemaAddHandler');
|
||||
var schema = options.schema;
|
||||
|
||||
if (log.isDebugEnabled())
|
||||
log.debug('Creating add schema handler with: %s',
|
||||
JSON.stringify(options.schema, null, 2));
|
||||
|
||||
var CVErr = errors.ConstraintViolationError;
|
||||
var NSAErr = errors.NoSuchAttributeError;
|
||||
var OCVErr = errors.ObjectclassViolationError;
|
||||
|
||||
return function schemaAddHandler(req, res, next) {
|
||||
var allowed = [];
|
||||
var attributes = req.toObject().attributes;
|
||||
var attrNames = Object.keys(attributes);
|
||||
var i;
|
||||
var j;
|
||||
var k;
|
||||
var key;
|
||||
|
||||
if (log.isDebugEnabled())
|
||||
log.debug('%s running %j against schema', req.logId, attributes);
|
||||
|
||||
if (!attributes.objectclass)
|
||||
return next(new OCVErr('no objectclass'));
|
||||
|
||||
for (i = 0; i < attributes.objectclass.length; i++) {
|
||||
var oc = attributes.objectclass[i].toLowerCase();
|
||||
if (!schema.objectclasses[oc])
|
||||
return next(new NSAErr(oc + ' is not a known objectClass'));
|
||||
|
||||
// We can check required attributes right here in line. Mays we have to
|
||||
// get the complete set of though. Also, to make checking much simpler,
|
||||
// we just push the musts into the may list.
|
||||
var must = schema.objectclasses[oc].must;
|
||||
for (j = 0; j < must.length; j++) {
|
||||
if (attrNames.indexOf(must[j]) === -1)
|
||||
return next(new OCVErr(must[j] + ' is a required attribute'));
|
||||
if (allowed.indexOf(must[j]) === -1)
|
||||
allowed.push(must[j]);
|
||||
}
|
||||
|
||||
schema.objectclasses[oc].may.forEach(function(attr) {
|
||||
if (allowed.indexOf(attr) === -1)
|
||||
allowed.push(attr);
|
||||
});
|
||||
}
|
||||
|
||||
// Now check that the entry's attributes are in the allowed list, and go
|
||||
// ahead and transform the values as appropriate
|
||||
for (i = 0; i < attrNames.length; i++) {
|
||||
key = attrNames[i];
|
||||
if (allowed.indexOf(key) === -1)
|
||||
return next(new OCVErr(key + ' is not valid for the objectClasses ' +
|
||||
attributes.objectclass.join()));
|
||||
|
||||
var transform = getTransformer(schema, key);
|
||||
if (transform) {
|
||||
for (j = 0; j < attributes[key].length; j++) {
|
||||
try {
|
||||
attributes[key][j] = transform(attributes[key][j]);
|
||||
} catch (e) {
|
||||
log.debug('%s Error parsing %s: %s', req.logId, k,
|
||||
attributes[key][j],
|
||||
e.stack);
|
||||
return next(new CVErr(attrNames[i]));
|
||||
}
|
||||
}
|
||||
for (j = 0; j < req.attributes.length; j++) {
|
||||
if (req.attributes[j].type === key) {
|
||||
req.attributes[j].vals = attributes[key];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return next();
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = createAddHandler;
|
||||
|
||||
// Now we have a modified attributes object we want to update
|
||||
// "transparently" in the request.
|
||||
// if (xformedValues) {
|
||||
// attrNames.forEach(function(k) {
|
||||
// for (var i = 0; i < req.attributes.length; i++) {
|
||||
// if (req.attributes[i].type === k) {
|
||||
// req.attributes[i].vals = attributes[k];
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
|
||||
|
||||
var createAddHandler = require('./add_handler');
|
||||
var createModifyHandler = require('./mod_handler');
|
||||
var parser = require('./parser');
|
||||
|
||||
|
||||
|
||||
///--- API
|
||||
|
||||
module.exports = {
|
||||
|
||||
createAddHandler: createAddHandler,
|
||||
|
||||
createModifyHandler: createModifyHandler,
|
||||
|
||||
load: parser.load
|
||||
|
||||
};
|
|
@ -0,0 +1,59 @@
|
|||
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
|
||||
|
||||
var assert = require('assert');
|
||||
|
||||
var dn = require('../dn');
|
||||
var errors = require('../errors');
|
||||
var logStub = require('../log_stub');
|
||||
|
||||
var getTransformer = require('./transform').getTransformer;
|
||||
|
||||
|
||||
|
||||
function createModifyHandler(options) {
|
||||
if (!options || typeof(options) !== 'object')
|
||||
throw new TypeError('options (object) required');
|
||||
if (!options.schema || typeof(options.schema) !== 'object')
|
||||
throw new TypeError('options.schema (object) required');
|
||||
// TODO add a callback mechanism here so objectclass constraints can be
|
||||
// enforced
|
||||
|
||||
var log4js = options.log4js || logStub;
|
||||
var log = log4js.getLogger('SchemaModifyHandler');
|
||||
var schema = options.schema;
|
||||
|
||||
var CVErr = errors.ConstraintViolationError;
|
||||
var NSAErr = errors.NoSuchAttributeError;
|
||||
var OCVErr = errors.ObjectclassViolationError;
|
||||
|
||||
return function schemaModifyHandler(req, res, next) {
|
||||
if (log.isDebugEnabled())
|
||||
log.debug('%s running %j against schema', req.logId, req.changes);
|
||||
|
||||
for (var i = 0; i < req.changes.length; i++) {
|
||||
var mod = req.changes[i].modification;
|
||||
var attribute = schema.attributes[mod.type];
|
||||
if (!attribute)
|
||||
return next(new NSAErr(mod.type));
|
||||
|
||||
if (!mod.vals || !mod.vals.length)
|
||||
continue;
|
||||
|
||||
var transform = getTransformer(schema, mod.type);
|
||||
if (transform) {
|
||||
for (var j = 0; j < mod.vals.length; j++) {
|
||||
try {
|
||||
mod.vals[j] = transform(mod.vals[j]);
|
||||
} catch (e) {
|
||||
log.debug('%s Error parsing %s: %s', req.logId, mod.vals[j],
|
||||
e.stack);
|
||||
return next(new CVErr(mod.type + ': ' + mod.vals[j]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return next();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = createModifyHandler;
|
|
@ -0,0 +1,437 @@
|
|||
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
|
||||
|
||||
var assert = require('assert');
|
||||
var fs = require('fs');
|
||||
|
||||
var dn = require('../dn');
|
||||
var errors = require('../errors');
|
||||
var logStub = require('../log_stub');
|
||||
|
||||
|
||||
|
||||
//// Attribute BNF
|
||||
//
|
||||
// AttributeTypeDescription = "(" whsp
|
||||
// numericoid whsp ; AttributeType identifier
|
||||
// [ "NAME" qdescrs ] ; name used in AttributeType
|
||||
// [ "DESC" qdstring ] ; description
|
||||
// [ "OBSOLETE" whsp ]
|
||||
// [ "SUP" woid ] ; derived from this other
|
||||
// ; AttributeType
|
||||
// [ "EQUALITY" woid ; Matching Rule name
|
||||
// [ "ORDERING" woid ; Matching Rule name
|
||||
// [ "SUBSTR" woid ] ; Matching Rule name
|
||||
// [ "SYNTAX" whsp noidlen whsp ] ; Syntax OID
|
||||
// [ "SINGLE-VALUE" whsp ] ; default multi-valued
|
||||
// [ "COLLECTIVE" whsp ] ; default not collective
|
||||
// [ "NO-USER-MODIFICATION" whsp ]; default user modifiable
|
||||
// [ "USAGE" whsp AttributeUsage ]; default userApplications
|
||||
// whsp ")"
|
||||
//
|
||||
// AttributeUsage =
|
||||
// "userApplications" /
|
||||
// "directoryOperation" /
|
||||
// "distributedOperation" / ; DSA-shared
|
||||
// "dSAOperation" ; DSA-specific, value depends on server
|
||||
|
||||
/// Objectclass BNF
|
||||
//
|
||||
// ObjectClassDescription = "(" whsp
|
||||
// numericoid whsp ; ObjectClass identifier
|
||||
// [ "NAME" qdescrs ]
|
||||
// [ "DESC" qdstring ]
|
||||
// [ "OBSOLETE" whsp ]
|
||||
// [ "SUP" oids ] ; Superior ObjectClasses
|
||||
// [ ( "ABSTRACT" / "STRUCTURAL" / "AUXILIARY" ) whsp ]
|
||||
// ; default structural
|
||||
// [ "MUST" oids ] ; AttributeTypes
|
||||
// [ "MAY" oids ] ; AttributeTypes
|
||||
// whsp ")"
|
||||
|
||||
// This is some fugly code, and really not that robust, but LDAP schema
|
||||
// is a pita with its optional ('s. So, whatever, it's good enough for our
|
||||
// purposes (namely, dropping in the OpenLDAP schema). This took me a little
|
||||
// over an hour to write, so there you go ;)
|
||||
|
||||
function parse(data) {
|
||||
if (!data || typeof(data) !== 'string')
|
||||
throw new TypeError('data (string) required');
|
||||
|
||||
var lines = [];
|
||||
data.split('\n').forEach(function(l) {
|
||||
if (/^#/.test(l) ||
|
||||
/^objectidentifier/i.test(l) ||
|
||||
!l.length)
|
||||
return;
|
||||
|
||||
lines.push(l);
|
||||
});
|
||||
|
||||
var attr;
|
||||
var oc;
|
||||
var syntax;
|
||||
var attributes = [];
|
||||
var objectclasses = [];
|
||||
var depth = 0;
|
||||
lines.join('\n').split(/\s+/).forEach(function(w) {
|
||||
if (attr) {
|
||||
if (w === '(') {
|
||||
depth++;
|
||||
} else if (w === ')') {
|
||||
if (--depth === 0) {
|
||||
if (attr._skip)
|
||||
delete attr._skip;
|
||||
|
||||
attributes.push(attr);
|
||||
attr = null;
|
||||
}
|
||||
return;
|
||||
} else if (!attr.oid) {
|
||||
attr.oid = w;
|
||||
} else if (w === 'NAME') {
|
||||
attr._names = [];
|
||||
} else if (w === 'DESC') {
|
||||
attr._desc = '';
|
||||
} else if (w === 'OBSOLETE') {
|
||||
attr.obsolete = true;
|
||||
} else if (w === 'SUP') {
|
||||
attr._sup = true;
|
||||
} else if (attr._sup) {
|
||||
attr.sup = w;
|
||||
delete attr._sup;
|
||||
} else if (w === 'EQUALITY') {
|
||||
attr._equality = true;
|
||||
} else if (w === 'ORDERING') {
|
||||
attr._ordering = true;
|
||||
} else if (w === 'SUBSTR') {
|
||||
attr._substr = true;
|
||||
} else if (w === 'SYNTAX') {
|
||||
attr._syntax = true;
|
||||
} else if (w === 'SINGLE-VALUE') {
|
||||
attr.singleValue = true;
|
||||
} else if (w === 'COLLECTIVE') {
|
||||
attr.collective = true;
|
||||
} else if (w === 'NO-USER-MODIFICATION') {
|
||||
attr.noUserModification = true;
|
||||
} else if (w === 'USAGE') {
|
||||
attr._usage = true;
|
||||
} else if (/^X-/.test(w)) {
|
||||
attr._skip = true;
|
||||
} else if (attr._skip) {
|
||||
// noop
|
||||
} else if (attr._usage) {
|
||||
attr.usage = w;
|
||||
delete attr._usage;
|
||||
} else if (attr._syntax) {
|
||||
attr.syntax = w;
|
||||
delete attr._syntax;
|
||||
} else if (attr._substr) {
|
||||
attr.substr = w;
|
||||
delete attr._substr;
|
||||
} else if (attr._ordering) {
|
||||
attr.ordering = w;
|
||||
delete attr._ordering;
|
||||
} else if (attr._equality) {
|
||||
attr.equality = w;
|
||||
delete attr._equality;
|
||||
} else if (attr._desc !== undefined) {
|
||||
attr._desc += w.replace(/\'/g, '');
|
||||
if (/\'$/.test(w)) {
|
||||
attr.desc = attr._desc;
|
||||
delete attr._desc;
|
||||
} else {
|
||||
attr._desc += ' ';
|
||||
}
|
||||
} else if (attr._names) {
|
||||
attr._names.push(w.replace(/\'/g, '').toLowerCase());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (oc) {
|
||||
if (w === '(') {
|
||||
depth++;
|
||||
} else if (w === ')') {
|
||||
if (--depth === 0) {
|
||||
objectclasses.push(oc);
|
||||
oc = null;
|
||||
}
|
||||
return;
|
||||
} else if (w === '$') {
|
||||
return;
|
||||
} else if (!oc.oid) {
|
||||
oc.oid = w;
|
||||
} else if (w === 'NAME') {
|
||||
oc._names = [];
|
||||
} else if (w === 'DESC') {
|
||||
oc._desc = '';
|
||||
} else if (w === 'OBSOLETE') {
|
||||
oc.obsolete = true;
|
||||
} else if (w === 'SUP') {
|
||||
oc._sup = [];
|
||||
} else if (w === 'ABSTRACT') {
|
||||
oc['abstract'] = true;
|
||||
} else if (w === 'AUXILIARY') {
|
||||
oc.auxiliary = true;
|
||||
} else if (w === 'STRUCTURAL') {
|
||||
oc.structural = true;
|
||||
} else if (w === 'MUST') {
|
||||
oc._must = [];
|
||||
} else if (w === 'MAY') {
|
||||
oc._may = [];
|
||||
} else if (oc._may) {
|
||||
oc._may.push(w.toLowerCase());
|
||||
} else if (oc._must) {
|
||||
oc._must.push(w.toLowerCase());
|
||||
} else if (oc._sup) {
|
||||
oc._sup.push(w.replace(/\'/g, '').toLowerCase());
|
||||
} else if (oc._desc !== undefined) {
|
||||
oc._desc += w.replace(/\'/g, '');
|
||||
if (/\'$/.test(w)) {
|
||||
oc.desc = oc._desc;
|
||||
delete oc._desc;
|
||||
} else {
|
||||
oc._desc += ' ';
|
||||
}
|
||||
} else if (oc._names) {
|
||||
oc._names.push(w.replace(/\'/g, '').toLowerCase());
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Throw this away for now.
|
||||
if (syntax) {
|
||||
if (w === '(') {
|
||||
depth++;
|
||||
} else if (w === ')') {
|
||||
if (--depth === 0) {
|
||||
syntax = false;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (/^attributetype/i.test(w)) {
|
||||
attr = {};
|
||||
} else if (/^objectclass/i.test(w)) {
|
||||
oc = {};
|
||||
} else if (/^ldapsyntax/i.test(w)) {
|
||||
syntax = true;
|
||||
} else if (!w) {
|
||||
// noop
|
||||
} else {
|
||||
throw new Error('Invalid token ' + w + ' in file ' + file);
|
||||
}
|
||||
});
|
||||
|
||||
// cleanup all the temporary arrays
|
||||
var i;
|
||||
for (i = 0; i < attributes.length; i++) {
|
||||
if (!attributes[i]._names)
|
||||
continue;
|
||||
|
||||
attributes[i].names = attributes[i]._names;
|
||||
delete attributes[i]._names;
|
||||
}
|
||||
for (i = 0; i < objectclasses.length; i++) {
|
||||
oc = objectclasses[i];
|
||||
if (oc._names) {
|
||||
oc.names = oc._names;
|
||||
delete oc._names;
|
||||
} else {
|
||||
oc.names = [];
|
||||
}
|
||||
if (oc._sup) {
|
||||
oc.sup = oc._sup;
|
||||
delete oc._sup;
|
||||
} else {
|
||||
oc.sup = [];
|
||||
}
|
||||
if (oc._must) {
|
||||
oc.must = oc._must;
|
||||
delete oc._must;
|
||||
} else {
|
||||
oc.must = [];
|
||||
}
|
||||
if (oc._may) {
|
||||
oc.may = oc._may;
|
||||
delete oc._may;
|
||||
} else {
|
||||
oc.may = [];
|
||||
}
|
||||
}
|
||||
|
||||
var _attributes = {};
|
||||
var _objectclasses = {};
|
||||
attributes.forEach(function(a) {
|
||||
for (var i = 0; i < a.names.length; i++) {
|
||||
a.names[i] = a.names[i].toLowerCase();
|
||||
_attributes[a.names[i]] = a;
|
||||
}
|
||||
});
|
||||
|
||||
objectclasses.forEach(function(oc) {
|
||||
for (var i = 0; i < oc.names.length; i++) {
|
||||
oc.names[i] = oc.names[i].toLowerCase();
|
||||
_objectclasses[oc.names[i]] = oc;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
attributes: _attributes,
|
||||
objectclasses: _objectclasses
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function parseFile(file, callback) {
|
||||
if (!file || typeof(file) !== 'string')
|
||||
throw new TypeError('file (string) required');
|
||||
if (!callback || typeof(callback) !== 'function')
|
||||
throw new TypeError('callback (function) required');
|
||||
|
||||
fs.readFile(file, 'utf8', function(err, data) {
|
||||
if (err)
|
||||
return callback(new errors.OperationsError(err.message));
|
||||
|
||||
try {
|
||||
return callback(null, parse(data));
|
||||
} catch (e) {
|
||||
return callback(new errors.OperationsError(e.message));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function _merge(child, parent) {
|
||||
Object.keys(parent).forEach(function(k) {
|
||||
if (Array.isArray(parent[k])) {
|
||||
if (k === 'names' || k === 'sup')
|
||||
return;
|
||||
|
||||
if (!child[k])
|
||||
child[k] = [];
|
||||
|
||||
parent[k].forEach(function(v) {
|
||||
if (child[k].indexOf(v) === -1)
|
||||
child[k].push(v);
|
||||
});
|
||||
} else if (!child[k]) {
|
||||
child[k] = parent[k];
|
||||
}
|
||||
});
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
|
||||
function compile(attributes, objectclasses) {
|
||||
assert.ok(attributes);
|
||||
assert.ok(objectclasses);
|
||||
|
||||
var _attributes = {};
|
||||
var _objectclasses = {};
|
||||
|
||||
Object.keys(attributes).forEach(function(k) {
|
||||
_attributes[k] = attributes[k];
|
||||
|
||||
var sup;
|
||||
if (attributes[k].sup && (sup = attributes[attributes[k].sup]))
|
||||
_attributes[k] = _merge(_attributes[k], sup);
|
||||
|
||||
_attributes[k].names.sort();
|
||||
});
|
||||
|
||||
Object.keys(objectclasses).forEach(function(k) {
|
||||
_objectclasses[k] = objectclasses[k];
|
||||
var sup;
|
||||
if (objectclasses[k].sup && (sup = objectclasses[objectclasses[k].sup]))
|
||||
_objectclasses[k] = _merge(_objectclasses[k], sup);
|
||||
|
||||
_objectclasses[k].names.sort();
|
||||
_objectclasses[k].sup.sort();
|
||||
_objectclasses[k].must.sort();
|
||||
_objectclasses[k].may.sort();
|
||||
});
|
||||
|
||||
return {
|
||||
attributes: _attributes,
|
||||
objectclasses: _objectclasses
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Loads all the `.schema` files in a directory, and parses them.
|
||||
*
|
||||
* This method returns the set of schema from all files, and the "last one"
|
||||
* wins, so don't do something stupid like have the same attribute defined
|
||||
* N times with varying definitions.
|
||||
*
|
||||
* @param {String} directory the directory of *.schema files to load.
|
||||
* @param {Function} callback of the form f(err, attributes, objectclasses).
|
||||
* @throws {TypeEror} on bad input.
|
||||
*/
|
||||
function load(directory, callback) {
|
||||
if (!directory || typeof(directory) !== 'string')
|
||||
throw new TypeError('directory (string) required');
|
||||
if (!callback || typeof(callback) !== 'function')
|
||||
throw new TypeError('callback (function) required');
|
||||
|
||||
fs.readdir(directory, function(err, files) {
|
||||
if (err)
|
||||
return callback(new errors.OperationsError(err.message));
|
||||
|
||||
var finished = 0;
|
||||
var attributes = {};
|
||||
var objectclasses = {};
|
||||
files.forEach(function(f) {
|
||||
if (!/\.schema$/.test(f)) {
|
||||
++finished;
|
||||
return;
|
||||
}
|
||||
|
||||
f = directory + '/' + f;
|
||||
parseFile(f, function(err, schema) {
|
||||
var cb = callback;
|
||||
if (err) {
|
||||
callback = null;
|
||||
if (cb)
|
||||
return cb(new errors.OperationsError(err.message));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys(schema.attributes).forEach(function(a) {
|
||||
attributes[a] = schema.attributes[a];
|
||||
});
|
||||
Object.keys(schema.objectclasses).forEach(function(oc) {
|
||||
objectclasses[oc] = schema.objectclasses[oc];
|
||||
});
|
||||
|
||||
if (++finished === files.length) {
|
||||
if (cb) {
|
||||
schema = compile(attributes, objectclasses);
|
||||
return cb(null, schema);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
///--- Exported API
|
||||
|
||||
module.exports = {
|
||||
|
||||
load: load,
|
||||
parse: parse,
|
||||
parseFile: parseFile
|
||||
|
||||
|
||||
};
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
|
||||
|
||||
var assert = require('assert');
|
||||
|
||||
var dn = require('../dn');
|
||||
var errors = require('../errors');
|
||||
var logStub = require('../log_stub');
|
||||
|
||||
var getTransformer = require('./transform').getTransformer;
|
||||
|
||||
|
||||
|
||||
function createSearchHandler(options) {
|
||||
if (!options || typeof(options) !== 'object')
|
||||
throw new TypeError('options (object) required');
|
||||
if (!options.schema || typeof(options.schema) !== 'object')
|
||||
throw new TypeError('options.schema (object) required');
|
||||
// TODO add a callback mechanism here so objectclass constraints can be
|
||||
// enforced
|
||||
|
||||
var log4js = options.log4js || logStub;
|
||||
var log = log4js.getLogger('SchemaSearchHandler');
|
||||
var schema = options.schema;
|
||||
|
||||
var CVErr = errors.ConstraintViolationError;
|
||||
var NSAErr = errors.NoSuchAttributeError;
|
||||
var OCVErr = errors.ObjectclassViolationError;
|
||||
|
||||
return function schemaSearchHandler(req, res, next) {
|
||||
if (log.isDebugEnabled())
|
||||
log.debug('%s running %j against schema', req.logId, req.filter);
|
||||
|
||||
return next();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = createSearchHandler;
|
|
@ -0,0 +1,139 @@
|
|||
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
|
||||
|
||||
var assert = require('assert');
|
||||
|
||||
var dn = require('../dn');
|
||||
|
||||
|
||||
|
||||
///--- API
|
||||
|
||||
function _getTransformer(syntax) {
|
||||
assert.ok(syntax);
|
||||
|
||||
// TODO size enforcement
|
||||
if (/\}$/.test(syntax))
|
||||
syntax = syntax.replace(/\{.+\}$/, '');
|
||||
|
||||
switch (syntax) {
|
||||
case '1.3.6.1.4.1.1466.115.121.1.27': // int
|
||||
case '1.3.6.1.4.1.1466.115.121.1.36': // numeric string
|
||||
return function(value) {
|
||||
return parseInt(value, 10);
|
||||
};
|
||||
|
||||
case '1.3.6.1.4.1.1466.115.121.1.7': // boolean
|
||||
return function(value) {
|
||||
return /^true$/i.test(value);
|
||||
};
|
||||
|
||||
case '1.3.6.1.4.1.1466.115.121.1.5': // binary
|
||||
return function(value) {
|
||||
return new Buffer(value).toString('base64');
|
||||
};
|
||||
|
||||
case '1.3.6.1.4.1.1466.115.121.1.12': // dn syntax
|
||||
return function(value) {
|
||||
return dn.parse(value).toString();
|
||||
};
|
||||
default:
|
||||
// noop
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
|
||||
function getTransformer(schema, type) {
|
||||
assert.ok(schema);
|
||||
assert.ok(type);
|
||||
|
||||
if (!schema.attributes[type] || !schema.attributes[type].syntax)
|
||||
return null;
|
||||
|
||||
return _getTransformer(schema.attributes[type].syntax);
|
||||
}
|
||||
|
||||
|
||||
function transformValue(schema, type, value) {
|
||||
assert.ok(schema);
|
||||
assert.ok(type);
|
||||
assert.ok(value);
|
||||
|
||||
if (!schema.attributes[type] || !schema.attributes[type].syntax)
|
||||
return value;
|
||||
|
||||
var transformer = _getTransformer(schema.attributes[type].syntax);
|
||||
|
||||
return transformer ? transformer(value) : null;
|
||||
}
|
||||
|
||||
|
||||
function transformObject(schema, attributes, keys) {
|
||||
assert.ok(schema);
|
||||
assert.ok(attributes);
|
||||
|
||||
if (!keys)
|
||||
keys = Object.keys(attributes);
|
||||
|
||||
var xformed = false;
|
||||
|
||||
keys.forEach(function(k) {
|
||||
k = k.toLowerCase();
|
||||
|
||||
var transform = _getTransformer(schema.attributes[k].syntax);
|
||||
if (transform) {
|
||||
xformed = true;
|
||||
|
||||
var vals = attributes[k];
|
||||
console.log('%s -> %j', k, vals);
|
||||
for (var i = 0; i < vals.length; i++)
|
||||
vals[i] = transform(vals[i]);
|
||||
}
|
||||
});
|
||||
|
||||
return xformed;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
module.exports = {
|
||||
|
||||
transformObject: transformObject,
|
||||
transformValue: transformValue,
|
||||
getTransformer: getTransformer
|
||||
};
|
||||
|
||||
|
||||
|
||||
// var syntax = schema.attributes[k].syntax;
|
||||
// if (/\}$/.test(syntax))
|
||||
// syntax = syntax.replace(/\{.+\}$/, '');
|
||||
|
||||
// switch (syntax) {
|
||||
// case '1.3.6.1.4.1.1466.115.121.1.27': // int
|
||||
// case '1.3.6.1.4.1.1466.115.121.1.36': // numeric string
|
||||
// for (j = 0; j < attr.length; j++)
|
||||
// attr[j] = parseInt(attr[j], 10);
|
||||
// xformed = true;
|
||||
// break;
|
||||
// case '1.3.6.1.4.1.1466.115.121.1.7': // boolean
|
||||
// for (j = 0; j < attr.length; j++)
|
||||
// attr[j] = /^true$/i.test(attr[j]);
|
||||
// xformed = true;
|
||||
// break;
|
||||
// case '1.3.6.1.4.1.1466.115.121.1.5': // binary
|
||||
// for (j = 0; j < attr.length; j++)
|
||||
// attr[j] = new Buffer(attr[j]).toString('base64');
|
||||
// xformed = true;
|
||||
// break;
|
||||
// case '1.3.6.1.4.1.1466.115.121.1.12': // dn syntax
|
||||
// for (j = 0; j < attr.length; j++)
|
||||
// attr[j] = dn.parse(attr[j]).toString();
|
||||
// xformed = true;
|
||||
// break;
|
||||
// default:
|
||||
// // noop
|
||||
// }
|
|
@ -205,7 +205,7 @@ function Server(options) {
|
|||
|
||||
EventEmitter.call(this, options);
|
||||
|
||||
this.log = options.log4js.getLogger('LDAPServer');
|
||||
this.log = options.log4js.getLogger('Server');
|
||||
var log = this.log;
|
||||
|
||||
function setupConnection(c) {
|
||||
|
|
|
@ -13,15 +13,15 @@ var url;
|
|||
///--- Tests
|
||||
|
||||
test('load library', function(t) {
|
||||
url = require('../lib/index').url;
|
||||
t.ok(url);
|
||||
parseURL = require('../lib/index').parseURL;
|
||||
t.ok(parseURL);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
|
||||
test('parse empty', function(t) {
|
||||
var u = url.parse('ldap:///');
|
||||
var u = parseURL('ldap:///');
|
||||
t.equal(u.hostname, 'localhost');
|
||||
t.equal(u.port, 389);
|
||||
t.ok(!u.DN);
|
||||
|
@ -32,7 +32,7 @@ test('parse empty', function(t) {
|
|||
|
||||
|
||||
test('parse hostname', function(t) {
|
||||
var u = url.parse('ldap://example.com/');
|
||||
var u = parseURL('ldap://example.com/');
|
||||
t.equal(u.hostname, 'example.com');
|
||||
t.equal(u.port, 389);
|
||||
t.ok(!u.DN);
|
||||
|
@ -43,7 +43,7 @@ test('parse hostname', function(t) {
|
|||
|
||||
|
||||
test('parse host and port', function(t) {
|
||||
var u = url.parse('ldap://example.com:1389/');
|
||||
var u = parseURL('ldap://example.com:1389/');
|
||||
t.equal(u.hostname, 'example.com');
|
||||
t.equal(u.port, 1389);
|
||||
t.ok(!u.DN);
|
||||
|
@ -55,7 +55,7 @@ test('parse host and port', function(t) {
|
|||
|
||||
test('parse full', function(t) {
|
||||
|
||||
var u = url.parse('ldaps://ldap.example.com:1389/dc=example%20,dc=com' +
|
||||
var u = parseURL('ldaps://ldap.example.com:1389/dc=example%20,dc=com' +
|
||||
'?cn,sn?sub?(cn=Babs%20Jensen)');
|
||||
|
||||
t.equal(u.secure, true);
|
||||
|
|
Loading…
Reference in New Issue