First commit

This commit is contained in:
2025-10-08 11:12:59 -04:00
commit b0605a28a9
820 changed files with 100317 additions and 0 deletions

9
node_modules/@ldapjs/change/.eslintrc generated vendored Normal file
View File

@ -0,0 +1,9 @@
{
"parserOptions": {
"ecmaVersion": "latest"
},
"extends": [
"standard"
]
}

10
node_modules/@ldapjs/change/.github/workflows/main.yml generated vendored Normal file
View File

@ -0,0 +1,10 @@
name: "CI"
on:
pull_request:
push:
branches:
- master
jobs:
call-core-ci:
uses: ldapjs/.github/.github/workflows/node-ci.yml@main

6
node_modules/@ldapjs/change/.taprc.yaml generated vendored Normal file
View File

@ -0,0 +1,6 @@
reporter: terse
coverage-map: coverage-map.js
files:
- 'index.test.js'
# - 'lib/**/*.test.js'

21
node_modules/@ldapjs/change/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
Copyright (c) 2014 Patrick Mooney. All rights reserved.
Copyright (c) 2014 Mark Cavage, Inc. All rights reserved.
Copyright (c) 2022 The LDAPJS Collaborators.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE

8
node_modules/@ldapjs/change/README.md generated vendored Normal file
View File

