Skip to content

Commit 601814b

Browse files
authored
feat: add Extended request operations (#516)
1 parent 25c2d48 commit 601814b

6 files changed

+284
-0
lines changed

client.go

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type Client interface {
2828
Modify(*ModifyRequest) error
2929
ModifyDN(*ModifyDNRequest) error
3030
ModifyWithResult(*ModifyRequest) (*ModifyResult, error)
31+
Extended(*ExtendedRequest) (*ExtendedResponse, error)
3132

3233
Compare(dn, attribute, value string) (bool, error)
3334
PasswordModify(*PasswordModifyRequest) (*PasswordModifyResult, error)

extended.go

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package ldap
2+
3+
import (
4+
"fmt"
5+
ber "github.com/go-asn1-ber/asn1-ber"
6+
)
7+
8+
// ExtendedRequest represents an extended request to send to the server
9+
// See: https://www.rfc-editor.org/rfc/rfc4511#section-4.12
10+
type ExtendedRequest struct {
11+
// ExtendedRequest ::= [APPLICATION 23] SEQUENCE {
12+
// requestName [0] LDAPOID,
13+
// requestValue [1] OCTET STRING OPTIONAL }
14+
15+
Name string
16+
Value *ber.Packet
17+
Controls []Control
18+
}
19+
20+
// NewExtendedRequest returns a new ExtendedRequest. The value can be
21+
// nil depending on the type of request
22+
func NewExtendedRequest(name string, value *ber.Packet) *ExtendedRequest {
23+
return &ExtendedRequest{
24+
Name: name,
25+
Value: value,
26+
}
27+
}
28+
29+
func (er ExtendedRequest) appendTo(envelope *ber.Packet) error {
30+
pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationExtendedRequest, nil, "Extended Request")
31+
pkt.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, ber.TagEOC, er.Name, "Extended Request Name"))
32+
if er.Value != nil {
33+
pkt.AppendChild(er.Value)
34+
}
35+
envelope.AppendChild(pkt)
36+
if len(er.Controls) > 0 {
37+
envelope.AppendChild(encodeControls(er.Controls))
38+
}
39+
return nil
40+
}
41+
42+
// ExtendedResponse represents the response from the directory server
43+
// after sending an extended request
44+
// See: https://www.rfc-editor.org/rfc/rfc4511#section-4.12
45+
type ExtendedResponse struct {
46+
// ExtendedResponse ::= [APPLICATION 24] SEQUENCE {
47+
// COMPONENTS OF LDAPResult,
48+
// responseName [10] LDAPOID OPTIONAL,
49+
// responseValue [11] OCTET STRING OPTIONAL }
50+
51+
Name string
52+
Value *ber.Packet
53+
Controls []Control
54+
}
55+
56+
// Extended performs an extended request. The resulting
57+
// ExtendedResponse may return a value in the form of a *ber.Packet
58+
func (l *Conn) Extended(er *ExtendedRequest) (*ExtendedResponse, error) {
59+
msgCtx, err := l.doRequest(er)
60+
if err != nil {
61+
return nil, err
62+
}
63+
defer l.finishMessage(msgCtx)
64+
65+
packet, err := l.readPacket(msgCtx)
66+
if err != nil {
67+
return nil, err
68+
}
69+
if err = GetLDAPError(packet); err != nil {
70+
return nil, err
71+
}
72+
73+
if len(packet.Children[1].Children) < 4 {
74+
return nil, fmt.Errorf(
75+
"ldap: malformed extended response: expected 4 children, got %d",
76+
len(packet.Children),
77+
)
78+
}
79+
80+
response := &ExtendedResponse{
81+
Name: packet.Children[1].Children[3].Data.String(),
82+
Controls: make([]Control, 0),
83+
}
84+
85+
if len(packet.Children) == 3 {
86+
for _, child := range packet.Children[2].Children {
87+
decodedChild, decodeErr := DecodeControl(child)
88+
if decodeErr != nil {
89+
return nil, fmt.Errorf("failed to decode child control: %s", decodeErr)
90+
}
91+
response.Controls = append(response.Controls, decodedChild)
92+
}
93+
}
94+
95+
if len(packet.Children[1].Children) == 5 {
96+
response.Value = packet.Children[1].Children[4]
97+
}
98+
99+
return response, nil
100+
}

extended_test.go

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package ldap
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestExtendedRequest_WhoAmI(t *testing.T) {
8+
l, err := DialURL(ldapServer)
9+
if err != nil {
10+
t.Errorf("%s failed: %v", t.Name(), err)
11+
return
12+
}
13+
defer l.Close()
14+
15+
l.Bind("", "") // anonymous
16+
defer l.Unbind()
17+
18+
rfc4532req := NewExtendedRequest("1.3.6.1.4.1.4203.1.11.3", nil) // request value is <nil>
19+
20+
var rfc4532resp *ExtendedResponse
21+
if rfc4532resp, err = l.Extended(rfc4532req); err != nil {
22+
t.Errorf("%s failed: %v", t.Name(), err)
23+
return
24+
}
25+
t.Logf("%#v\n", rfc4532resp)
26+
}
27+
28+
func TestExtendedRequest_FastBind(t *testing.T) {
29+
conn, err := DialURL(ldapServer)
30+
if err != nil {
31+
t.Error(err)
32+
}
33+
defer conn.Close()
34+
35+
request := NewExtendedRequest("1.3.6.1.4.1.4203.1.11.3", nil)
36+
_, err = conn.Extended(request)
37+
if err != nil {
38+
t.Errorf("%s failed: %v", t.Name(), err)
39+
return
40+
}
41+
}

