@ldapjs/messages
Version:
API for creating and parsing LDAP messages
285 lines (255 loc) • 7.19 kB
JavaScript
'use strict'
const LdapMessage = require('../ldap-message')
const { operations } = require('@ldapjs/protocol')
const RECOGNIZED_OIDS = require('./extension-utils/recognized-oids')
/**
* Implements the extension request message as described in
* https://www.rfc-editor.org/rfc/rfc4511.html#section-4.12.
*
* There is a set of supported extension request OIDs supported. Any
* unrecognized OID will be treated a simple string pair, i.e. both
* `requestName` and `requestValue` will be assumed to be simple strings.
*/
class ExtensionRequest extends LdapMessage {
#requestName
#requestValue
/**
* @typedef {LdapMessageOptions} ExtensionRequestOptions
* @property {string} [requestName=''] The name of the extension, i.e.
* OID for the request.
* @property {string|object} [requestValue] The value for the request.
* If `undefined`, no value will be sent. If the request requires a simple
* string value, provide such a string. For complex valued requests, e.g.
* for a password modify request, it should be a plain object with the
* appropriate properties. See the implementation of {@link parseToPojo}
* for the set of supported objects.
*/
/**
* @param {ExtensionRequestOptions} [options]
*/
constructor (options = {}) {
options.protocolOp = operations.LDAP_REQ_EXTENSION
super(options)
this.requestName = options.requestName || ''
this.requestValue = options.requestValue
}
/**
* Alias of {@link requestName}.
*
* @type {string}
*/
get _dn () {
return this.#requestName
}
/**
* The name (OID) of the request.
*
* @returns {string}
*/
get requestName () {
return this.#requestName
}
/**
* Set the name for the request. Should be an OID that
* matches a specification.
*
* @param {string} value
*/
set requestName (value) {
this.#requestName = value
}
/**
* The name of the request type.
*
* @type {string}
*/
get type () {
return 'ExtensionRequest'
}
/**
* The value, if any, for the request.
*
* @returns {undefined|string|object} value
*/
get requestValue () {
return this.#requestValue
}
/**
* Set the value for the request. The value should conform
* to the specification identified by the {@link requestName}.
* See the implemenation of {@link parseToPojo} for valid
* value shapes.
*
* @param {undefined|string|object} value
*/
set requestValue (val) {
this.#requestValue = val
}
/**
* Internal use only.
*
* @param {import('@ldapjs/asn1').BerWriter} ber
*
* @returns {import('@ldapjs/asn1').BerWriter}
*/
_toBer (ber) {
ber.startSequence(operations.LDAP_REQ_EXTENSION)
ber.writeString(this.requestName, 0x80)
if (this.requestValue) {
switch (this.requestName) {
case RECOGNIZED_OIDS.get('CANCEL_REQUEST'): {
encodeCancelRequest({ ber, requestValue: this.requestValue })
break
}
case RECOGNIZED_OIDS.get('PASSWORD_MODIFY'): {
encodePasswordModify({
ber,
requestValue: this.requestValue
})
break
}
default: {
// We assume the value is a plain string since
// we do not recognize the request OID, or we know
// that the OID uses a plain string value.
ber.writeString(this.requestValue, 0x81)
}
}
}
ber.endSequence()
return ber
}
/**
* Internal use only.
*
* @param {object}
*
* @returns {object}
*/
_pojo (obj = {}) {
obj.requestName = this.requestName
obj.requestValue = this.requestValue
return obj
}
/**
* Implements the standardized `parseToPojo` method.
*
* @see LdapMessage.parseToPojo
*
* @param {import('@ldapjs/asn1').BerReader} ber
*/
static parseToPojo (ber) {
const protocolOp = ber.readSequence()
if (protocolOp !== operations.LDAP_REQ_EXTENSION) {
const op = protocolOp.toString(16).padStart(2, '0')
throw Error(`found wrong protocol operation: 0x${op}`)
}
// While the requestName is an OID, it is not an
// _encoded_ OID. It is a plain string. So we do
// not use `.readOID` here.
const requestName = ber.readString(0x80)
if (ber.peek() !== 0x81) {
// There is not a request value present, so we just
// return an empty value representation.
return { protocolOp, requestName }
}
let requestValue
switch (requestName) {
case RECOGNIZED_OIDS.get('CANCEL_REQUEST'): {
requestValue = readCancelRequest(ber)
break
}
case RECOGNIZED_OIDS.get('PASSWORD_MODIFY'): {
requestValue = readPasswordModify(ber)
break
}
default: {
// We will assume it is a plain string value
// since we do not recognize the OID, or we know
// that the OID uses a plain string value.
requestValue = ber.readString(0x81)
break
}
}
return { protocolOp, requestName, requestValue }
}
/**
* A list of EXTENDED operation OIDs that this module
* recognizes. Key names are named according to the common name
* of the extension. Key values are the OID associated with that
* extension. For example, key `PASSWORD_MODIFY` corresponds to
* OID `1.3.6.1.4.1.4203.1.11.1`.
*
* @returns {Map<string, string>}
*/
static recognizedOIDs () {
return RECOGNIZED_OIDS
}
}
module.exports = ExtensionRequest
/**
* @param {object} input
* @param {@import('@ldapjs/asn1').BerWriter} input.ber
* @param {object} requestValue
*/
function encodeCancelRequest ({ ber, requestValue }) {
ber.startSequence(0x81)
ber.startSequence()
ber.writeInt(requestValue)
ber.endSequence()
ber.endSequence()
}
/**
* @param {@import('@ldapjs/asn1').BerReader} ber
* @returns {number}
*/
function readCancelRequest (ber) {
ber.readSequence(0x81)
ber.readSequence()
return ber.readInt()
}
/**
* @param {object} input
* @param {@import('@ldapjs/asn1').BerWriter} input.ber
* @param {object} requestValue
*/
function encodePasswordModify ({ ber, requestValue }) {
// start the value sequence
ber.startSequence(0x81)
// start the generic packed sequence
ber.startSequence()
if (requestValue.userIdentity) {
ber.writeString(requestValue.userIdentity, 0x80)
}
if (requestValue.oldPassword) {
ber.writeString(requestValue.oldPassword, 0x81)
}
if (requestValue.newPassword) {
ber.writeString(requestValue.newPassword, 0x82)
}
ber.endSequence()
ber.endSequence()
}
/**
* @param {@import('@ldapjs/asn1').BerReader} ber
* @returns {object}
*/
function readPasswordModify (ber) {
// advance to the embedded sequence
ber.readSequence(0x81)
// advance to the value of the embedded sequence
ber.readSequence()
let userIdentity
if (ber.peek() === 0x80) {
userIdentity = ber.readString(0x80)
}
let oldPassword
if (ber.peek() === 0x81) {
oldPassword = ber.readString(0x81)
}
let newPassword
if (ber.peek() === 0x82) {
newPassword = ber.readString(0x82)
}
return { userIdentity, oldPassword, newPassword }
}