From aedd9f222cfd0f0c9875b8bd30bcc09b4ce68a91 Mon Sep 17 00:00:00 2001 From: Mark Cavage Date: Tue, 8 Nov 2011 16:12:48 -0800 Subject: [PATCH] GH-28 Support extensible filters (client only) --- lib/filters/ext_filter.js | 107 ++++++++++++++++++++++++++++++++++++ lib/filters/index.js | 36 ++++++++++++- tst/filters/ext.test.js | 110 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 251 insertions(+), 2 deletions(-) create mode 100644 lib/filters/ext_filter.js create mode 100644 tst/filters/ext.test.js diff --git a/lib/filters/ext_filter.js b/lib/filters/ext_filter.js new file mode 100644 index 0000000..8de19cf --- /dev/null +++ b/lib/filters/ext_filter.js @@ -0,0 +1,107 @@ +// Copyright 2011 Mark Cavage, Inc. All rights reserved. + +var assert = require('assert'); +var util = require('util'); + +var Filter = require('./filter'); + +var Protocol = require('../protocol'); + + + +///--- API + +function ExtensibleFilter(options) { + if (typeof(options) === 'object') { + if (options.rule && typeof(options.rule) !== 'string') + throw new TypeError('options.rule must be a string'); + if (options.matchType && typeof(options.matchType) !== 'string') + throw new TypeError('options.type must be a string'); + if (options.value && typeof(options.value) !== 'string') + throw new TypeError('options.value (string) required'); + } else { + options = {}; + } + + this.matchType = options.matchType || null; + this.rule = options.rule || null; + this.value = options.value || ''; + this.dnAttributes = options.dnAttributes || false; + options.type = Protocol.FILTER_EXT; + Filter.call(this, options); + + var self = this; + this.__defineGetter__('json', function() { + return { + type: 'ExtensibleMatch', + matchRule: self.rule, + matchType: self.matchType, + matchValue: self.value, + dnAttributes: self.dnAttributes + }; + }); + this.__defineGetter__('matchingRule', function() { + return self.rule; + }); + this.__defineGetter__('matchValue', function() { + return self.value; + }); +} +util.inherits(ExtensibleFilter, Filter); +module.exports = ExtensibleFilter; + + +ExtensibleFilter.prototype.toString = function() { + var str = '('; + + if (this.matchType) + str += this.matchType; + + str += ':'; + + if (this.dnAttributes) + str += 'dn:'; + + if (this.rule) + str += this.rule + ':'; + + return (str + '=' + this.value + ')'); +}; + + +/** + * THIS IS A STUB! + * + * ldapjs does not support server side extensible matching. This class exists + * only for the client to send them. + * + * @param {Object} target the target object. + * @return {Boolean} false always. + */ +ExtensibleFilter.prototype.matches = function(target) { + if (typeof(target) !== 'object') + throw new TypeError('target (object) required'); + + return false; +}; + + +ExtensibleFilter.prototype.parse = function(ber) { + throw new Error('ExtensibleFilter not supported'); +}; + + +ExtensibleFilter.prototype._toBer = function(ber) { + assert.ok(ber); + + if (this.rule) + ber.writeString(this.rule, 0x81); + if (this.matchType) + ber.writeString(this.matchType, 0x82); + + ber.writeString(this.value, 0x83); + if (this.dnAttributes) + ber.writeBoolean(this.dnAttributes, 0x84); + + return ber; +}; diff --git a/lib/filters/index.js b/lib/filters/index.js index 088e96a..448fd4c 100644 --- a/lib/filters/index.js +++ b/lib/filters/index.js @@ -10,6 +10,7 @@ var Filter = require('./filter'); var AndFilter = require('./and_filter'); var ApproximateFilter = require('./approx_filter'); var EqualityFilter = require('./equality_filter'); +var ExtensibleFilter = require('./ext_filter'); var GreaterThanEqualsFilter = require('./ge_filter'); var LessThanEqualsFilter = require('./le_filter'); var NotFilter = require('./not_filter'); @@ -123,7 +124,7 @@ function _parseString(str) { })); } else { switch (stack[i].op) { - case '=': // could be presence, equality or substr + case '=': // could be presence, equality, substr or ext if (stack[i].value === '*') { filters.push(new PresenceFilter(stack[i])); } else { @@ -150,7 +151,32 @@ function _parseString(str) { } } if (vals.length === 1) { - filters.push(new EqualityFilter(stack[i])); + if (stack[i].attribute.indexOf(':') !== -1) { + var extTmp = stack[i].attribute.split(':'); + var extOpts = {}; + extOpts.matchType = extTmp[0]; + switch (extTmp.length) { + case 2: + break; + case 3: + if (extTmp[1].toLowerCase() === 'dn') { + extOpts.dnAttributes = true; + } else { + extOpts.rule = extTmp[1]; + } + break; + case 4: + extOpts.dnAttributes = true; + extOpts.rule = extTmp[2]; + break; + default: + throw new Error('Invalid extensible filter'); + } + extOpts.value = vals[0]; + filters.push(new ExtensibleFilter(extOpts)); + } else { + filters.push(new EqualityFilter(stack[i])); + } } else { filters.push(new SubstringFilter({ attribute: stack[i].attribute, @@ -245,6 +271,11 @@ function _parse(ber) { f.parse(ber); return f; + case Protocol.FILTER_EXT: + f = new ExtensibleFilter(); + f.parse(ber); + return f; + case Protocol.FILTER_GE: f = new GreaterThanEqualsFilter(); f.parse(ber); @@ -309,6 +340,7 @@ module.exports = { AndFilter: AndFilter, ApproximateFilter: ApproximateFilter, EqualityFilter: EqualityFilter, + ExtensibleFilter: ExtensibleFilter, GreaterThanEqualsFilter: GreaterThanEqualsFilter, LessThanEqualsFilter: LessThanEqualsFilter, NotFilter: NotFilter, diff --git a/tst/filters/ext.test.js b/tst/filters/ext.test.js new file mode 100644 index 0000000..e2e7135 --- /dev/null +++ b/tst/filters/ext.test.js @@ -0,0 +1,110 @@ +// Copyright 2011 Mark Cavage, Inc. All rights reserved. + +var test = require('tap').test; + +var asn1 = require('asn1'); + + +///--- Globals + +var ExtensibleFilter; +var BerReader = asn1.BerReader; +var BerWriter = asn1.BerWriter; +var filters; + + +///--- Tests + +test('load library', function(t) { + filters = require('../../lib/index').filters; + t.ok(filters); + ExtensibleFilter = filters.ExtensibleFilter; + t.ok(ExtensibleFilter); + t.end(); +}); + + +test('Construct no args', function(t) { + var f = new ExtensibleFilter(); + t.ok(f); + t.end(); +}); + + +test('Construct args', function(t) { + var f = new ExtensibleFilter({ + matchType: 'foo', + value: 'bar', + }); + t.ok(f); + t.equal(f.matchType, 'foo'); + t.equal(f.value, 'bar'); + t.equal(f.toString(), '(foo:=bar)'); + t.end(); +}); + + +test('parse RFC example 1', function(t) { + var f = filters.parseString('(cn:caseExactMatch:=Fred Flintstone)'); + t.ok(f); + t.equal(f.matchType, 'cn'); + t.equal(f.matchingRule, 'caseExactMatch'); + t.equal(f.matchValue, 'Fred Flintstone'); + t.notOk(f.dnAttributes); + t.end(); +}); + + +test('parse RFC example 2', function(t) { + var f = filters.parseString('(cn:=Betty Rubble)'); + t.ok(f); + t.equal(f.matchType, 'cn'); + t.equal(f.matchValue, 'Betty Rubble'); + t.notOk(f.dnAttributes); + t.notOk(f.matchingRule); + t.end(); +}); + + +test('parse RFC example 3', function(t) { + var f = filters.parseString('(sn:dn:2.4.6.8.10:=Barney Rubble)'); + t.ok(f); + t.equal(f.matchType, 'sn'); + t.equal(f.matchingRule, '2.4.6.8.10'); + t.equal(f.matchValue, 'Barney Rubble'); + t.ok(f.dnAttributes); + t.end(); +}); + + +test('parse RFC example 3', function(t) { + var f = filters.parseString('(o:dn:=Ace Industry)'); + t.ok(f); + t.equal(f.matchType, 'o'); + t.notOk(f.matchingRule); + t.equal(f.matchValue, 'Ace Industry'); + t.ok(f.dnAttributes); + t.end(); +}); + + +test('parse RFC example 4', function(t) { + var f = filters.parseString('(:1.2.3:=Wilma Flintstone)'); + t.ok(f); + t.notOk(f.matchType); + t.equal(f.matchingRule, '1.2.3'); + t.equal(f.matchValue, 'Wilma Flintstone'); + t.notOk(f.dnAttributes); + t.end(); +}); + + +test('parse RFC example 5', function(t) { + var f = filters.parseString('(:DN:2.4.6.8.10:=Dino)'); + t.ok(f); + t.notOk(f.matchType); + t.equal(f.matchingRule, '2.4.6.8.10'); + t.equal(f.matchValue, 'Dino'); + t.ok(f.dnAttributes); + t.end(); +});