v3/client.go

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type Client interface {
2828
Modify(*ModifyRequest) error
2929
ModifyDN(*ModifyDNRequest) error
3030
ModifyWithResult(*ModifyRequest) (*ModifyResult, error)
31+
Extended(*ExtendedRequest) (*ExtendedResponse, error)
3132

3233
Compare(dn, attribute, value string) (bool, error)
3334
PasswordModify(*PasswordModifyRequest) (*PasswordModifyResult, error)

v3/extended.go

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package ldap
2+
3+
import (
4+
"fmt"
5+
ber "github.com/go-asn1-ber/asn1-ber"
6+
)
7+
8+
// ExtendedRequest represents an extended request to send to the server
9+
// See: https://www.rfc-editor.org/rfc/rfc4511#section-4.12
10+
type ExtendedRequest struct {
11+
// ExtendedRequest ::= [APPLICATION 23] SEQUENCE {
12+
// requestName [0] LDAPOID,
13+
// requestValue [1] OCTET STRING OPTIONAL }
14+
15+
Name string
16+
Value *ber.Packet
17+
Controls []Control
18+
}
19+
20+
// NewExtendedRequest returns a new ExtendedRequest. The value can be
21+
// nil depending on the type of request
22+
func NewExtendedRequest(name string, value *ber.Packet) *ExtendedRequest {
23+
return &ExtendedRequest{
24+
Name: name,
25+
Value: value,
26+
}
27+
}
28+
29+
func (er ExtendedRequest) appendTo(envelope *ber.Packet) error {
30+
pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationExtendedRequest, nil, "Extended Request")
31+
pkt.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, ber.TagEOC, er.Name, "Extended Request Name"))
32+
if er.Value != nil {
33+
pkt.AppendChild(er.Value)
34+
}
35+
envelope.AppendChild(pkt)
36+
if len(er.Controls) > 0 {
37+
envelope.AppendChild(encodeControls(er.Controls))
38+
}
39+
return nil
40+
}
41+
42+
// ExtendedResponse represents the response from the directory server
43+
// after sending an extended request
44+
// See: https://www.rfc-editor.org/rfc/rfc4511#section-4.12
45+
type ExtendedResponse struct {
46+
// ExtendedResponse ::= [APPLICATION 24] SEQUENCE {
47+
// COMPONENTS OF LDAPResult,
48+
// responseName [10] LDAPOID OPTIONAL,
49+
// responseValue [11] OCTET STRING OPTIONAL }
50+
51+
Name string
52+
Value *ber.Packet
53+
Controls []Control
54+
}
55+
56+
// Extended performs an extended request. The resulting
57+
// ExtendedResponse may return a value in the form of a *ber.Packet
58+
func (l *Conn) Extended(er *ExtendedRequest) (*ExtendedResponse, error) {
59+
msgCtx, err := l.doRequest(er)
60+
if err != nil {
61+
return nil, err
62+
}
63+
defer l.finishMessage(msgCtx)
64+
65+
packet, err := l.readPacket(msgCtx)
66+
if err != nil {
67+
return nil, err
68+
}
69+
if err = GetLDAPError(packet); err != nil {
70+
return nil, err
71+
}
72+
73+
if len(packet.Children[1].Children) < 4 {
74+
return nil, fmt.Errorf(
75+
"ldap: malformed extended response: expected 4 children, got %d",
76+
len(packet.Children),
77+
)
78+
}
79+
80+
response := &ExtendedResponse{
81+
Name: packet.Children[1].Children[3].Data.String(),
82+
Controls: make([]Control, 0),
83+
}
84+
85+
if len(packet.Children) == 3 {
86+
for _, child := range packet.Children[2].Children {
87+
decodedChild, decodeErr := DecodeControl(child)
88+
if decodeErr != nil {
89+
return nil, fmt.Errorf("failed to decode child control: %s", decodeErr)
90+
}
91+
response.Controls = append(response.Controls, decodedChild)
92+
}
93+
}
94+
95+
if len(packet.Children[1].Children) == 5 {
96+
response.Value = packet.Children[1].Children[4]
97+
}
98+
99+
return response, nil
100+
}

v3/extended_test.go

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package ldap
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestExtendedRequest_WhoAmI(t *testing.T) {
8+
l, err := DialURL(ldapServer)
9+
if err != nil {
10+
t.Errorf("%s failed: %v", t.Name(), err)
11+
return
12+
}
13+
defer l.Close()
14+
15+
l.Bind("", "") // anonymous
16+
defer l.Unbind()
17+
18+
rfc4532req := NewExtendedRequest("1.3.6.1.4.1.4203.1.11.3", nil) // request value is <nil>
19+
20+
var rfc4532resp *ExtendedResponse
21+
if rfc4532resp, err = l.Extended(rfc4532req); err != nil {
22+
t.Errorf("%s failed: %v", t.Name(), err)
23+
return
24+
}
25+
t.Logf("%#v\n", rfc4532resp)
26+
}
27+
28+
func TestExtendedRequest_FastBind(t *testing.T) {
29+
conn, err := DialURL(ldapServer)
30+
if err != nil {
31+
t.Error(err)
32+
}
33+
defer conn.Close()
34+
35+
request := NewExtendedRequest("1.3.6.1.4.1.4203.1.11.3", nil)
36+
_, err = conn.Extended(request)
37+
if err != nil {
38+
t.Errorf("%s failed: %v", t.Name(), err)
39+
return
40+
}
41+
}

0 commit comments

Comments
 (0)