Skip to content

Commit

Permalink
feat: add Extended request operations (#516)
Browse files Browse the repository at this point in the history
  • Loading branch information
cpuschma authored Oct 31, 2024
1 parent 25c2d48 commit 601814b
Show file tree
Hide file tree
Showing 6 changed files with 284 additions and 0 deletions.
1 change: 1 addition & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type Client interface {
Modify(*ModifyRequest) error
ModifyDN(*ModifyDNRequest) error
ModifyWithResult(*ModifyRequest) (*ModifyResult, error)
Extended(*ExtendedRequest) (*ExtendedResponse, error)

Compare(dn, attribute, value string) (bool, error)
PasswordModify(*PasswordModifyRequest) (*PasswordModifyResult, error)
Expand Down
100 changes: 100 additions & 0 deletions extended.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package ldap

import (
"fmt"
ber "github.com/go-asn1-ber/asn1-ber"
)

// ExtendedRequest represents an extended request to send to the server
// See: https://www.rfc-editor.org/rfc/rfc4511#section-4.12
type ExtendedRequest struct {
// ExtendedRequest ::= [APPLICATION 23] SEQUENCE {
// requestName [0] LDAPOID,
// requestValue [1] OCTET STRING OPTIONAL }

Name string
Value *ber.Packet
Controls []Control
}

// NewExtendedRequest returns a new ExtendedRequest. The value can be
// nil depending on the type of request
func NewExtendedRequest(name string, value *ber.Packet) *ExtendedRequest {
return &ExtendedRequest{
Name: name,
Value: value,
}
}

func (er ExtendedRequest) appendTo(envelope *ber.Packet) error {
pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationExtendedRequest, nil, "Extended Request")
pkt.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, ber.TagEOC, er.Name, "Extended Request Name"))
if er.Value != nil {
pkt.AppendChild(er.Value)
}
envelope.AppendChild(pkt)
if len(er.Controls) > 0 {
envelope.AppendChild(encodeControls(er.Controls))
}
return nil
}

// ExtendedResponse represents the response from the directory server
// after sending an extended request
// See: https://www.rfc-editor.org/rfc/rfc4511#section-4.12
type ExtendedResponse struct {
// ExtendedResponse ::= [APPLICATION 24] SEQUENCE {
// COMPONENTS OF LDAPResult,
// responseName [10] LDAPOID OPTIONAL,
// responseValue [11] OCTET STRING OPTIONAL }

Name string
Value *ber.Packet
Controls []Control
}

// Extended performs an extended request. The resulting
// ExtendedResponse may return a value in the form of a *ber.Packet
func (l *Conn) Extended(er *ExtendedRequest) (*ExtendedResponse, error) {
msgCtx, err := l.doRequest(er)
if err != nil {
return nil, err
}
defer l.finishMessage(msgCtx)

packet, err := l.readPacket(msgCtx)
if err != nil {
return nil, err
}
if err = GetLDAPError(packet); err != nil {
return nil, err
}

if len(packet.Children[1].Children) < 4 {
return nil, fmt.Errorf(
"ldap: malformed extended response: expected 4 children, got %d",
len(packet.Children),
)
}

response := &ExtendedResponse{
Name: packet.Children[1].Children[3].Data.String(),
Controls: make([]Control, 0),
}

if len(packet.Children) == 3 {
for _, child := range packet.Children[2].Children {
decodedChild, decodeErr := DecodeControl(child)
if decodeErr != nil {
return nil, fmt.Errorf("failed to decode child control: %s", decodeErr)
}
response.Controls = append(response.Controls, decodedChild)
}
}

if len(packet.Children[1].Children) == 5 {
response.Value = packet.Children[1].Children[4]
}

return response, nil
}
41 changes: 41 additions & 0 deletions extended_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package ldap

import (
"testing"
)

func TestExtendedRequest_WhoAmI(t *testing.T) {
l, err := DialURL(ldapServer)
if err != nil {
t.Errorf("%s failed: %v", t.Name(), err)
return
}
defer l.Close()

l.Bind("", "") // anonymous
defer l.Unbind()

rfc4532req := NewExtendedRequest("1.3.6.1.4.1.4203.1.11.3", nil) // request value is <nil>

var rfc4532resp *ExtendedResponse
if rfc4532resp, err = l.Extended(rfc4532req); err != nil {
t.Errorf("%s failed: %v", t.Name(), err)
return
}
t.Logf("%#v\n", rfc4532resp)
}

