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

Added search with channels first draft #319

Closed
wants to merge 2 commits into from
Closed
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 v3/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ const (
ErrorUnexpectedMessage = 204
ErrorUnexpectedResponse = 205
ErrorEmptyPassword = 206
ErrorUsage = 207
)

// LDAPResultCodeMap contains string descriptions for LDAP error codes
Expand Down
39 changes: 39 additions & 0 deletions v3/examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io/ioutil"
"log"
"sync"
)

// This example demonstrates how to bind a connection to an ldap user
Expand Down Expand Up @@ -49,6 +50,44 @@ func ExampleConn_Search() {
}
}

func ExampleConn_SearchWithChannel() {
l, err := DialURL(fmt.Sprintf("%s:%d", "ldap.example.com", 389))
if err != nil {
log.Fatal(err)
}
defer l.Close()

searchRequest := NewSearchRequest(
"dc=example,dc=com", // The base dn to search
ScopeWholeSubtree, NeverDerefAliases, 0, 0, false,
"(&(objectClass=organizationalPerson))", // The filter to apply
[]string{"dn", "cn"}, // A list attributes to retrieve
nil,
)

// this is basically how Search() does it:
ch := make(chan *SearchResult)
wg := sync.WaitGroup{}
wg.Add(1)

go func() {
for res := range ch {
if len(res.Entries) != 0 {
fmt.Printf("%s has DN %s\n", res.Entries[0].GetAttributeValue("cn"), res.Entries[0].DN)
}
}
wg.Done()
}()

err = l.SearchWithChannel(searchRequest, ch)

wg.Wait()

if err != nil {
log.Fatalf("Error while searching: %s", err)
}
}

// This example demonstrates how to start a TLS connection
func ExampleConn_StartTLS() {
l, err := DialURL("ldap://ldap.example.com:389")
Expand Down
2 changes: 2 additions & 0 deletions v3/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@ go 1.13
require (
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c
github.com/go-asn1-ber/asn1-ber v1.5.1
github.com/go-ldap/ldap v3.0.3+incompatible
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 // indirect
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
)
4 changes: 4 additions & 0 deletions v3/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzU
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8=
github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-ldap/ldap v3.0.3+incompatible h1:HTeSZO8hWMS1Rgb2Ziku6b8a7qRIZZMHjsvuZyatzwk=
github.com/go-ldap/ldap v3.0.3+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM=
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
89 changes: 89 additions & 0 deletions v3/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -408,3 +408,92 @@ func (l *Conn) Search(searchRequest *SearchRequest) (*SearchResult, error) {
}
}
}

// SearchWithChannel performs a search request and returns all search results via the given
// channel as soon as they are received. This means you get all results until an error
// happens (or the search successfully finished), e.g. for size / time limited requests all
// are recieved via the channel until the limit is reached.
func (l *Conn) SearchWithChannel(searchRequest *SearchRequest, ch chan *SearchResult) error {
if ch == nil {
return NewError(ErrorUsage, errors.New("ldap: SearchWithChannel got nil channel"))
}
defer close(ch)

packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID"))
// encode search request
err := searchRequest.appendTo(packet)
if err != nil {
return err
}

l.Debug.PrintPacket(packet)

msgCtx, err := l.sendMessage(packet)
if err != nil {
return err
}
defer l.finishMessage(msgCtx)

foundSearchResultDone := false
for !foundSearchResultDone {
l.Debug.Printf("%d: waiting for response", msgCtx.id)
packetResponse, ok := <-msgCtx.responses
if !ok {
return NewError(ErrorNetwork, errors.New("ldap: response channel closed"))
}
packet, err = packetResponse.ReadPacket()
l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
if err != nil {
return err
}

if l.Debug {
if err := addLDAPDescriptions(packet); err != nil {
return err
}
ber.PrintPacket(packet)
}

switch packet.Children[1].Tag {
case ApplicationSearchResultEntry:
entry := new(Entry)
entry.DN = packet.Children[1].Children[0].Value.(string)
for _, child := range packet.Children[1].Children[1].Children {
attr := new(EntryAttribute)
attr.Name = child.Children[0].Value.(string)
for _, value := range child.Children[1].Children {
attr.Values = append(attr.Values, value.Value.(string))
attr.ByteValues = append(attr.ByteValues, value.ByteValue)
}
entry.Attributes = append(entry.Attributes, attr)
}
ch <- &SearchResult{Entries: []*Entry{entry}}

case ApplicationSearchResultDone:
if err := GetLDAPError(packet); err != nil {
return err
}
if len(packet.Children) == 3 {
result := &SearchResult{}
for _, child := range packet.Children[2].Children {
decodedChild, err := DecodeControl(child)
if err != nil {
return fmt.Errorf("failed to decode child control: %s", err)
}
result.Controls = append(result.Controls, decodedChild)
}
ch <- result
}
foundSearchResultDone = true

case ApplicationSearchResultReference:
ref := packet.Children[1].Children[0].Value.(string)
ch <- &SearchResult{Referrals: []string{ref}}
}
}

l.Debug.Printf("%d: returning", msgCtx.id)
return nil

}