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

Refactor DirSync search process #458

Merged
merged 3 commits into from
Sep 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,6 @@ type Client interface {
SearchAsync(ctx context.Context, searchRequest *SearchRequest, bufferSize int) Response
SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) (*SearchResult, error)
DirSync(searchRequest *SearchRequest, flags, maxAttrCount int64, cookie []byte) (*SearchResult, error)
DirSyncAsync(ctx context.Context, searchRequest *SearchRequest, bufferSize int, flags, maxAttrCount int64, cookie []byte) Response
Syncrepl(ctx context.Context, searchRequest *SearchRequest, bufferSize int, mode ControlSyncRequestMode, cookie []byte, reloadHint bool) Response
}
104 changes: 63 additions & 41 deletions control.go
Original file line number Diff line number Diff line change
Expand Up @@ -523,29 +523,7 @@ func DecodeControl(packet *ber.Packet) (Control, error) {
return NewControlSubtreeDelete(), nil
case ControlTypeDirSync:
value.Description += " (DirSync)"
c := new(ControlDirSync)
if value.Value != nil {
valueChildren, err := ber.DecodePacketErr(value.Data.Bytes())
if err != nil {
return nil, err
}
value.Data.Truncate(0)
value.Value = nil
value.AppendChild(valueChildren)
}
value = value.Children[0]
if len(value.Children) != 3 { // also on initial creation, Cookie is an empty string
return nil, fmt.Errorf("invalid number of children in dirSync control")
}
value.Description = "DirSync Control Value"
value.Children[0].Description = "Flags"
value.Children[1].Description = "MaxAttrCnt"
value.Children[2].Description = "Cookie"
c.Flags = value.Children[0].Value.(int64)
c.MaxAttrCnt = value.Children[1].Value.(int64)
c.Cookie = value.Children[2].Data.Bytes()
value.Children[2].Value = c.Cookie
return c, nil
return NewResponseControlDirSync(value)
case ControlTypeSyncState:
value.Description += " (Sync State)"
valueChildren, err := ber.DecodePacketErr(value.Data.Bytes())
Expand Down Expand Up @@ -640,18 +618,57 @@ func encodeControls(controls []Control) *ber.Packet {

// ControlDirSync implements the control described in https://msdn.microsoft.com/en-us/library/aa366978(v=vs.85).aspx
type ControlDirSync struct {
Flags int64
MaxAttrCnt int64
Cookie []byte
Criticality bool
Flags int64
MaxAttrCount int64
Cookie []byte
}

// NewControlDirSync returns a dir sync control
// @deprecated Use NewRequestControlDirSync instead
func NewControlDirSync(flags int64, maxAttrCount int64, cookie []byte) *ControlDirSync {
return NewRequestControlDirSync(flags, maxAttrCount, cookie)
}

// NewRequestControlDirSync returns a dir sync control
func NewRequestControlDirSync(
flags int64, maxAttrCount int64, cookie []byte,
) *ControlDirSync {
return &ControlDirSync{
Flags: flags,
MaxAttrCnt: maxAttrCount,
Cookie: cookie,
Criticality: true,
Flags: flags,
MaxAttrCount: maxAttrCount,
Cookie: cookie,
}
}

// NewResponseControlDirSync returns a dir sync control
func NewResponseControlDirSync(value *ber.Packet) (*ControlDirSync, error) {
if value.Value != nil {
valueChildren, err := ber.DecodePacketErr(value.Data.Bytes())
if err != nil {
return nil, fmt.Errorf("failed to decode data bytes: %s", err)
}
value.Data.Truncate(0)
value.Value = nil
value.AppendChild(valueChildren)
}
child := value.Children[0]
if len(child.Children) != 3 { // also on initial creation, Cookie is an empty string
return nil, fmt.Errorf("invalid number of children in dirSync control")
}
child.Description = "DirSync Control Value"
child.Children[0].Description = "Flags"
child.Children[1].Description = "MaxAttrCount"
child.Children[2].Description = "Cookie"

cookie := child.Children[2].Data.Bytes()
child.Children[2].Value = cookie
return &ControlDirSync{
Criticality: true,
Flags: child.Children[0].Value.(int64),
MaxAttrCount: child.Children[1].Value.(int64),
Cookie: cookie,
}, nil
}

// GetControlType returns the OID
Expand All @@ -661,28 +678,33 @@ func (c *ControlDirSync) GetControlType() string {

// String returns a human-readable description
func (c *ControlDirSync) String() string {
return fmt.Sprintf("ControlType: %s (%q), Criticality: true, ControlValue: Flags: %d, MaxAttrCnt: %d", ControlTypeMap[ControlTypeDirSync], ControlTypeDirSync, c.Flags, c.MaxAttrCnt)
return fmt.Sprintf(
"ControlType: %s (%q) Criticality: %t ControlValue: Flags: %d MaxAttrCount: %d",
ControlTypeMap[ControlTypeDirSync],
ControlTypeDirSync,
c.Criticality,
c.Flags,
c.MaxAttrCount,
)
}

// Encode returns the ber packet representation
func (c *ControlDirSync) Encode() *ber.Packet {
cookie := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "Cookie")
if len(c.Cookie) != 0 {
cookie.Value = c.Cookie
cookie.Data.Write(c.Cookie)
}

packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeDirSync, "Control Type ("+ControlTypeMap[ControlTypeDirSync]+")"))
packet.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, true, "Criticality")) // must be true always
packet.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, c.Criticality, "Criticality")) // must be true always

val := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Control Value (DirSync)")

seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "DirSync Control Value")
seq.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(c.Flags), "Flags"))
seq.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(c.MaxAttrCnt), "MaxAttrCount"))

cookie := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "Cookie")
if len(c.Cookie) != 0 {
cookie.Value = c.Cookie
cookie.Data.Write(c.Cookie)
}
seq.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(c.MaxAttrCount), "MaxAttrCount"))
seq.AppendChild(cookie)

val.AppendChild(seq)

packet.AppendChild(val)
Expand Down
6 changes: 3 additions & 3 deletions control_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ func TestControlString(t *testing.T) {
}

func TestControlDirSync(t *testing.T) {
runControlTest(t, NewControlDirSync(DirSyncObjectSecurity, 1000, nil))
runControlTest(t, NewControlDirSync(DirSyncObjectSecurity, 1000, []byte("I'm a cookie!")))
runControlTest(t, NewRequestControlDirSync(DirSyncObjectSecurity, 1000, nil))
runControlTest(t, NewRequestControlDirSync(DirSyncObjectSecurity, 1000, []byte("I'm a cookie!")))
}

func runControlTest(t *testing.T, originalControl Control) {
Expand Down Expand Up @@ -122,7 +122,7 @@ func TestDescribeControlString(t *testing.T) {
}

func TestDescribeControlDirSync(t *testing.T) {
runAddControlDescriptions(t, NewControlDirSync(DirSyncObjectSecurity, 1000, nil), "Control Type (DirSync)", "Criticality", "Control Value")
runAddControlDescriptions(t, NewRequestControlDirSync(DirSyncObjectSecurity, 1000, nil), "Control Type (DirSync)", "Criticality", "Control Value")
}

func runAddControlDescriptions(t *testing.T, originalControl Control, childDescriptions ...string) {
Expand Down
43 changes: 43 additions & 0 deletions examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,49 @@ func ExampleConn_DirSync() {
}
}

// This example demonstrates how to use DirSync search asynchronously
func ExampleConn_DirSyncAsync() {
cpuschma marked this conversation as resolved.
Show resolved Hide resolved
conn, err := Dial("tcp", "ad.example.org:389")
if err != nil {
log.Fatalf("Failed to connect: %s\n", err)
}
defer conn.Close()

_, err = conn.SimpleBind(&SimpleBindRequest{
Username: "cn=Some User,ou=people,dc=example,dc=org",
Password: "MySecretPass",
})
if err != nil {
log.Fatalf("failed to bind: %s", err)
}

req := &SearchRequest{
BaseDN: `DC=example,DC=org`,
Filter: `(&(objectClass=person)(!(objectClass=computer)))`,
Attributes: []string{"*"},
Scope: ScopeWholeSubtree,
}

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

var cookie []byte = nil
r := conn.DirSyncAsync(ctx, req, 64, DirSyncObjectSecurity, 1000, cookie)
for r.Next() {
entry := r.Entry()
if entry != nil {
entry.Print()
}
controls := r.Controls()
if len(controls) != 0 {
fmt.Printf("%s", controls)
}
}
if err := r.Err(); err != nil {
log.Fatal(err)
}
}

// This example demonstrates how to use EXTERNAL SASL with TLS client certificates.
func ExampleConn_ExternalBind() {
ldapCert := "/path/to/cert.pem"
Expand Down
59 changes: 30 additions & 29 deletions search.go
Original file line number Diff line number Diff line change
Expand Up @@ -633,55 +633,56 @@ func unpackAttributes(children []*ber.Packet) []*EntryAttribute {
}

// DirSync does a Search with dirSync Control.
func (l *Conn) DirSync(searchRequest *SearchRequest, flags int64, maxAttrCount int64, cookie []byte) (*SearchResult, error) {
var dirSyncControl *ControlDirSync
cpuschma marked this conversation as resolved.
Show resolved Hide resolved

func (l *Conn) DirSync(
searchRequest *SearchRequest, flags int64, maxAttrCount int64, cookie []byte,
) (*SearchResult, error) {
control := FindControl(searchRequest.Controls, ControlTypeDirSync)
if control == nil {
dirSyncControl = NewControlDirSync(flags, maxAttrCount, cookie)
searchRequest.Controls = append(searchRequest.Controls, dirSyncControl)
c := NewRequestControlDirSync(flags, maxAttrCount, cookie)
searchRequest.Controls = append(searchRequest.Controls, c)
} else {
castControl, ok := control.(*ControlDirSync)
if !ok {
return nil, fmt.Errorf("Expected DirSync control to be of type *ControlDirSync, got %v", control)
c := control.(*ControlDirSync)
if c.Flags != flags {
return nil, fmt.Errorf("flags given in search request (%d) conflicts with flags given in search call (%d)", c.Flags, flags)
}
if castControl.Flags != flags {
return nil, fmt.Errorf("flags given in search request (%d) conflicts with flags given in search call (%d)", castControl.Flags, flags)
if c.MaxAttrCount != maxAttrCount {
return nil, fmt.Errorf("MaxAttrCnt given in search request (%d) conflicts with maxAttrCount given in search call (%d)", c.MaxAttrCount, maxAttrCount)
}
if castControl.MaxAttrCnt != maxAttrCount {
return nil, fmt.Errorf("MaxAttrCnt given in search request (%d) conflicts with maxAttrCount given in search call (%d)", castControl.MaxAttrCnt, maxAttrCount)
}
dirSyncControl = castControl
}
searchResult := new(SearchResult)
result, err := l.Search(searchRequest)
searchResult, err := l.Search(searchRequest)
l.Debug.Printf("Looking for result...")
if err != nil {
return searchResult, err
return nil, err
}
if result == nil {
return searchResult, NewError(ErrorNetwork, errors.New("ldap: packet not received"))
if searchResult == nil {
return nil, NewError(ErrorNetwork, errors.New("ldap: packet not received"))
}

searchResult.Entries = append(searchResult.Entries, result.Entries...)
searchResult.Referrals = append(searchResult.Referrals, result.Referrals...)
searchResult.Controls = append(searchResult.Controls, result.Controls...)

l.Debug.Printf("Looking for DirSync Control...")
dirSyncResult := FindControl(result.Controls, ControlTypeDirSync)
if dirSyncResult == nil {
dirSyncControl = nil
resultControl := FindControl(searchResult.Controls, ControlTypeDirSync)
if resultControl == nil {
l.Debug.Printf("Could not find dirSyncControl control. Breaking...")
return searchResult, nil
}

cookie = dirSyncResult.(*ControlDirSync).Cookie
cookie = resultControl.(*ControlDirSync).Cookie
if len(cookie) == 0 {
dirSyncControl = nil
l.Debug.Printf("Could not find cookie. Breaking...")
return searchResult, nil
}
dirSyncControl.SetCookie(cookie)

return searchResult, nil
}

// DirSyncDirSyncAsync performs a search request and returns all search results
// asynchronously. This is efficient when the server returns lots of entries.
func (l *Conn) DirSyncAsync(
ctx context.Context, searchRequest *SearchRequest, bufferSize int,
flags, maxAttrCount int64, cookie []byte,
) Response {
control := NewRequestControlDirSync(flags, maxAttrCount, cookie)
searchRequest.Controls = append(searchRequest.Controls, control)
r := newSearchResponse(l, bufferSize)
r.start(ctx, searchRequest)
return r
}
1 change: 1 addition & 0 deletions v3/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,6 @@ type Client interface {
SearchAsync(ctx context.Context, searchRequest *SearchRequest, bufferSize int) Response
SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) (*SearchResult, error)
DirSync(searchRequest *SearchRequest, flags, maxAttrCount int64, cookie []byte) (*SearchResult, error)
DirSyncAsync(ctx context.Context, searchRequest *SearchRequest, bufferSize int, flags, maxAttrCount int64, cookie []byte) Response
Syncrepl(ctx context.Context, searchRequest *SearchRequest, bufferSize int, mode ControlSyncRequestMode, cookie []byte, reloadHint bool) Response
}
Loading
Loading