@mntu/nestjs-ldap
Version:
NestJS library to access LDAP
218 lines (192 loc) • 6.6 kB
text/typescript
import { BerWriter, BerReader } from 'asn1';
import { Attribute } from './attribute';
export class Change {
private _modification: Attribute;
private _operation?: number;
get operation(): 'add' | 'delete' | 'replace' {
switch (this._operation) {
case 0x00:
return 'add';
case 0x01:
return 'delete';
case 0x02:
return 'replace';
default:
throw new Error(
`0x${this._operation?.toString(16)} is invalid`,
);
}
}
set operation(value: 'add' | 'delete' | 'replace') {
switch (value) {
case 'add':
this._operation = 0x00;
break;
case 'delete':
this._operation = 0x01;
break;
case 'replace':
this._operation = 0x02;
break;
default:
throw new Error(
`Invalid operation type: 0x${Number(value).toString(16)}`,
);
}
}
get modification(): Attribute | Record<string, any> {
return this._modification;
}
set modification(value: Attribute | Record<string, any>) {
if (Attribute.isAttribute(value)) {
this._modification = value;
return;
}
// Does it have an attribute-like structure
if (
Object.keys(value).length === 2 &&
typeof value.type === 'string' &&
Array.isArray(value.vals)
) {
this._modification = new Attribute({
type: value.type,
vals: value.vals,
});
return;
}
const keys = Object.keys(value);
if (keys.length > 1) {
throw new Error('Only one attribute per Change allowed');
} else if (keys.length === 0) {
return;
}
const k = keys[0];
const _attribute = new Attribute({ type: k });
if (Array.isArray(value[k])) {
value[k].forEach((v: string | Buffer): void => {
_attribute.addValue(v);
});
} else if (Buffer.isBuffer(value[k])) {
_attribute.addValue(value[k]);
} else if (value[k] !== undefined && value[k] !== null) {
_attribute.addValue(value[k]);
}
this._modification = _attribute;
}
constructor(options: Record<string, any> = { operation: 'add' }) {
this._modification = new Attribute();
this.operation = options.operation || options.type || 'add';
this.modification = options.modification || {};
}
isChange = (change: Change | Record<string, any>): boolean => {
if (!change || typeof change !== 'object') {
return false;
}
if (
change instanceof Change ||
(typeof change.toBer === 'function' &&
change.modification !== undefined &&
change.operation !== undefined)
) {
return true;
}
return false;
};
compare = (
a: Change | Record<string, any>,
b: Change | Record<string, any>,
): number => {
if (!this.isChange(a) || !this.isChange(b)) {
throw new TypeError('can only compare Changes');
}
if (a.operation < b.operation) return -1;
if (a.operation > b.operation) return 1;
return Attribute.compare(a.modification, b.modification);
};
/**
* Apply a Change to properties of an object.
*
* @param {Object} change the change to apply.
* @param {Object} obj the object to apply it to.
* @param {Boolean} scalar convert single-item arrays to scalars. Default: false
*/
apply = (
change: Record<string, any> = {
operation: 'add',
modification: { type: '' },
},
object: Record<string, any>,
scalar: any,
): any => {
const { type } = change.modification;
const { vals } = change.modification;
let data = object[type];
if (data !== undefined) {
if (!Array.isArray(data)) {
data = [data];
}
} else {
data = [];
}
switch (change.operation) {
case 'replace':
if (vals.length === 0) {
// replace empty is a delete
delete object[type];
return object;
}
data = vals;
break;
case 'add': {
// add only new unique entries
const newValues = vals.filter(
(entry: any) => !data.includes(entry),
);
data = data.concat(newValues);
break;
}
case 'delete':
data = data.filter((entry: any) => !vals.includes(entry));
if (data.length === 0) {
// Erase the attribute if empty
delete object[type];
return object;
}
break;
default:
break;
}
if (scalar && data.length === 1) {
// store single-value outputs as scalars, if requested
// eslint-disable-next-line prefer-destructuring
object[type] = data[0];
} else {
object[type] = data;
}
return object;
};
parse = (ber?: BerReader): boolean => {
if (!ber) {
return false;
}
ber.readSequence();
this._operation = ber.readEnumeration();
this._modification = new Attribute();
this._modification.parse(ber);
return true;
};
toBer = (ber?: BerWriter): BerWriter => {
if (!ber) {
throw new TypeError('ldapjs Change toBer: ber is undefined');
}
ber.startSequence();
ber.writeEnumeration(this._operation || 0x00);
ber = this._modification?.toBer(ber);
ber.endSequence();
return ber;
};
json = (): Record<string, any> => ({
operation: this.operation,
modification: this._modification ? this._modification.json : {},
});
}