如何将参数传递给 ldapjs exop() 函数?

我正在尝试使用“更改用户密码”扩展操作,如 this RFC 中所定义,该操作声明它采用三个可选参数的序列。但是,似乎 ldapjs 的 client.exop() 函数只允许我为其提供字符串或缓冲区。

这是我的尝试:

const dn = `uid=${username},ou=People,dc=${orginization},dc=com`
client.exop('1.3.6.1.4.1.4203.1.11.1',[dn,null,newPassword],(err,value,res) => {
  // ...
})

这是由此产生的错误:

TypeError: options.requestvalue must be a buffer or a string

我应该如何将这些值编码为字符串? ldapjs 文档很少提供有关将参数传递给扩展操作的信息。

lokeven 回答:如何将参数传递给 ldapjs exop() 函数?

TL:DR; 扩展操作参数需要是按照BER标准编码的ASN.1值。这不是一项微不足道的任务,因此您可能需要一个额外的 npm 库,例如 asn1 来帮助完成此过程。

在梳理了 ldapjs 的代码,阅读了大量关于 ASN.1 以及 LDAP 如何使用 ASN.1 标准的内容后,经过一些试验和错误,我终于能够解决这个问题。由于明显缺乏这方面的文档,我想我会分享我在 stackoverflow 上学到的东西,这样其他人就不需要像我一样经历那么多麻烦。

一个工作示例

这使用 asn1 npm 库对发送的数据进行编码。

const { Ber } = require('asn1')

// ...

const CTX_SPECIFIC_CLASS = 0b10 << 6
const writer = new Ber.Writer()
writer.startSequence()
writer.writeString(dn,CTX_SPECIFIC_CLASS | 0) // sequence item number 0
// I'm choosing to omit the optional sequence item number 1
writer.writeString(newPassword,CTX_SPECIFIC_CLASS | 2) // sequence item number 2
writer.endSequence()
client.exop('1.3.6.1.4.1.4203.1.11.1',writer.buffer,(err,value,res) => {
  // ...
})

什么是 ASN.1?

ASN.1 是一种用于描述对象接口的语言。这些接口的特殊之处在于它们与语言无关 - 即 javascript 可以创建一个符合这些接口之一的对象,对其进行编码,然后将其发送到 python 服务器,然后该服务器根据相同的接口解码和验证对象。 ASN.1 的大部分内容与我们试图完成的事情无关,但重要的是要注意我们试图做的是创建一个符合这些 ASN.1 接口之一的对象(LDAP 是围绕他们)。

什么是误码率?

BER 描述了一种表示符合 ASN.1 接口的对象的标准方法。使用 BER 标准,我们可以将 javascript 数据编码到 LDAP 服务器可以理解的缓冲区中。

BER 基础知识

BER 旨在成为一种非常紧凑的编码标准。我将在这里介绍基础知识,但如果您想了解有关 BER 二进制表示的更多详细信息(它是为 LDAP 用户量身定制的),我强烈推荐 this articleA Layman's Guide to a Subset of ASN.1,BER,and DER 是另一个很好的资源。

ASN.1 描述了许多基本对象类型,例如字符串和数字,并且它描述了结构化对象类型,例如序列或集合。它还为用户提供了使用他们自己的自定义类型的能力。

在 BER 中,每条数据都以两个字节为前缀(通常):一个标识符字节和一个数据长度字节。标识符字节用有关它包含的数据类型的信息标记数据(字符串?序列?自定义类型?)。标签有四种“类别”:通用(例如字符串)、应用程序(LDAP 定义了一些您可能会遇到的应用程序标签)、特定于上下文(请参阅下面的“BER 序列”部分)和私有(不太可能适用)这里)。字符串标记的位序列将始终被解释为字符串标记,但自定义标记的位序列的含义可能因环境甚至在请求中而异。

在asn1 npm库中,可以写出一个字符串元素如下:

writer.writeString('text')

要查找所有可用函数,该库的作者要求您查看 the source code

BER 序列

序列用于描述具有特定形状的对象(一组键值对)。某些元素可能是可选的,而其他元素是必需的。我所关注的 RFC 对其参数进行了以下描述。我们需要符合这个序列的接口,以便将我们的密码重置参数发送到 LDAP。

PasswdModifyRequestValue ::= SEQUENCE {
  userIdentity    [0]  OCTET STRING OPTIONAL
  oldPasswd       [1]  OCTET STRING OPTIONAL
  newPasswd       [2]  OCTET STRING OPTIONAL }

[0][1][2] 均指上下文特定的标签编号。使用上下文特定标记 1 标记的值将被解释为 oldPasswd 参数的值。我们不需要使用全局字符串标记来指示我们的值是字符串类型——LDAP 已经可以使用我们遵循的接口推断出该信息。这意味着在按此顺序写入字符串时,不是像之前那样做 writer.writeString('text')(自动使用全局字符串标签),而是必须提供如下标签编号:

const CTX_SPECIFIC_CLASS = 0b10 << 6
writer.writeString(newPassword,CTX_SPECIFIC_CLASS | 2) // The second optional parameter allows you to set a custom tag on the data being set (instead of the default string tag).

标签字节的前两位保留用于指定标签类(在这种情况下,它是特定于上下文的类,或位“10”)。因此,CTX_SPECIFIC_CLASS | 2 指的是 RFC 描述的 newPasswd 序列项。请注意,如果我想省略一个可选的序列条目,我只是不写出用该序列 ID 标记的值。

结束语

希望这应该为读者提供足够的信息,以便能够为扩展的 LDAP 操作格式化和发送 BER 编码的参数。我想指出的是,我不是 ASN.1/BER 专家 - 以上所有信息只是我在过去几天自己的研究中理解这些概念的方式。所以,这篇文章中可能有一些错误解释的地方。如果您碰巧比我更了解这个主题,请随时编辑它。

本文链接:https://www.f2er.com/1095971.html

大家都在问