@ -0,0 +1,8 @@
# change
Provides objects for managing changes as described in
[RFC 4511 §4.6](https://www.rfc-editor.org/rfc/rfc4511.html#section-4.6).
## License
MIT.

3
node_modules/@ldapjs/change/coverage-map.js generated vendored Normal file
View File

@ -0,0 +1,3 @@
'use strict'
module.exports = testFile => testFile.replace(/\.test\.js$/, '.js')

320
node_modules/@ldapjs/change/index.js generated vendored Normal file
View File

@ -0,0 +1,320 @@
'use strict'
const { BerReader, BerWriter } = require('@ldapjs/asn1')
const Attribute = require('@ldapjs/attribute')
/**
* Implements an LDAP CHANGE sequence as described in
* https://www.rfc-editor.org/rfc/rfc4511.html#section-4.6.
*/
class Change {
#operation
#modification
/**
* @typedef {object} ChangeParameters
* @property {string | number} operation One of `add` (0), `delete` (1), or
* `replace` (2). Default: `add`.
* @property {object | import('@ldapjs/attribute')} modification An attribute
* instance or an object that is shaped like an attribute.
*/
/**
* @param {ChangeParameters} input
*
* @throws When the `modification` parameter is invalid.
*/
constructor ({ operation = 'add', modification }) {
this.operation = operation
this.modification = modification
}
get [Symbol.toStringTag] () {
return 'LdapChange'
}
/**
* The attribute that will be modified by the {@link Change}.
*
* @returns {import('@ldapjs/attribute')}
*/
get modification () {
return this.#modification
}
/**
* Define the attribute to be modified by the {@link Change}.
*
* @param {object|import('@ldapjs/attribute')} mod
*
* @throws When `mod` is not an instance of `Attribute` or is not an
* `Attribute` shaped object.
*/
set modification (mod) {
if (Attribute.isAttribute(mod) === false) {
throw Error('modification must be an Attribute')
}
if (Object.prototype.toString.call(mod) !== '[object LdapAttribute]') {
mod = new Attribute(mod)
}
this.#modification = mod
}
/**
* Get a plain JavaScript object representation of the change.
*
* @returns {object}
*/
get pojo () {
return {
operation: this.operation,
modification: this.modification.pojo
}
}
/**
* The string name of the operation that will be performed.
*
* @returns {string} One of `add`, `delete`, or `replace`.
*/
get operation () {
switch (this.#operation) {
case 0x00: {
return 'add'
}
case 0x01: {
return 'delete'
}
case 0x02: {
return 'replace'
}
}
}
/**
* Define the operation that the {@link Change} represents.
*
* @param {string|number} op May be one of `add` (0), `delete` (1),
* or `replace` (2).
*
* @throws When the `op` is not recognized.
*/
set operation (op) {
if (typeof op === 'string') {
op = op.toLowerCase()
}
switch (op) {
case 0x00:
case 'add': {
this.#operation = 0x00
break
}
case 0x01:
case 'delete': {
this.#operation = 0x01
break
}
case 0x02:
case 'replace': {
this.#operation = 0x02
break
}
default: {
const type = Number.isInteger(op)
? '0x' + Number(op).toString(16)
: op
throw Error(`invalid operation type: ${type}`)
}
}
}
/**
* Serialize the instance to a BER.
*
* @returns {import('@ldapjs/asn1').BerReader}
*/
toBer () {
const writer = new BerWriter()
writer.startSequence()
writer.writeEnumeration(this.#operation)
const attrBer = this.#modification.toBer()
writer.appendBuffer(attrBer.buffer)
writer.endSequence()
return new BerReader(writer.buffer)
}
/**
* See {@link pojo}.
*
* @returns {object}
*/
toJSON () {
return this.pojo
}
/**
* Applies a {@link Change} to a `target` object.
*
* @example
* const change = new Change({
* operation: 'add',
* modification: {
* type: 'cn',
* values: ['new']
* }
* })
* const target = {
* cn: ['old']
* }
* Change.apply(change, target)
* // target = { cn: ['old', 'new'] }
*
* @param {Change} change The change to apply.
* @param {object} target The object to modify. This object will be mutated
* by the function. It should have properties that match the `modification`
* of the change.
* @param {boolean} scalar When `true`, will convert single-item arrays
* to scalar values. Default: `false`.
*
* @returns {object} The mutated `target`.
*
* @throws When the `change` is not an instance of {@link Change}.
*/
static apply (change, target, scalar = false) {
if (Change.isChange(change) === false) {
throw Error('change must be an instance of Change')
}
const type = change.modification.type
const values = change.modification.values
let data = target[type]
if (data === undefined) {
data = []
} else if (Array.isArray(data) === false) {
data = [data]
}
switch (change.operation) {
case 'add': {
// Add only new unique entries.
const newValues = values.filter(v => data.indexOf(v) === -1)
Array.prototype.push.apply(data, newValues)
break
}
case 'delete': {
data = data.filter(v => values.indexOf(v) === -1)
if (data.length === 0) {
// An empty list indicates the attribute should be removed
// completely.
delete target[type]
return target
}
break
}
case 'replace': {
if (values.length === 0) {
// A new value set that is empty is a delete.
delete target[type]
return target
}
data = values
break
}
}
if (scalar === true && data.length === 1) {
// Replace array value with a scalar value if the modified set is
// single valued and the operation calls for a scalar.
target[type] = data[0]
} else {
target[type] = data
}
return target
}
/**
* Determines if an object is an instance of {@link Change}, or at least
* resembles the shape of a {@link Change} object. A plain object will match
* if it has a `modification` property that matches an `Attribute`,
* an `operation` property that is a string or number, and has a `toBer`
* method. An object that resembles a {@link Change} does not guarantee
* compatibility. A `toString` check is much more accurate.
*
* @param {Change|object} change
*
* @returns {boolean}
*/
static isChange (change) {
if (Object.prototype.toString.call(change) === '[object LdapChange]') {
return true
}
if (Object.prototype.toString.call(change) !== '[object Object]') {
return false
}
if (
Attribute.isAttribute(change.modification) === true &&
(typeof change.operation === 'string' || typeof change.operation === 'number')
) {
return true
}
return false
}
/**
* Compares two {@link Change} instance to determine the priority of the
* changes relative to each other.
*
* @param {Change} change1
* @param {Change} change2
*
* @returns {number} -1 for lower priority, 1 for higher priority, and 0
* for equal priority in relation to `change1`, e.g. -1 would mean `change`
* has lower priority than `change2`.
*
* @throws When neither parameter resembles a {@link Change} object.
*/
static compare (change1, change2) {
if (Change.isChange(change1) === false || Change.isChange(change2) === false) {
throw Error('can only compare Change instances')
}
if (change1.operation < change2.operation) {
return -1
}
if (change1.operation > change2.operation) {
return 1
}
return Attribute.compare(change1.modification, change2.modification)
}
/**
* Parse a BER into a new {@link Change} object.
*
* @param {import('@ldapjs/asn1').BerReader} ber The BER to process. It must
* be at an offset that starts a new change sequence. The reader will be
* advanced to the end of the change sequence by this method.
*
* @returns {Change}
*
* @throws When there is an error processing the BER.
*/
static fromBer (ber) {
ber.readSequence()
const operation = ber.readEnumeration()
const modification = Attribute.fromBer(ber)
return new Change({ operation, modification })
}
}
module.exports = Change

422
node_modules/@ldapjs/change/index.test.js generated vendored Normal file
View File

@ -0,0 +1,422 @@
'use strict'
const tap = require('tap')
const { BerReader } = require('@ldapjs/asn1')
const Attribute = require('@ldapjs/attribute')
const Change = require('./index')
tap.test('constructor', t => {
t.test('throws for bad operation', async t => {
t.throws(
() => new Change({ operation: 'bad' }),
Error('invalid operation type: bad')
)
})
t.test('throws for bad modification', async t => {
t.throws(
() => new Change({ modification: 'bad' }),
Error('modification must be an Attribute')
)
})
t.test('creates an instance', async t => {
const change = new Change({
modification: new Attribute()
})
t.equal(change.operation, 'add')
t.type(change.modification, Attribute)
t.equal(Object.prototype.toString.call(change), '[object LdapChange]')
})
t.end()
})
tap.test('modification', t => {
t.test('gets', async t => {
const attr = new Attribute()
const change = new Change({ modification: attr })
t.equal(change.modification, attr)
})
t.test('sets', async t => {
const attr1 = new Attribute()
const attr2 = new Attribute()
const change = new Change({ modification: attr1 })
t.equal(change.modification, attr1)
change.modification = attr2
t.equal(change.modification, attr2)
t.not(attr1, attr2)
})
t.test('throws if value is not attribute-like', async t => {
const change = new Change({ modification: new Attribute() })
t.throws(
() => { change.modification = { foo: 'foo' } },
Error('modification must be an Attribute')
)
})
t.test('converts attribute-like to Attribute', async t => {
const change = new Change({
modification: {
type: 'dn=foo,dc=example,dc=com',
values: []
}
})
t.equal(
Object.prototype.toString.call(change.modification),
'[object LdapAttribute]'
)
})
t.end()
})
tap.test('.operation', t => {
const attr = new Attribute()
const change = new Change({ modification: attr })
t.test('throws for unrecognized operation', async t => {
t.throws(
() => { change.operation = 'bad' },
Error('invalid operation type: bad')
)
t.throws(
() => { change.operation = 0xff },
Error('invalid operation type: 0xff')
)
})
t.test('sets and gets', async t => {
change.operation = 0
t.equal(change.operation, 'add')
change.operation = 'add'
t.equal(change.operation, 'add')
change.operation = 1
t.equal(change.operation, 'delete')
change.operation = 'delete'
t.equal(change.operation, 'delete')
change.operation = 2
t.equal(change.operation, 'replace')
change.operation = 'replace'
t.equal(change.operation, 'replace')
change.operation = 'Replace'
t.equal(change.operation, 'replace')
})
t.end()
})
tap.test('.pojo', t => {
t.test('returns a plain object', async t => {
const change = new Change({
modification: new Attribute()
})
const expected = {
operation: 'add',
modification: {
type: '',
values: []
}
}
t.strictSame(change.pojo, expected)
t.strictSame(change.toJSON(), expected)
})
t.end()
})
tap.test('toBer', t => {
t.test('serializes to ber', async t => {
const expected = Buffer.from([
0x30, 0x15, // sequence, 21 bytes
0x0a, 0x01, 0x00, // enumerated value 0
0x30, 0x10, // sequence, 16 bytes
0x04, 0x02, // string, 2 bytes
0x63, 0x6e, // 'cn'
0x31, 0x0a, // sequence of strings, 10 bytes
0x04, 0x03, // string, 3 bytes
0x66, 0x6f, 0x6f, // 'foo'
0x04, 0x03, // string 3 bytes
0x62, 0x61, 0x72
])
const change = new Change({
modification: {
type: 'cn',
values: ['foo', 'bar']
}
})
const ber = change.toBer()
t.equal(expected.compare(ber.buffer), 0)
})
t.end()
})
tap.test('#apply', t => {
t.test('throws if change is not a Change', async t => {
t.throws(
() => Change.apply({}, {}),
Error('change must be an instance of Change')
)
})
t.test('applies to a target with no type', async t => {
const attr = new Attribute({
type: 'cn',
values: ['new']
})
const change = new Change({ modification: attr })
const target = {}
Change.apply(change, target)
t.strictSame(target, {
cn: ['new']
})
})
t.test('applies to a target with a scalar type', async t => {
const attr = new Attribute({
type: 'cn',
values: ['new']
})
const change = new Change({ modification: attr })
const target = { cn: 'old' }
Change.apply(change, target)
t.strictSame(target, {
cn: ['old', 'new']
})
})
t.test('applies to a target with an array type', async t => {
const attr = new Attribute({
type: 'cn',
values: ['new']
})
const change = new Change({ modification: attr })
const target = { cn: ['old'] }
Change.apply(change, target)
t.strictSame(target, {
cn: ['old', 'new']
})
})
t.test('add operation adds only new values', async t => {
const attr = new Attribute({
type: 'cn',
values: ['new', 'foo']
})
const change = new Change({ modification: attr })
const target = { cn: ['old', 'new'] }
Change.apply(change, target)
t.strictSame(target, {
cn: ['old', 'new', 'foo']
})
})
t.test('delete operation removes property', async t => {
const attr = new Attribute({
type: 'cn',
values: ['new']
})
const change = new Change({
operation: 'delete',
modification: attr
})
const target = { cn: ['new'] }
Change.apply(change, target)
t.strictSame(target, {})
})
t.test('delete operation removes values', async t => {
const attr = new Attribute({
type: 'cn',
values: ['remove_me']
})
const change = new Change({
operation: 'delete',
modification: attr
})
const target = { cn: ['remove_me', 'keep_me'] }
Change.apply(change, target)
t.strictSame(target, {
cn: ['keep_me']
})
})
t.test('replace removes empty set', async t => {
const attr = new Attribute({
type: 'cn',
values: []
})
const change = new Change({
operation: 'replace',
modification: attr
})
const target = { cn: ['old'] }
Change.apply(change, target)
t.strictSame(target, {})
})
t.test('replace removes values', async t => {
const attr = new Attribute({
type: 'cn',
values: ['new_set']
})
const change = new Change({
operation: 'replace',
modification: attr
})
const target = { cn: ['old_set'] }
Change.apply(change, target)
t.strictSame(target, {
cn: ['new_set']
})
})
t.test('scalar option works for new single values', async t => {
const attr = new Attribute({
type: 'cn',
values: ['new']
})
const change = new Change({ modification: attr })
const target = {}
Change.apply(change, target, true)
t.strictSame(target, {
cn: 'new'
})
})
t.test('scalar option is ignored for multiple values', async t => {
const attr = new Attribute({
type: 'cn',
values: ['new']
})
const change = new Change({ modification: attr })
const target = {
cn: ['old']
}
Change.apply(change, target, true)
t.strictSame(target, {
cn: ['old', 'new']
})
})
t.end()
})
tap.test('#isChange', t => {
t.test('true for instance', async t => {
const change = new Change({ modification: new Attribute() })
t.equal(Change.isChange(change), true)
})
t.test('false for non-object', async t => {
t.equal(Change.isChange([]), false)
})
t.test('true for shape match', async t => {
const change = {
operation: 'add',
modification: {
type: '',
values: []
}
}
t.equal(Change.isChange(change), true)
change.operation = 0
change.modification = new Attribute()
t.equal(Change.isChange(change), true)
})
t.test('false for shape mis-match', async t => {
const change = {
operation: 'add',
mod: {
type: '',
values: []
}
}
t.equal(Change.isChange(change), false)
})
t.end()
})
tap.test('#compare', t => {
t.test('throws if params are not changes', async t => {
const change = new Change({ modification: new Attribute() })
const expected = Error('can only compare Change instances')
t.throws(
() => Change.compare({}, change),
expected
)
t.throws(
() => Change.compare(change, {}),
expected
)
})
t.test('orders add first', async t => {
const change1 = new Change({ modification: new Attribute() })
const change2 = new Change({
operation: 'delete',
modification: new Attribute()
})
t.equal(Change.compare(change1, change2), -1)
change2.operation = 'replace'
t.equal(Change.compare(change1, change2), -1)
})
t.test('orders delete above add', async t => {
const change1 = new Change({ modification: new Attribute() })
const change2 = new Change({
operation: 'delete',
modification: new Attribute()
})
t.equal(Change.compare(change2, change1), 1)
})
t.test('orders by attribute for same operation', async t => {
const change1 = new Change({ modification: new Attribute() })
const change2 = new Change({ modification: new Attribute() })
t.equal(Change.compare(change1, change2), 0)
})
t.end()
})
tap.test('#fromBer', t => {
t.test('creates instance', async t => {
const bytes = [
0x30, 0x15, // sequence, 21 bytes
0x0a, 0x01, 0x00, // enumerated value 0
0x30, 0x10, // sequence, 16 bytes
0x04, 0x02, // string, 2 bytes
0x63, 0x6e, // 'cn'
0x31, 0x0a, // sequence of strings, 10 bytes
0x04, 0x03, // string, 3 bytes
0x66, 0x6f, 0x6f, // 'foo'
0x04, 0x03, // string 3 bytes
0x62, 0x61, 0x72
]
const reader = new BerReader(Buffer.from(bytes))
const change = Change.fromBer(reader)
t.strictSame(change.pojo, {
operation: 'add',
modification: {
type: 'cn',
values: ['foo', 'bar']
}
})
})
t.end()
})

43
node_modules/@ldapjs/change/package.json generated vendored Normal file
View File

@ -0,0 +1,43 @@
{
"originalAuthor": "Patrick Mooney",
"originalContributors": [
"Mark Cavage <mcavage@gmail.com>",
"Cody Peter Mello <cody.mello@joyent.com>"
],
"name": "@ldapjs/change",
"homepage": "https://github.com/ldapjs/change",
"description": "API for handling LDAP change objects",
"version": "1.0.0",
"license": "MIT",
"repository": {
"type": "git",
"url": "git@github.com:ldapjs/change.git"
},
"main": "index.js",
"dependencies": {
"@ldapjs/asn1": "2.0.0",
"@ldapjs/attribute": "1.0.0"
},
"devDependencies": {
"@fastify/pre-commit": "^2.0.2",
"eslint": "^8.34.0",
"eslint-config-standard": "^17.0.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-n": "^15.6.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^6.1.1",
"tap": "^16.3.4"
},
"scripts": {
"lint": "eslint .",
"lint:ci": "eslint .",
"test": "tap --no-coverage-report",
"test:cov": "tap",
"test:cov:html": "tap --coverage-report=html",
"test:watch": "tap -w --no-coverage-report"
},
"precommit": [
"lint",
"test"
]
}