Skip to content
This repository has been archived by the owner on May 14, 2024. It is now read-only.

Commit

Permalink
Merge pull request #4 from its-sami/master
Browse files Browse the repository at this point in the history
  • Loading branch information
jsumners authored Nov 2, 2023
2 parents 9718b85 + f71dde0 commit e55985d
Show file tree
Hide file tree
Showing 4 changed files with 259 additions and 0 deletions.
7 changes: 7 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const { Ber } = require('@ldapjs/asn1')
const Control = require('./lib/control')
const EntryChangeNotificationControl = require('./lib/controls/entry-change-notification-control')
const PagedResultsControl = require('./lib/controls/paged-results-control')
const PasswordPolicyControl = require('./lib/controls/password-policy-control')
const PersistentSearchControl = require('./lib/controls/persistent-search-control')
const ServerSideSortingRequestControl = require('./lib/controls/server-side-sorting-request-control')
const ServerSideSortingResponseControl = require('./lib/controls/server-side-sorting-response-control')
Expand Down Expand Up @@ -50,6 +51,11 @@ module.exports = {
break
}

case PasswordPolicyControl.OID: {
control = new PasswordPolicyControl(opts)
break
}

case PersistentSearchControl.OID: {
control = new PersistentSearchControl(opts)
break
Expand Down Expand Up @@ -88,6 +94,7 @@ module.exports = {
Control,
EntryChangeNotificationControl,
PagedResultsControl,
PasswordPolicyControl,
PersistentSearchControl,
ServerSideSortingRequestControl,
ServerSideSortingResponseControl,
Expand Down
21 changes: 21 additions & 0 deletions index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,27 @@ tap.test('#getControl', t => {
t.equal(Buffer.compare(c.value.cookie, Buffer.alloc(0)), 0)
})

t.test('returns a PasswordPolicyControl', async t => {
const ppc = new controls.PasswordPolicyControl({
type: controls.PasswordPolicyControl.OID,
criticality: true,
value: {
error: 1,
timeBeforeExpiration: 2
}
})

const ber = new BerWriter()
ppc.toBer(ber)

const c = controls.getControl(new BerReader(ber.buffer))
t.ok(c)
t.equal(c.type, controls.PasswordPolicyControl.OID)
t.ok(c.criticality)
t.equal(c.value.error, 1)
t.equal(c.value.timeBeforeExpiration, 2)
})

t.test('returns a PersistentSearchControl', async t => {
const buf = Buffer.from([
0x30, 0x26, 0x04, 0x17, 0x32, 0x2e, 0x31, 0x36, 0x2e, 0x38, 0x34, 0x30,
Expand Down
118 changes: 118 additions & 0 deletions lib/controls/password-policy-control.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
'use strict'

const { BerReader, BerWriter } = require('@ldapjs/asn1')
const isObject = require('../is-object')
const hasOwn = require('../has-own')
const Control = require('../control')

/**
* @typedef {object} PasswordPolicyResponseControlValue
* @property {number} error One of 0 (passwordExpired), 1 (accountLocked),
* 2 (changeAfterReset), 3 (passwordModNotAllowed), 4 (mustSupplyOldPassword),
* 5 (insufficientPasswordQuality), 6 (passwordTooShort), 7 (passwordTooYoung),
* 8 (passwordInHistory), 9 (passwordTooYoung)
* @property {number} timeBeforeExpiration
* @property {number} graceAuthNsRemaining
*/

/**
* Implements both request and response controls:
* https://datatracker.ietf.org/doc/html/draft-behera-ldap-password-policy-11#name-controls-used-for-password-
*
* @extends Control
*/
class PasswordPolicyControl extends Control {
static OID = '1.3.6.1.4.1.42.2.27.8.5.1'

/**
* @typedef {ControlParams} PasswordPolicyResponseParams
* @property {PasswordPolicyResponseControlValue | Buffer} [value]
*/

/**
* Creates a new password policy control.
*
* @param {PasswordPolicyResponseParams} [options]
*/
constructor (options = {}) {
options.type = PasswordPolicyControl.OID
super(options)

this._value = {}

if (hasOwn(options, 'value') === false) {
return
}

if (Buffer.isBuffer(options.value)) {
this.#parse(options.value)
} else if (isObject(options.value)) {
if (hasOwn(options.value, 'timeBeforeExpiration') === true && hasOwn(options.value, 'graceAuthNsRemaining') === true) {
throw new Error('options.value must contain either timeBeforeExpiration or graceAuthNsRemaining, not both')
}
this._value = options.value
} else {
throw new TypeError('options.value must be a Buffer or Object')
}
}

get value () {
return this._value
}

set value (obj) {
this._value = Object.assign({}, this._value, obj)
}

/**
* Given a BER buffer that represents a
* {@link PasswordPolicyResponseControlValue}, read that buffer into the
* current instance.
*/
#parse (buffer) {
const ber = new BerReader(buffer)
if (ber.readSequence()) {
this._value = {}
if (ber.peek() === 0xa0) {
ber.readSequence(0xa0)
if (ber.peek() === 0x80) {
this._value.timeBeforeExpiration = ber._readTag(0x80)
} else if (ber.peek() === 0x81) {
this._value.graceAuthNsRemaining = ber._readTag(0x81)
}
}
if (ber.peek() === 0x81) {
this._value.error = ber._readTag(0x81)
}
}
}

_toBer (ber) {
if (!this._value || Object.keys(this._value).length === 0) { return }

const writer = new BerWriter()
writer.startSequence()
if (hasOwn(this._value, 'timeBeforeExpiration')) {
writer.startSequence(0xa0)
writer.writeInt(this._value.timeBeforeExpiration, 0x80)
writer.endSequence()
} else if (hasOwn(this._value, 'graceAuthNsRemaining')) {
writer.startSequence(0xa0)
writer.writeInt(this._value.graceAuthNsRemaining, 0x81)
writer.endSequence()
}
if (hasOwn(this._value, 'error')) {
writer.writeInt(this._value.error, 0x81)
}
writer.endSequence()

ber.writeBuffer(writer.buffer, 0x04)
return ber
}

_updatePlainObject (obj) {
obj.controlValue = this.value
return obj
}
}
module.exports = PasswordPolicyControl
113 changes: 113 additions & 0 deletions lib/controls/password-policy-control.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
'use strict'

const tap = require('tap')
const { BerWriter } = require('@ldapjs/asn1')
const PPC = require('./password-policy-control')
const Control = require('../control')

tap.test('contructor', t => {
t.test('new no args', async t => {
const control = new PPC()
t.ok(control)
t.type(control, PPC)
t.type(control, Control)
t.equal(control.type, PPC.OID)
t.same(control.value, {})
})

t.test('new with args', async t => {
const control = new PPC({
type: '1.3.6.1.4.1.42.2.27.8.5.1',
criticality: true,
value: {
error: 1,
timeBeforeExpiration: 2
}
})
t.ok(control)
t.equal(control.type, '1.3.6.1.4.1.42.2.27.8.5.1')
t.ok(control.criticality)
t.same(control.value, {
error: 1,
timeBeforeExpiration: 2
})
})

t.test('with value buffer', async t => {
const value = new BerWriter()
value.startSequence()
value.writeInt(5, 0x81)
value.endSequence()

const control = new PPC({ value: value.buffer })
t.same(control.value, {
error: 5
})
})

t.test('throws for bad value', async t => {
t.throws(() => new PPC({ value: 42 }))
t.throws(() => new PPC({ value: { timeBeforeExpiration: 1, graceAuthNsRemaining: 2 } }))
})

t.end()
})

tap.test('pojo', t => {
t.test('adds control value', async t => {
const control = new PPC()
t.same(control.pojo, {
type: PPC.OID,
criticality: false,
value: {}
})
})

t.end()
})

tap.test('toBer', t => {
t.test('converts empty instance to BER', async t => {
const target = new BerWriter()
target.startSequence()
target.writeString(PPC.OID)
target.writeBoolean(false) // Control.criticality
target.endSequence()

const control = new PPC()
const ber = control.toBer()

t.equal(Buffer.compare(ber.buffer, target.buffer), 0)
})

t.test('converts full instance to BER', async t => {
const target = new BerWriter()
target.startSequence()
target.writeString(PPC.OID)
target.writeBoolean(true) // Control.criticality

const value = new BerWriter()
value.startSequence()
value.startSequence(0xa0)
value.writeInt(2, 0x81)
value.endSequence()
value.writeInt(1, 0x81)
value.endSequence()

target.writeBuffer(value.buffer, 0x04)
target.endSequence()

const control = new PPC({
criticality: true,
value: {
error: 1,
graceAuthNsRemaining: 2
}
})
const ber = control.toBer()

t.equal(Buffer.compare(ber.buffer, target.buffer), 0)
})

t.end()
})

0 comments on commit e55985d

Please sign in to comment.