func TestExtendedRequest_FastBind(t *testing.T) {
conn, err := DialURL(ldapServer)
if err != nil {
t.Error(err)
}
defer conn.Close()

request := NewExtendedRequest("1.3.6.1.4.1.4203.1.11.3", nil)
_, err = conn.Extended(request)
if err != nil {
t.Errorf("%s failed: %v", t.Name(), err)
return
}
}
1 change: 1 addition & 0 deletions v3/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type Client interface {
Modify(*ModifyRequest) error
ModifyDN(*ModifyDNRequest) error
ModifyWithResult(*ModifyRequest) (*ModifyResult, error)
Extended(*ExtendedRequest) (*ExtendedResponse, error)

Compare(dn, attribute, value string) (bool, error)
PasswordModify(*PasswordModifyRequest) (*PasswordModifyResult, error)
Expand Down
100 changes: 100 additions & 0 deletions v3/extended.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package ldap

import (
"fmt"
ber "github.com/go-asn1-ber/asn1-ber"
)

// ExtendedRequest represents an extended request to send to the server
// See: https://www.rfc-editor.org/rfc/rfc4511#section-4.12
type ExtendedRequest struct {
// ExtendedRequest ::= [APPLICATION 23] SEQUENCE {
// requestName [0] LDAPOID,
// requestValue [1] OCTET STRING OPTIONAL }

Name string
Value *ber.Packet
Controls []Control
}

// NewExtendedRequest returns a new ExtendedRequest. The value can be
// nil depending on the type of request
func NewExtendedRequest(name string, value *ber.Packet) *ExtendedRequest {
return &ExtendedRequest{
Name: name,
Value: value,
}
}

func (er ExtendedRequest) appendTo(envelope *ber.Packet) error {
pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationExtendedRequest, nil, "Extended Request")
pkt.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, ber.TagEOC, er.Name, "Extended Request Name"))
if er.Value != nil {
pkt.AppendChild(er.Value)
}
envelope.AppendChild(pkt)
if len(er.Controls) > 0 {
envelope.AppendChild(encodeControls(er.Controls))
}
return nil
}

// ExtendedResponse represents the response from the directory server
// after sending an extended request
// See: https://www.rfc-editor.org/rfc/rfc4511#section-4.12
type ExtendedResponse struct {
// ExtendedResponse ::= [APPLICATION 24] SEQUENCE {
// COMPONENTS OF LDAPResult,
// responseName [10] LDAPOID OPTIONAL,
// responseValue [11] OCTET STRING OPTIONAL }

Name string
Value *ber.Packet
Controls []Control
}

// Extended performs an extended request. The resulting
// ExtendedResponse may return a value in the form of a *ber.Packet
func (l *Conn) Extended(er *ExtendedRequest) (*ExtendedResponse, error) {
msgCtx, err := l.doRequest(er)
if err != nil {
return nil, err
}
defer l.finishMessage(msgCtx)

packet, err := l.readPacket(msgCtx)
if err != nil {
return nil, err
}
if err = GetLDAPError(packet); err != nil {
return nil, err
}

if len(packet.Children[1].Children) < 4 {
return nil, fmt.Errorf(
"ldap: malformed extended response: expected 4 children, got %d",
len(packet.Children),
)
}

response := &ExtendedResponse{
Name: packet.Children[1].Children[3].Data.String(),
Controls: make([]Control, 0),
}

if len(packet.Children) == 3 {
for _, child := range packet.Children[2].Children {
decodedChild, decodeErr := DecodeControl(child)
if decodeErr != nil {
return nil, fmt.Errorf("failed to decode child control: %s", decodeErr)
}
response.Controls = append(response.Controls, decodedChild)
}
}

if len(packet.Children[1].Children) == 5 {
response.Value = packet.Children[1].Children[4]
}

return response, nil
}
41 changes: 41 additions & 0 deletions v3/extended_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package ldap

import (
"testing"
)

func TestExtendedRequest_WhoAmI(t *testing.T) {
l, err := DialURL(ldapServer)
if err != nil {
t.Errorf("%s failed: %v", t.Name(), err)
return
}
defer l.Close()

l.Bind("", "") // anonymous
defer l.Unbind()

rfc4532req := NewExtendedRequest("1.3.6.1.4.1.4203.1.11.3", nil) // request value is <nil>

var rfc4532resp *ExtendedResponse
if rfc4532resp, err = l.Extended(rfc4532req); err != nil {
t.Errorf("%s failed: %v", t.Name(), err)
return
}
t.Logf("%#v\n", rfc4532resp)
}

func TestExtendedRequest_FastBind(t *testing.T) {
conn, err := DialURL(ldapServer)
if err != nil {
t.Error(err)
}
defer conn.Close()

request := NewExtendedRequest("1.3.6.1.4.1.4203.1.11.3", nil)
_, err = conn.Extended(request)
if err != nil {
t.Errorf("%s failed: %v", t.Name(), err)
return
}
}

0 comments on commit 601814b

Please sign in to comment.