Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add Extended request operations #516

Merged
merged 9 commits into from
Oct 31, 2024
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

Check failure on line 15 in extended_test.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `l.Bind` is not checked (errcheck)
defer l.Unbind()

Check failure on line 16 in extended_test.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `l.Unbind` is not checked (errcheck)

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
}
}
Loading