From 11fbda69e7962660ef6621bd5870fddde34dacfe Mon Sep 17 00:00:00 2001 From: Mark Cavage Date: Fri, 19 Aug 2011 15:08:23 -0700 Subject: [PATCH] Schema support for add/mofify --- lib/client.js | 2 +- lib/index.js | 23 +- lib/messages/search_entry.js | 3 +- lib/schema.js | 106 --------- lib/schema/add_handler.js | 115 +++++++++ lib/schema/index.js | 19 ++ lib/schema/mod_handler.js | 59 +++++ lib/schema/parser.js | 437 +++++++++++++++++++++++++++++++++++ lib/schema/search_handler.js | 37 +++ lib/schema/transform.js | 139 +++++++++++ lib/server.js | 2 +- tst/url.test.js | 12 +- 12 files changed, 829 insertions(+), 125 deletions(-) delete mode 100644 lib/schema.js create mode 100644 lib/schema/add_handler.js create mode 100644 lib/schema/index.js create mode 100644 lib/schema/mod_handler.js create mode 100644 lib/schema/parser.js create mode 100644 lib/schema/search_handler.js create mode 100644 lib/schema/transform.js diff --git a/lib/client.js b/lib/client.js index 4090dd3..46055cf 100644 --- a/lib/client.js +++ b/lib/client.js @@ -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; }); diff --git a/lib/index.js b/lib/index.js index f248a3d..491f30c 100644 --- a/lib/index.js +++ b/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 }; diff --git a/lib/messages/search_entry.js b/lib/messages/search_entry.js index 95ee969..d8bc3a8 100644 --- a/lib/messages/search_entry.js +++ b/lib/messages/search_entry.js @@ -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') { diff --git a/lib/schema.js b/lib/schema.js deleted file mode 100644 index 8449825..0000000 --- a/lib/schema.js +++ /dev/null @@ -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(); - } - } - } - -}; diff --git a/lib/schema/add_handler.js b/lib/schema/add_handler.js new file mode 100644 index 0000000..4ef16a8 --- /dev/null +++ b/lib/schema/add_handler.js @@ -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; + // } + // } + // }); + // } + diff --git a/lib/schema/index.js b/lib/schema/index.js new file mode 100644 index 0000000..9299f76 --- /dev/null +++ b/lib/schema/index.js @@ -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 + +}; diff --git a/lib/schema/mod_handler.js b/lib/schema/mod_handler.js new file mode 100644 index 0000000..8e206a4 --- /dev/null +++ b/lib/schema/mod_handler.js @@ -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; diff --git a/lib/schema/parser.js b/lib/schema/parser.js new file mode 100644 index 0000000..aa85b20 --- /dev/null +++ b/lib/schema/parser.js @@ -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 + + +}; + diff --git a/lib/schema/search_handler.js b/lib/schema/search_handler.js new file mode 100644 index 0000000..e0529e4 --- /dev/null +++ b/lib/schema/search_handler.js @@ -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; diff --git a/lib/schema/transform.js b/lib/schema/transform.js new file mode 100644 index 0000000..2e24b5e --- /dev/null +++ b/lib/schema/transform.js @@ -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 + // } diff --git a/lib/server.js b/lib/server.js index dc74864..f9674c8 100644 --- a/lib/server.js +++ b/lib/server.js @@ -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) { diff --git a/tst/url.test.js b/tst/url.test.js index 9ae8038..0a992b4 100644 --- a/tst/url.test.js +++ b/tst/url.test.js @@ -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);