From 2bf94d8db4d269e0f5af423ffb8c972162c8bbe0 Mon Sep 17 00:00:00 2001 From: David Warden Date: Thu, 17 Mar 2016 10:45:39 -0400 Subject: [PATCH] version bump go-ldap to v2.2.1 --- Godeps/Godeps.json | 4 +- .../src/github.com/go-ldap/ldap/.travis.yml | 5 +- .../src/github.com/go-ldap/ldap/README.md | 41 +-- .../src/github.com/go-ldap/ldap/add.go | 104 +++++++ .../src/github.com/go-ldap/ldap/client.go | 23 ++ .../src/github.com/go-ldap/ldap/conn.go | 9 +- .../src/github.com/go-ldap/ldap/control.go | 32 +++ .../src/github.com/go-ldap/ldap/del.go | 79 +++++ .../src/github.com/go-ldap/ldap/dn.go | 12 +- .../src/github.com/go-ldap/ldap/dn_test.go | 66 ++--- .../src/github.com/go-ldap/ldap/error.go | 137 +++++++++ .../github.com/go-ldap/ldap/example_test.go | 4 +- .../src/github.com/go-ldap/ldap/filter.go | 272 +++++++++++++++--- .../github.com/go-ldap/ldap/filter_test.go | 222 ++++++++++++-- .../src/github.com/go-ldap/ldap/ldap.go | 121 +------- .../src/github.com/go-ldap/ldap/ldap_test.go | 78 +++-- .../src/github.com/go-ldap/ldap/search.go | 61 +++- .../github.com/go-ldap/ldap/search_test.go | 31 ++ 18 files changed, 1028 insertions(+), 273 deletions(-) create mode 100644 Godeps/_workspace/src/github.com/go-ldap/ldap/add.go create mode 100644 Godeps/_workspace/src/github.com/go-ldap/ldap/client.go create mode 100644 Godeps/_workspace/src/github.com/go-ldap/ldap/del.go create mode 100644 Godeps/_workspace/src/github.com/go-ldap/ldap/error.go create mode 100644 Godeps/_workspace/src/github.com/go-ldap/ldap/search_test.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index b5cb1a460df..c054540ecf4 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -159,8 +159,8 @@ }, { "ImportPath": "github.com/go-ldap/ldap", - "Comment": "v1-19-g83e6542", - "Rev": "83e65426fd1c06626e88aa8a085e5bfed0208e29" + "Comment": "v2.2.1", + "Rev": "07a7330929b9ee80495c88a4439657d89c7dbd87" }, { "ImportPath": "github.com/go-macaron/binding", diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/.travis.yml b/Godeps/_workspace/src/github.com/go-ldap/ldap/.travis.yml index f90ee667c09..3a5402596d0 100644 --- a/Godeps/_workspace/src/github.com/go-ldap/ldap/.travis.yml +++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/.travis.yml @@ -2,10 +2,13 @@ language: go go: - 1.2 - 1.3 + - 1.4 + - 1.5 - tip +go_import_path: gopkg.in/ldap.v2 install: - go get gopkg.in/asn1-ber.v1 - - go get gopkg.in/ldap.v1 + - go get gopkg.in/ldap.v2 - go get code.google.com/p/go.tools/cmd/cover || go get golang.org/x/tools/cmd/cover - go build -v ./... script: diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/README.md b/Godeps/_workspace/src/github.com/go-ldap/ldap/README.md index c940520461e..f49b4d6a1b3 100644 --- a/Godeps/_workspace/src/github.com/go-ldap/ldap/README.md +++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/README.md @@ -1,8 +1,20 @@ -[![GoDoc](https://godoc.org/gopkg.in/ldap.v1?status.svg)](https://godoc.org/gopkg.in/ldap.v1) [![Build Status](https://travis-ci.org/go-ldap/ldap.svg)](https://travis-ci.org/go-ldap/ldap) +[![GoDoc](https://godoc.org/gopkg.in/ldap.v2?status.svg)](https://godoc.org/gopkg.in/ldap.v2) +[![Build Status](https://travis-ci.org/go-ldap/ldap.svg)](https://travis-ci.org/go-ldap/ldap) # Basic LDAP v3 functionality for the GO programming language. -## Required Librarys: +## Install + +For the latest version use: + + go get gopkg.in/ldap.v2 + +Import the latest version with: + + import "gopkg.in/ldap.v2" + + +## Required Libraries: - gopkg.in/asn1-ber.v1 @@ -14,6 +26,9 @@ - Compiling string filters to LDAP filters - Paging Search Results - Modify Requests / Responses + - Add Requests / Responses + - Delete Requests / Responses + - Better Unicode support ## Examples: @@ -26,23 +41,15 @@ ## TODO: - - Add Requests / Responses - - Delete Requests / Responses - - Modify DN Requests / Responses - - Compare Requests / Responses - - Implement Tests / Benchmarks + - [x] Add Requests / Responses + - [x] Delete Requests / Responses + - [x] Modify DN Requests / Responses + - [ ] Compare Requests / Responses + - [ ] Implement Tests / Benchmarks + + --- -This feature is disabled at the moment, because in some cases the "Search Request Done" packet will be handled before the last "Search Request Entry": - - - Mulitple internal goroutines to handle network traffic - Makes library goroutine safe - Can perform multiple search requests at the same time and return - the results to the proper goroutine. All requests are blocking requests, - so the goroutine does not need special handling - ---- - The Go gopher was designed by Renee French. (http://reneefrench.blogspot.com/) The design is licensed under the Creative Commons 3.0 Attributions license. Read this article for more details: http://blog.golang.org/gopher diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/add.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/add.go new file mode 100644 index 00000000000..643ce5ffe4c --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/add.go @@ -0,0 +1,104 @@ +// +// https://tools.ietf.org/html/rfc4511 +// +// AddRequest ::= [APPLICATION 8] SEQUENCE { +// entry LDAPDN, +// attributes AttributeList } +// +// AttributeList ::= SEQUENCE OF attribute Attribute + +package ldap + +import ( + "errors" + "log" + + "gopkg.in/asn1-ber.v1" +) + +type Attribute struct { + attrType string + attrVals []string +} + +func (a *Attribute) encode() *ber.Packet { + seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Attribute") + seq.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, a.attrType, "Type")) + set := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSet, nil, "AttributeValue") + for _, value := range a.attrVals { + set.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, value, "Vals")) + } + seq.AppendChild(set) + return seq +} + +type AddRequest struct { + dn string + attributes []Attribute +} + +func (a AddRequest) encode() *ber.Packet { + request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationAddRequest, nil, "Add Request") + request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, a.dn, "DN")) + attributes := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Attributes") + for _, attribute := range a.attributes { + attributes.AppendChild(attribute.encode()) + } + request.AppendChild(attributes) + return request +} + +func (a *AddRequest) Attribute(attrType string, attrVals []string) { + a.attributes = append(a.attributes, Attribute{attrType: attrType, attrVals: attrVals}) +} + +func NewAddRequest(dn string) *AddRequest { + return &AddRequest{ + dn: dn, + } + +} + +func (l *Conn) Add(addRequest *AddRequest) error { + messageID := l.nextMessageID() + packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request") + packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, messageID, "MessageID")) + packet.AppendChild(addRequest.encode()) + + l.Debug.PrintPacket(packet) + + channel, err := l.sendMessage(packet) + if err != nil { + return err + } + if channel == nil { + return NewError(ErrorNetwork, errors.New("ldap: could not send message")) + } + defer l.finishMessage(messageID) + + l.Debug.Printf("%d: waiting for response", messageID) + packet = <-channel + l.Debug.Printf("%d: got response %p", messageID, packet) + if packet == nil { + return NewError(ErrorNetwork, errors.New("ldap: could not retrieve message")) + } + + if l.Debug { + if err := addLDAPDescriptions(packet); err != nil { + return err + } + ber.PrintPacket(packet) + } + + if packet.Children[1].Tag == ApplicationAddResponse { + resultCode, resultDescription := getLDAPResultCode(packet) + if resultCode != 0 { + return NewError(resultCode, errors.New(resultDescription)) + } + } else { + log.Printf("Unexpected Response: %d", packet.Children[1].Tag) + } + + l.Debug.Printf("%d: returning", messageID) + return nil +} diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/client.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/client.go new file mode 100644 index 00000000000..d3401f9e61e --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/client.go @@ -0,0 +1,23 @@ +package ldap + +import "crypto/tls" + +// Client knows how to interact with an LDAP server +type Client interface { + Start() + StartTLS(config *tls.Config) error + Close() + + Bind(username, password string) error + SimpleBind(simpleBindRequest *SimpleBindRequest) (*SimpleBindResult, error) + + Add(addRequest *AddRequest) error + Del(delRequest *DelRequest) error + Modify(modifyRequest *ModifyRequest) error + + Compare(dn, attribute, value string) (bool, error) + PasswordModify(passwordModifyRequest *PasswordModifyRequest) (*PasswordModifyResult, error) + + Search(searchRequest *SearchRequest) (*SearchResult, error) + SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) (*SearchResult, error) +} diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/conn.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/conn.go index c51e1afe87d..2f16443f6e4 100644 --- a/Godeps/_workspace/src/github.com/go-ldap/ldap/conn.go +++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/conn.go @@ -8,11 +8,12 @@ import ( "crypto/tls" "errors" "fmt" - "gopkg.in/asn1-ber.v1" "log" "net" "sync" "time" + + "gopkg.in/asn1-ber.v1" ) const ( @@ -53,6 +54,8 @@ type Conn struct { messageMutex sync.Mutex } +var _ Client = &Conn{} + // DefaultTimeout is a package-level variable that sets the timeout value // used for the Dial and DialTLS methods. // @@ -176,7 +179,7 @@ func (l *Conn) StartTLS(config *tls.Config) error { ber.PrintPacket(packet) } - if packet.Children[1].Children[0].Value.(int64) == 0 { + if resultCode, message := getLDAPResultCode(packet); resultCode == LDAPResultSuccess { conn := tls.Client(l.conn, config) if err := conn.Handshake(); err != nil { @@ -186,6 +189,8 @@ func (l *Conn) StartTLS(config *tls.Config) error { l.isTLS = true l.conn = conn + } else { + return NewError(resultCode, fmt.Errorf("ldap: cannot StartTLS (%s)", message)) } go l.reader() diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/control.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/control.go index 562fbe43090..4d82980933e 100644 --- a/Godeps/_workspace/src/github.com/go-ldap/ldap/control.go +++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/control.go @@ -16,11 +16,13 @@ const ( ControlTypeBeheraPasswordPolicy = "1.3.6.1.4.1.42.2.27.8.5.1" ControlTypeVChuPasswordMustChange = "2.16.840.1.113730.3.4.4" ControlTypeVChuPasswordWarning = "2.16.840.1.113730.3.4.5" + ControlTypeManageDsaIT = "2.16.840.1.113730.3.4.2" ) var ControlTypeMap = map[string]string{ ControlTypePaging: "Paging", ControlTypeBeheraPasswordPolicy: "Password Policy - Behera Draft", + ControlTypeManageDsaIT: "Manage DSA IT", } type Control interface { @@ -165,6 +167,36 @@ func (c *ControlVChuPasswordWarning) String() string { c.Expire) } +type ControlManageDsaIT struct { + Criticality bool +} + +func (c *ControlManageDsaIT) GetControlType() string { + return ControlTypeManageDsaIT +} + +func (c *ControlManageDsaIT) Encode() *ber.Packet { + //FIXME + packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control") + packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeManageDsaIT, "Control Type ("+ControlTypeMap[ControlTypeManageDsaIT]+")")) + if c.Criticality { + packet.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, c.Criticality, "Criticality")) + } + return packet +} + +func (c *ControlManageDsaIT) String() string { + return fmt.Sprintf( + "Control Type: %s (%q) Criticality: %t", + ControlTypeMap[ControlTypeManageDsaIT], + ControlTypeManageDsaIT, + c.Criticality) +} + +func NewControlManageDsaIT(Criticality bool) *ControlManageDsaIT { + return &ControlManageDsaIT{Criticality: Criticality} +} + func FindControl(controls []Control, controlType string) Control { for _, c := range controls { if c.GetControlType() == controlType { diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/del.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/del.go new file mode 100644 index 00000000000..2f0eae1cd29 --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/del.go @@ -0,0 +1,79 @@ +// +// https://tools.ietf.org/html/rfc4511 +// +// DelRequest ::= [APPLICATION 10] LDAPDN + +package ldap + +import ( + "errors" + "log" + + "gopkg.in/asn1-ber.v1" +) + +type DelRequest struct { + DN string + Controls []Control +} + +func (d DelRequest) encode() *ber.Packet { + request := ber.Encode(ber.ClassApplication, ber.TypePrimitive, ApplicationDelRequest, d.DN, "Del Request") + request.Data.Write([]byte(d.DN)) + return request +} + +func NewDelRequest(DN string, + Controls []Control) *DelRequest { + return &DelRequest{ + DN: DN, + Controls: Controls, + } +} + +func (l *Conn) Del(delRequest *DelRequest) error { + messageID := l.nextMessageID() + packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request") + packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, messageID, "MessageID")) + packet.AppendChild(delRequest.encode()) + if delRequest.Controls != nil { + packet.AppendChild(encodeControls(delRequest.Controls)) + } + + l.Debug.PrintPacket(packet) + + channel, err := l.sendMessage(packet) + if err != nil { + return err + } + if channel == nil { + return NewError(ErrorNetwork, errors.New("ldap: could not send message")) + } + defer l.finishMessage(messageID) + + l.Debug.Printf("%d: waiting for response", messageID) + packet = <-channel + l.Debug.Printf("%d: got response %p", messageID, packet) + if packet == nil { + return NewError(ErrorNetwork, errors.New("ldap: could not retrieve message")) + } + + if l.Debug { + if err := addLDAPDescriptions(packet); err != nil { + return err + } + ber.PrintPacket(packet) + } + + if packet.Children[1].Tag == ApplicationDelResponse { + resultCode, resultDescription := getLDAPResultCode(packet) + if resultCode != 0 { + return NewError(resultCode, errors.New(resultDescription)) + } + } else { + log.Printf("Unexpected Response: %d", packet.Children[1].Tag) + } + + l.Debug.Printf("%d: returning", messageID) + return nil +} diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/dn.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/dn.go index 31d52db4b23..5d83c5e9ab9 100644 --- a/Godeps/_workspace/src/github.com/go-ldap/ldap/dn.go +++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/dn.go @@ -47,17 +47,17 @@ package ldap import ( "bytes" + enchex "encoding/hex" "errors" "fmt" "strings" - enchex "encoding/hex" ber "gopkg.in/asn1-ber.v1" ) type AttributeTypeAndValue struct { - Type string - Value string + Type string + Value string } type RelativeDN struct { @@ -71,7 +71,7 @@ type DN struct { func ParseDN(str string) (*DN, error) { dn := new(DN) dn.RDNs = make([]*RelativeDN, 0) - rdn := new (RelativeDN) + rdn := new(RelativeDN) rdn.Attributes = make([]*AttributeTypeAndValue, 0) buffer := bytes.Buffer{} attribute := new(AttributeTypeAndValue) @@ -115,7 +115,7 @@ func ParseDN(str string) (*DN, error) { index := strings.IndexAny(str[i:], ",+") data := str if index > 0 { - data = str[i:i+index] + data = str[i : i+index] } else { data = str[i:] } @@ -126,7 +126,7 @@ func ParseDN(str string) (*DN, error) { } packet := ber.DecodePacket(raw_ber) buffer.WriteString(packet.Data.String()) - i += len(data)-1 + i += len(data) - 1 } } else if char == ',' || char == '+' { // We're done with this RDN or value, push it diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/dn_test.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/dn_test.go index 6740e1819c7..39817c42741 100644 --- a/Godeps/_workspace/src/github.com/go-ldap/ldap/dn_test.go +++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/dn_test.go @@ -1,38 +1,40 @@ -package ldap +package ldap_test import ( "reflect" "testing" + + "gopkg.in/ldap.v2" ) func TestSuccessfulDNParsing(t *testing.T) { - testcases := map[string]DN { - "": DN{[]*RelativeDN{}}, - "cn=Jim\\2C \\22Hasse Hö\\22 Hansson!,dc=dummy,dc=com": DN{[]*RelativeDN{ - &RelativeDN{[]*AttributeTypeAndValue{&AttributeTypeAndValue{"cn", "Jim, \"Hasse Hö\" Hansson!"},}}, - &RelativeDN{[]*AttributeTypeAndValue{&AttributeTypeAndValue{"dc", "dummy"},}}, - &RelativeDN{[]*AttributeTypeAndValue{&AttributeTypeAndValue{"dc", "com"}, }},}}, - "UID=jsmith,DC=example,DC=net": DN{[]*RelativeDN{ - &RelativeDN{[]*AttributeTypeAndValue{&AttributeTypeAndValue{"UID", "jsmith"},}}, - &RelativeDN{[]*AttributeTypeAndValue{&AttributeTypeAndValue{"DC", "example"},}}, - &RelativeDN{[]*AttributeTypeAndValue{&AttributeTypeAndValue{"DC", "net"}, }},}}, - "OU=Sales+CN=J. Smith,DC=example,DC=net": DN{[]*RelativeDN{ - &RelativeDN{[]*AttributeTypeAndValue{ - &AttributeTypeAndValue{"OU", "Sales"}, - &AttributeTypeAndValue{"CN", "J. Smith"},}}, - &RelativeDN{[]*AttributeTypeAndValue{&AttributeTypeAndValue{"DC", "example"},}}, - &RelativeDN{[]*AttributeTypeAndValue{&AttributeTypeAndValue{"DC", "net"}, }},}}, - "1.3.6.1.4.1.1466.0=#04024869": DN{[]*RelativeDN{ - &RelativeDN{[]*AttributeTypeAndValue{&AttributeTypeAndValue{"1.3.6.1.4.1.1466.0", "Hi"},}},}}, - "1.3.6.1.4.1.1466.0=#04024869,DC=net": DN{[]*RelativeDN{ - &RelativeDN{[]*AttributeTypeAndValue{&AttributeTypeAndValue{"1.3.6.1.4.1.1466.0", "Hi"},}}, - &RelativeDN{[]*AttributeTypeAndValue{&AttributeTypeAndValue{"DC", "net"}, }},}}, - "CN=Lu\\C4\\8Di\\C4\\87": DN{[]*RelativeDN{ - &RelativeDN{[]*AttributeTypeAndValue{&AttributeTypeAndValue{"CN", "Lučić"},}},}}, + testcases := map[string]ldap.DN{ + "": ldap.DN{[]*ldap.RelativeDN{}}, + "cn=Jim\\2C \\22Hasse Hö\\22 Hansson!,dc=dummy,dc=com": ldap.DN{[]*ldap.RelativeDN{ + &ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"cn", "Jim, \"Hasse Hö\" Hansson!"}}}, + &ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"dc", "dummy"}}}, + &ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"dc", "com"}}}}}, + "UID=jsmith,DC=example,DC=net": ldap.DN{[]*ldap.RelativeDN{ + &ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"UID", "jsmith"}}}, + &ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"DC", "example"}}}, + &ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"DC", "net"}}}}}, + "OU=Sales+CN=J. Smith,DC=example,DC=net": ldap.DN{[]*ldap.RelativeDN{ + &ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{ + &ldap.AttributeTypeAndValue{"OU", "Sales"}, + &ldap.AttributeTypeAndValue{"CN", "J. Smith"}}}, + &ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"DC", "example"}}}, + &ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"DC", "net"}}}}}, + "1.3.6.1.4.1.1466.0=#04024869": ldap.DN{[]*ldap.RelativeDN{ + &ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"1.3.6.1.4.1.1466.0", "Hi"}}}}}, + "1.3.6.1.4.1.1466.0=#04024869,DC=net": ldap.DN{[]*ldap.RelativeDN{ + &ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"1.3.6.1.4.1.1466.0", "Hi"}}}, + &ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"DC", "net"}}}}}, + "CN=Lu\\C4\\8Di\\C4\\87": ldap.DN{[]*ldap.RelativeDN{ + &ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"CN", "Lučić"}}}}}, } for test, answer := range testcases { - dn, err := ParseDN(test) + dn, err := ldap.ParseDN(test) if err != nil { t.Errorf(err.Error()) continue @@ -49,16 +51,16 @@ func TestSuccessfulDNParsing(t *testing.T) { } func TestErrorDNParsing(t *testing.T) { - testcases := map[string]string { - "*": "DN ended with incomplete type, value pair", - "cn=Jim\\0Test": "Failed to decode escaped character: encoding/hex: invalid byte: U+0054 'T'", - "cn=Jim\\0": "Got corrupted escaped character", + testcases := map[string]string{ + "*": "DN ended with incomplete type, value pair", + "cn=Jim\\0Test": "Failed to decode escaped character: encoding/hex: invalid byte: U+0054 'T'", + "cn=Jim\\0": "Got corrupted escaped character", "DC=example,=net": "DN ended with incomplete type, value pair", - "1=#0402486": "Failed to decode BER encoding: encoding/hex: odd length hex string", + "1=#0402486": "Failed to decode BER encoding: encoding/hex: odd length hex string", } for test, answer := range testcases { - _, err := ParseDN(test) + _, err := ldap.ParseDN(test) if err == nil { t.Errorf("Expected %s to fail parsing but succeeded\n", test) } else if err.Error() != answer { @@ -66,5 +68,3 @@ func TestErrorDNParsing(t *testing.T) { } } } - - diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/error.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/error.go new file mode 100644 index 00000000000..2dbc30ac085 --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/error.go @@ -0,0 +1,137 @@ +package ldap + +import ( + "fmt" + + "gopkg.in/asn1-ber.v1" +) + +// LDAP Result Codes +const ( + LDAPResultSuccess = 0 + LDAPResultOperationsError = 1 + LDAPResultProtocolError = 2 + LDAPResultTimeLimitExceeded = 3 + LDAPResultSizeLimitExceeded = 4 + LDAPResultCompareFalse = 5 + LDAPResultCompareTrue = 6 + LDAPResultAuthMethodNotSupported = 7 + LDAPResultStrongAuthRequired = 8 + LDAPResultReferral = 10 + LDAPResultAdminLimitExceeded = 11 + LDAPResultUnavailableCriticalExtension = 12 + LDAPResultConfidentialityRequired = 13 + LDAPResultSaslBindInProgress = 14 + LDAPResultNoSuchAttribute = 16 + LDAPResultUndefinedAttributeType = 17 + LDAPResultInappropriateMatching = 18 + LDAPResultConstraintViolation = 19 + LDAPResultAttributeOrValueExists = 20 + LDAPResultInvalidAttributeSyntax = 21 + LDAPResultNoSuchObject = 32 + LDAPResultAliasProblem = 33 + LDAPResultInvalidDNSyntax = 34 + LDAPResultAliasDereferencingProblem = 36 + LDAPResultInappropriateAuthentication = 48 + LDAPResultInvalidCredentials = 49 + LDAPResultInsufficientAccessRights = 50 + LDAPResultBusy = 51 + LDAPResultUnavailable = 52 + LDAPResultUnwillingToPerform = 53 + LDAPResultLoopDetect = 54 + LDAPResultNamingViolation = 64 + LDAPResultObjectClassViolation = 65 + LDAPResultNotAllowedOnNonLeaf = 66 + LDAPResultNotAllowedOnRDN = 67 + LDAPResultEntryAlreadyExists = 68 + LDAPResultObjectClassModsProhibited = 69 + LDAPResultAffectsMultipleDSAs = 71 + LDAPResultOther = 80 + + ErrorNetwork = 200 + ErrorFilterCompile = 201 + ErrorFilterDecompile = 202 + ErrorDebugging = 203 + ErrorUnexpectedMessage = 204 + ErrorUnexpectedResponse = 205 +) + +var LDAPResultCodeMap = map[uint8]string{ + LDAPResultSuccess: "Success", + LDAPResultOperationsError: "Operations Error", + LDAPResultProtocolError: "Protocol Error", + LDAPResultTimeLimitExceeded: "Time Limit Exceeded", + LDAPResultSizeLimitExceeded: "Size Limit Exceeded", + LDAPResultCompareFalse: "Compare False", + LDAPResultCompareTrue: "Compare True", + LDAPResultAuthMethodNotSupported: "Auth Method Not Supported", + LDAPResultStrongAuthRequired: "Strong Auth Required", + LDAPResultReferral: "Referral", + LDAPResultAdminLimitExceeded: "Admin Limit Exceeded", + LDAPResultUnavailableCriticalExtension: "Unavailable Critical Extension", + LDAPResultConfidentialityRequired: "Confidentiality Required", + LDAPResultSaslBindInProgress: "Sasl Bind In Progress", + LDAPResultNoSuchAttribute: "No Such Attribute", + LDAPResultUndefinedAttributeType: "Undefined Attribute Type", + LDAPResultInappropriateMatching: "Inappropriate Matching", + LDAPResultConstraintViolation: "Constraint Violation", + LDAPResultAttributeOrValueExists: "Attribute Or Value Exists", + LDAPResultInvalidAttributeSyntax: "Invalid Attribute Syntax", + LDAPResultNoSuchObject: "No Such Object", + LDAPResultAliasProblem: "Alias Problem", + LDAPResultInvalidDNSyntax: "Invalid DN Syntax", + LDAPResultAliasDereferencingProblem: "Alias Dereferencing Problem", + LDAPResultInappropriateAuthentication: "Inappropriate Authentication", + LDAPResultInvalidCredentials: "Invalid Credentials", + LDAPResultInsufficientAccessRights: "Insufficient Access Rights", + LDAPResultBusy: "Busy", + LDAPResultUnavailable: "Unavailable", + LDAPResultUnwillingToPerform: "Unwilling To Perform", + LDAPResultLoopDetect: "Loop Detect", + LDAPResultNamingViolation: "Naming Violation", + LDAPResultObjectClassViolation: "Object Class Violation", + LDAPResultNotAllowedOnNonLeaf: "Not Allowed On Non Leaf", + LDAPResultNotAllowedOnRDN: "Not Allowed On RDN", + LDAPResultEntryAlreadyExists: "Entry Already Exists", + LDAPResultObjectClassModsProhibited: "Object Class Mods Prohibited", + LDAPResultAffectsMultipleDSAs: "Affects Multiple DSAs", + LDAPResultOther: "Other", +} + +func getLDAPResultCode(packet *ber.Packet) (code uint8, description string) { + if len(packet.Children) >= 2 { + response := packet.Children[1] + if response.ClassType == ber.ClassApplication && response.TagType == ber.TypeConstructed && len(response.Children) >= 3 { + // Children[1].Children[2] is the diagnosticMessage which is guaranteed to exist as seen here: https://tools.ietf.org/html/rfc4511#section-4.1.9 + return uint8(response.Children[0].Value.(int64)), response.Children[2].Value.(string) + } + } + + return ErrorNetwork, "Invalid packet format" +} + +type Error struct { + Err error + ResultCode uint8 +} + +func (e *Error) Error() string { + return fmt.Sprintf("LDAP Result Code %d %q: %s", e.ResultCode, LDAPResultCodeMap[e.ResultCode], e.Err.Error()) +} + +func NewError(resultCode uint8, err error) error { + return &Error{ResultCode: resultCode, Err: err} +} + +func IsErrorWithCode(err error, desiredResultCode uint8) bool { + if err == nil { + return false + } + + serverError, ok := err.(*Error) + if !ok { + return false + } + + return serverError.ResultCode == desiredResultCode +} diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/example_test.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/example_test.go index 0d441f43b99..b018a966489 100644 --- a/Godeps/_workspace/src/github.com/go-ldap/ldap/example_test.go +++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/example_test.go @@ -5,10 +5,10 @@ import ( "fmt" "log" - "github.com/go-ldap/ldap" + "gopkg.in/ldap.v2" ) -// ExampleConn_Bind demonstrats how to bind a connection to an ldap user +// ExampleConn_Bind demonstrates how to bind a connection to an ldap user // allowing access to restricted attrabutes that user has access to func ExampleConn_Bind() { l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389)) diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/filter.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/filter.go index 1ee1ff89d87..63bcec1e3ae 100644 --- a/Godeps/_workspace/src/github.com/go-ldap/ldap/filter.go +++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/filter.go @@ -5,9 +5,12 @@ package ldap import ( + "bytes" + hexpac "encoding/hex" "errors" "fmt" "strings" + "unicode/utf8" "gopkg.in/asn1-ber.v1" ) @@ -50,6 +53,20 @@ var FilterSubstringsMap = map[uint64]string{ FilterSubstringsFinal: "Substrings Final", } +const ( + MatchingRuleAssertionMatchingRule = 1 + MatchingRuleAssertionType = 2 + MatchingRuleAssertionMatchValue = 3 + MatchingRuleAssertionDNAttributes = 4 +) + +var MatchingRuleAssertionMap = map[uint64]string{ + MatchingRuleAssertionMatchingRule: "Matching Rule Assertion Matching Rule", + MatchingRuleAssertionType: "Matching Rule Assertion Type", + MatchingRuleAssertionMatchValue: "Matching Rule Assertion Match Value", + MatchingRuleAssertionDNAttributes: "Matching Rule Assertion DN Attributes", +} + func CompileFilter(filter string) (*ber.Packet, error) { if len(filter) == 0 || filter[0] != '(' { return nil, NewError(ErrorFilterCompile, errors.New("ldap: filter does not start with an '('")) @@ -108,7 +125,7 @@ func DecompileFilter(packet *ber.Packet) (ret string, err error) { if i == 0 && child.Tag != FilterSubstringsInitial { ret += "*" } - ret += ber.DecodeString(child.Data.Bytes()) + ret += EscapeFilter(ber.DecodeString(child.Data.Bytes())) if child.Tag != FilterSubstringsFinal { ret += "*" } @@ -116,22 +133,53 @@ func DecompileFilter(packet *ber.Packet) (ret string, err error) { case FilterEqualityMatch: ret += ber.DecodeString(packet.Children[0].Data.Bytes()) ret += "=" - ret += ber.DecodeString(packet.Children[1].Data.Bytes()) + ret += EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes())) case FilterGreaterOrEqual: ret += ber.DecodeString(packet.Children[0].Data.Bytes()) ret += ">=" - ret += ber.DecodeString(packet.Children[1].Data.Bytes()) + ret += EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes())) case FilterLessOrEqual: ret += ber.DecodeString(packet.Children[0].Data.Bytes()) ret += "<=" - ret += ber.DecodeString(packet.Children[1].Data.Bytes()) + ret += EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes())) case FilterPresent: ret += ber.DecodeString(packet.Data.Bytes()) ret += "=*" case FilterApproxMatch: ret += ber.DecodeString(packet.Children[0].Data.Bytes()) ret += "~=" - ret += ber.DecodeString(packet.Children[1].Data.Bytes()) + ret += EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes())) + case FilterExtensibleMatch: + attr := "" + dnAttributes := false + matchingRule := "" + value := "" + + for _, child := range packet.Children { + switch child.Tag { + case MatchingRuleAssertionMatchingRule: + matchingRule = ber.DecodeString(child.Data.Bytes()) + case MatchingRuleAssertionType: + attr = ber.DecodeString(child.Data.Bytes()) + case MatchingRuleAssertionMatchValue: + value = ber.DecodeString(child.Data.Bytes()) + case MatchingRuleAssertionDNAttributes: + dnAttributes = child.Value.(bool) + } + } + + if len(attr) > 0 { + ret += attr + } + if dnAttributes { + ret += ":dn" + } + if len(matchingRule) > 0 { + ret += ":" + ret += matchingRule + } + ret += ":=" + ret += EscapeFilter(value) } ret += ")" @@ -155,58 +203,143 @@ func compileFilterSet(filter string, pos int, parent *ber.Packet) (int, error) { } func compileFilter(filter string, pos int) (*ber.Packet, int, error) { - var packet *ber.Packet - var err error + var ( + packet *ber.Packet + err error + ) defer func() { if r := recover(); r != nil { err = NewError(ErrorFilterCompile, errors.New("ldap: error compiling filter")) } }() - newPos := pos - switch filter[pos] { + + currentRune, currentWidth := utf8.DecodeRuneInString(filter[newPos:]) + + switch currentRune { + case utf8.RuneError: + return nil, 0, NewError(ErrorFilterCompile, fmt.Errorf("ldap: error reading rune at position %d", newPos)) case '(': - packet, newPos, err = compileFilter(filter, pos+1) + packet, newPos, err = compileFilter(filter, pos+currentWidth) newPos++ return packet, newPos, err case '&': packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterAnd, nil, FilterMap[FilterAnd]) - newPos, err = compileFilterSet(filter, pos+1, packet) + newPos, err = compileFilterSet(filter, pos+currentWidth, packet) return packet, newPos, err case '|': packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterOr, nil, FilterMap[FilterOr]) - newPos, err = compileFilterSet(filter, pos+1, packet) + newPos, err = compileFilterSet(filter, pos+currentWidth, packet) return packet, newPos, err case '!': packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterNot, nil, FilterMap[FilterNot]) var child *ber.Packet - child, newPos, err = compileFilter(filter, pos+1) + child, newPos, err = compileFilter(filter, pos+currentWidth) packet.AppendChild(child) return packet, newPos, err default: + READING_ATTR := 0 + READING_EXTENSIBLE_MATCHING_RULE := 1 + READING_CONDITION := 2 + + state := READING_ATTR + attribute := "" + extensibleDNAttributes := false + extensibleMatchingRule := "" condition := "" - for newPos < len(filter) && filter[newPos] != ')' { - switch { - case packet != nil: - condition += fmt.Sprintf("%c", filter[newPos]) - case filter[newPos] == '=': - packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterEqualityMatch, nil, FilterMap[FilterEqualityMatch]) - case filter[newPos] == '>' && filter[newPos+1] == '=': - packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterGreaterOrEqual, nil, FilterMap[FilterGreaterOrEqual]) - newPos++ - case filter[newPos] == '<' && filter[newPos+1] == '=': - packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterLessOrEqual, nil, FilterMap[FilterLessOrEqual]) - newPos++ - case filter[newPos] == '~' && filter[newPos+1] == '=': - packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterApproxMatch, nil, FilterMap[FilterLessOrEqual]) - newPos++ - case packet == nil: - attribute += fmt.Sprintf("%c", filter[newPos]) + + for newPos < len(filter) { + remainingFilter := filter[newPos:] + currentRune, currentWidth = utf8.DecodeRuneInString(remainingFilter) + if currentRune == ')' { + break + } + if currentRune == utf8.RuneError { + return packet, newPos, NewError(ErrorFilterCompile, fmt.Errorf("ldap: error reading rune at position %d", newPos)) + } + + switch state { + case READING_ATTR: + switch { + // Extensible rule, with only DN-matching + case currentRune == ':' && strings.HasPrefix(remainingFilter, ":dn:="): + packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterExtensibleMatch, nil, FilterMap[FilterExtensibleMatch]) + extensibleDNAttributes = true + state = READING_CONDITION + newPos += 5 + + // Extensible rule, with DN-matching and a matching OID + case currentRune == ':' && strings.HasPrefix(remainingFilter, ":dn:"): + packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterExtensibleMatch, nil, FilterMap[FilterExtensibleMatch]) + extensibleDNAttributes = true + state = READING_EXTENSIBLE_MATCHING_RULE + newPos += 4 + + // Extensible rule, with attr only + case currentRune == ':' && strings.HasPrefix(remainingFilter, ":="): + packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterExtensibleMatch, nil, FilterMap[FilterExtensibleMatch]) + state = READING_CONDITION + newPos += 2 + + // Extensible rule, with no DN attribute matching + case currentRune == ':': + packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterExtensibleMatch, nil, FilterMap[FilterExtensibleMatch]) + state = READING_EXTENSIBLE_MATCHING_RULE + newPos += 1 + + // Equality condition + case currentRune == '=': + packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterEqualityMatch, nil, FilterMap[FilterEqualityMatch]) + state = READING_CONDITION + newPos += 1 + + // Greater-than or equal + case currentRune == '>' && strings.HasPrefix(remainingFilter, ">="): + packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterGreaterOrEqual, nil, FilterMap[FilterGreaterOrEqual]) + state = READING_CONDITION + newPos += 2 + + // Less-than or equal + case currentRune == '<' && strings.HasPrefix(remainingFilter, "<="): + packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterLessOrEqual, nil, FilterMap[FilterLessOrEqual]) + state = READING_CONDITION + newPos += 2 + + // Approx + case currentRune == '~' && strings.HasPrefix(remainingFilter, "~="): + packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterApproxMatch, nil, FilterMap[FilterApproxMatch]) + state = READING_CONDITION + newPos += 2 + + // Still reading the attribute name + default: + attribute += fmt.Sprintf("%c", currentRune) + newPos += currentWidth + } + + case READING_EXTENSIBLE_MATCHING_RULE: + switch { + + // Matching rule OID is done + case currentRune == ':' && strings.HasPrefix(remainingFilter, ":="): + state = READING_CONDITION + newPos += 2 + + // Still reading the matching rule oid + default: + extensibleMatchingRule += fmt.Sprintf("%c", currentRune) + newPos += currentWidth + } + + case READING_CONDITION: + // append to the condition + condition += fmt.Sprintf("%c", currentRune) + newPos += currentWidth } - newPos++ } + if newPos == len(filter) { err = NewError(ErrorFilterCompile, errors.New("ldap: unexpected end of filter")) return packet, newPos, err @@ -217,6 +350,36 @@ func compileFilter(filter string, pos int) (*ber.Packet, int, error) { } switch { + case packet.Tag == FilterExtensibleMatch: + // MatchingRuleAssertion ::= SEQUENCE { + // matchingRule [1] MatchingRuleID OPTIONAL, + // type [2] AttributeDescription OPTIONAL, + // matchValue [3] AssertionValue, + // dnAttributes [4] BOOLEAN DEFAULT FALSE + // } + + // Include the matching rule oid, if specified + if len(extensibleMatchingRule) > 0 { + packet.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionMatchingRule, extensibleMatchingRule, MatchingRuleAssertionMap[MatchingRuleAssertionMatchingRule])) + } + + // Include the attribute, if specified + if len(attribute) > 0 { + packet.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionType, attribute, MatchingRuleAssertionMap[MatchingRuleAssertionType])) + } + + // Add the value (only required child) + encodedString, err := escapedStringToEncodedBytes(condition) + if err != nil { + return packet, newPos, err + } + packet.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionMatchValue, encodedString, MatchingRuleAssertionMap[MatchingRuleAssertionMatchValue])) + + // Defaults to false, so only include in the sequence if true + if extensibleDNAttributes { + packet.AppendChild(ber.NewBoolean(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionDNAttributes, extensibleDNAttributes, MatchingRuleAssertionMap[MatchingRuleAssertionDNAttributes])) + } + case packet.Tag == FilterEqualityMatch && condition == "*": packet = ber.NewString(ber.ClassContext, ber.TypePrimitive, FilterPresent, attribute, FilterMap[FilterPresent]) case packet.Tag == FilterEqualityMatch && strings.Contains(condition, "*"): @@ -238,15 +401,56 @@ func compileFilter(filter string, pos int) (*ber.Packet, int, error) { default: tag = FilterSubstringsAny } - seq.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, tag, part, FilterSubstringsMap[uint64(tag)])) + encodedString, err := escapedStringToEncodedBytes(part) + if err != nil { + return packet, newPos, err + } + seq.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, tag, encodedString, FilterSubstringsMap[uint64(tag)])) } packet.AppendChild(seq) default: + encodedString, err := escapedStringToEncodedBytes(condition) + if err != nil { + return packet, newPos, err + } packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute, "Attribute")) - packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, condition, "Condition")) + packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, encodedString, "Condition")) } - newPos++ + newPos += currentWidth return packet, newPos, err } } + +// Convert from "ABC\xx\xx\xx" form to literal bytes for transport +func escapedStringToEncodedBytes(escapedString string) (string, error) { + var buffer bytes.Buffer + i := 0 + for i < len(escapedString) { + currentRune, currentWidth := utf8.DecodeRuneInString(escapedString[i:]) + if currentRune == utf8.RuneError { + return "", NewError(ErrorFilterCompile, fmt.Errorf("ldap: error reading rune at position %d", i)) + } + + // Check for escaped hex characters and convert them to their literal value for transport. + if currentRune == '\\' { + // http://tools.ietf.org/search/rfc4515 + // \ (%x5C) is not a valid character unless it is followed by two HEX characters due to not + // being a member of UTF1SUBSET. + if i+2 > len(escapedString) { + return "", NewError(ErrorFilterCompile, errors.New("ldap: missing characters for escape in filter")) + } + if escByte, decodeErr := hexpac.DecodeString(escapedString[i+1 : i+3]); decodeErr != nil { + return "", NewError(ErrorFilterCompile, errors.New("ldap: invalid characters for escape in filter")) + } else { + buffer.WriteByte(escByte[0]) + i += 2 // +1 from end of loop, so 3 total for \xx. + } + } else { + buffer.WriteRune(currentRune) + } + + i += currentWidth + } + return buffer.String(), nil +} diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/filter_test.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/filter_test.go index 673ef080235..ae1b79b0c0d 100644 --- a/Godeps/_workspace/src/github.com/go-ldap/ldap/filter_test.go +++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/filter_test.go @@ -1,54 +1,220 @@ -package ldap +package ldap_test import ( + "strings" "testing" "gopkg.in/asn1-ber.v1" + "gopkg.in/ldap.v2" ) type compileTest struct { - filterStr string - filterType int + filterStr string + + expectedFilter string + expectedType int + expectedErr string } var testFilters = []compileTest{ - compileTest{filterStr: "(&(sn=Miller)(givenName=Bob))", filterType: FilterAnd}, - compileTest{filterStr: "(|(sn=Miller)(givenName=Bob))", filterType: FilterOr}, - compileTest{filterStr: "(!(sn=Miller))", filterType: FilterNot}, - compileTest{filterStr: "(sn=Miller)", filterType: FilterEqualityMatch}, - compileTest{filterStr: "(sn=Mill*)", filterType: FilterSubstrings}, - compileTest{filterStr: "(sn=*Mill)", filterType: FilterSubstrings}, - compileTest{filterStr: "(sn=*Mill*)", filterType: FilterSubstrings}, - compileTest{filterStr: "(sn=*i*le*)", filterType: FilterSubstrings}, - compileTest{filterStr: "(sn=Mi*l*r)", filterType: FilterSubstrings}, - compileTest{filterStr: "(sn=Mi*le*)", filterType: FilterSubstrings}, - compileTest{filterStr: "(sn=*i*ler)", filterType: FilterSubstrings}, - compileTest{filterStr: "(sn>=Miller)", filterType: FilterGreaterOrEqual}, - compileTest{filterStr: "(sn<=Miller)", filterType: FilterLessOrEqual}, - compileTest{filterStr: "(sn=*)", filterType: FilterPresent}, - compileTest{filterStr: "(sn~=Miller)", filterType: FilterApproxMatch}, + compileTest{ + filterStr: "(&(sn=Miller)(givenName=Bob))", + expectedFilter: "(&(sn=Miller)(givenName=Bob))", + expectedType: ldap.FilterAnd, + }, + compileTest{ + filterStr: "(|(sn=Miller)(givenName=Bob))", + expectedFilter: "(|(sn=Miller)(givenName=Bob))", + expectedType: ldap.FilterOr, + }, + compileTest{ + filterStr: "(!(sn=Miller))", + expectedFilter: "(!(sn=Miller))", + expectedType: ldap.FilterNot, + }, + compileTest{ + filterStr: "(sn=Miller)", + expectedFilter: "(sn=Miller)", + expectedType: ldap.FilterEqualityMatch, + }, + compileTest{ + filterStr: "(sn=Mill*)", + expectedFilter: "(sn=Mill*)", + expectedType: ldap.FilterSubstrings, + }, + compileTest{ + filterStr: "(sn=*Mill)", + expectedFilter: "(sn=*Mill)", + expectedType: ldap.FilterSubstrings, + }, + compileTest{ + filterStr: "(sn=*Mill*)", + expectedFilter: "(sn=*Mill*)", + expectedType: ldap.FilterSubstrings, + }, + compileTest{ + filterStr: "(sn=*i*le*)", + expectedFilter: "(sn=*i*le*)", + expectedType: ldap.FilterSubstrings, + }, + compileTest{ + filterStr: "(sn=Mi*l*r)", + expectedFilter: "(sn=Mi*l*r)", + expectedType: ldap.FilterSubstrings, + }, + // substring filters escape properly + compileTest{ + filterStr: `(sn=Mi*함*r)`, + expectedFilter: `(sn=Mi*\ed\95\a8*r)`, + expectedType: ldap.FilterSubstrings, + }, + // already escaped substring filters don't get double-escaped + compileTest{ + filterStr: `(sn=Mi*\ed\95\a8*r)`, + expectedFilter: `(sn=Mi*\ed\95\a8*r)`, + expectedType: ldap.FilterSubstrings, + }, + compileTest{ + filterStr: "(sn=Mi*le*)", + expectedFilter: "(sn=Mi*le*)", + expectedType: ldap.FilterSubstrings, + }, + compileTest{ + filterStr: "(sn=*i*ler)", + expectedFilter: "(sn=*i*ler)", + expectedType: ldap.FilterSubstrings, + }, + compileTest{ + filterStr: "(sn>=Miller)", + expectedFilter: "(sn>=Miller)", + expectedType: ldap.FilterGreaterOrEqual, + }, + compileTest{ + filterStr: "(sn<=Miller)", + expectedFilter: "(sn<=Miller)", + expectedType: ldap.FilterLessOrEqual, + }, + compileTest{ + filterStr: "(sn=*)", + expectedFilter: "(sn=*)", + expectedType: ldap.FilterPresent, + }, + compileTest{ + filterStr: "(sn~=Miller)", + expectedFilter: "(sn~=Miller)", + expectedType: ldap.FilterApproxMatch, + }, + compileTest{ + filterStr: `(objectGUID='\fc\fe\a3\ab\f9\90N\aaGm\d5I~\d12)`, + expectedFilter: `(objectGUID='\fc\fe\a3\ab\f9\90N\aaGm\d5I~\d12)`, + expectedType: ldap.FilterEqualityMatch, + }, + compileTest{ + filterStr: `(objectGUID=абвгдеёжзийклмнопрстуфхцчшщъыьэюя)`, + expectedFilter: `(objectGUID=\d0\b0\d0\b1\d0\b2\d0\b3\d0\b4\d0\b5\d1\91\d0\b6\d0\b7\d0\b8\d0\b9\d0\ba\d0\bb\d0\bc\d0\bd\d0\be\d0\bf\d1\80\d1\81\d1\82\d1\83\d1\84\d1\85\d1\86\d1\87\d1\88\d1\89\d1\8a\d1\8b\d1\8c\d1\8d\d1\8e\d1\8f)`, + expectedType: ldap.FilterEqualityMatch, + }, + compileTest{ + filterStr: `(objectGUID=함수목록)`, + expectedFilter: `(objectGUID=\ed\95\a8\ec\88\98\eb\aa\a9\eb\a1\9d)`, + expectedType: ldap.FilterEqualityMatch, + }, + compileTest{ + filterStr: `(objectGUID=`, + expectedFilter: ``, + expectedType: 0, + expectedErr: "unexpected end of filter", + }, + compileTest{ + filterStr: `(objectGUID=함수목록`, + expectedFilter: ``, + expectedType: 0, + expectedErr: "unexpected end of filter", + }, + compileTest{ + filterStr: `(&(objectclass=inetorgperson)(cn=中文))`, + expectedFilter: `(&(objectclass=inetorgperson)(cn=\e4\b8\ad\e6\96\87))`, + expectedType: 0, + }, + // attr extension + compileTest{ + filterStr: `(memberOf:=foo)`, + expectedFilter: `(memberOf:=foo)`, + expectedType: ldap.FilterExtensibleMatch, + }, + // attr+named matching rule extension + compileTest{ + filterStr: `(memberOf:test:=foo)`, + expectedFilter: `(memberOf:test:=foo)`, + expectedType: ldap.FilterExtensibleMatch, + }, + // attr+oid matching rule extension + compileTest{ + filterStr: `(cn:1.2.3.4.5:=Fred Flintstone)`, + expectedFilter: `(cn:1.2.3.4.5:=Fred Flintstone)`, + expectedType: ldap.FilterExtensibleMatch, + }, + // attr+dn+oid matching rule extension + compileTest{ + filterStr: `(sn:dn:2.4.6.8.10:=Barney Rubble)`, + expectedFilter: `(sn:dn:2.4.6.8.10:=Barney Rubble)`, + expectedType: ldap.FilterExtensibleMatch, + }, + // attr+dn extension + compileTest{ + filterStr: `(o:dn:=Ace Industry)`, + expectedFilter: `(o:dn:=Ace Industry)`, + expectedType: ldap.FilterExtensibleMatch, + }, + // dn extension + compileTest{ + filterStr: `(:dn:2.4.6.8.10:=Dino)`, + expectedFilter: `(:dn:2.4.6.8.10:=Dino)`, + expectedType: ldap.FilterExtensibleMatch, + }, + compileTest{ + filterStr: `(memberOf:1.2.840.113556.1.4.1941:=CN=User1,OU=blah,DC=mydomain,DC=net)`, + expectedFilter: `(memberOf:1.2.840.113556.1.4.1941:=CN=User1,OU=blah,DC=mydomain,DC=net)`, + expectedType: ldap.FilterExtensibleMatch, + }, + // compileTest{ filterStr: "()", filterType: FilterExtensibleMatch }, } +var testInvalidFilters = []string{ + `(objectGUID=\zz)`, + `(objectGUID=\a)`, +} + func TestFilter(t *testing.T) { // Test Compiler and Decompiler for _, i := range testFilters { - filter, err := CompileFilter(i.filterStr) + filter, err := ldap.CompileFilter(i.filterStr) if err != nil { - t.Errorf("Problem compiling %s - %s", i.filterStr, err.Error()) - } else if filter.Tag != ber.Tag(i.filterType) { - t.Errorf("%q Expected %q got %q", i.filterStr, FilterMap[uint64(i.filterType)], FilterMap[uint64(filter.Tag)]) + if i.expectedErr == "" || !strings.Contains(err.Error(), i.expectedErr) { + t.Errorf("Problem compiling '%s' - '%v' (expected error to contain '%v')", i.filterStr, err, i.expectedErr) + } + } else if filter.Tag != ber.Tag(i.expectedType) { + t.Errorf("%q Expected %q got %q", i.filterStr, ldap.FilterMap[uint64(i.expectedType)], ldap.FilterMap[uint64(filter.Tag)]) } else { - o, err := DecompileFilter(filter) + o, err := ldap.DecompileFilter(filter) if err != nil { t.Errorf("Problem compiling %s - %s", i.filterStr, err.Error()) - } else if i.filterStr != o { - t.Errorf("%q expected, got %q", i.filterStr, o) + } else if i.expectedFilter != o { + t.Errorf("%q expected, got %q", i.expectedFilter, o) } } } } +func TestInvalidFilter(t *testing.T) { + for _, filterStr := range testInvalidFilters { + if _, err := ldap.CompileFilter(filterStr); err == nil { + t.Errorf("Problem compiling %s - expected err", filterStr) + } + } +} + func BenchmarkFilterCompile(b *testing.B) { b.StopTimer() filters := make([]string, len(testFilters)) @@ -61,7 +227,7 @@ func BenchmarkFilterCompile(b *testing.B) { maxIdx := len(filters) b.StartTimer() for i := 0; i < b.N; i++ { - CompileFilter(filters[i%maxIdx]) + ldap.CompileFilter(filters[i%maxIdx]) } } @@ -71,12 +237,12 @@ func BenchmarkFilterDecompile(b *testing.B) { // Test Compiler and Decompiler for idx, i := range testFilters { - filters[idx], _ = CompileFilter(i.filterStr) + filters[idx], _ = ldap.CompileFilter(i.filterStr) } maxIdx := len(filters) b.StartTimer() for i := 0; i < b.N; i++ { - DecompileFilter(filters[i%maxIdx]) + ldap.DecompileFilter(filters[i%maxIdx]) } } diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/ldap.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/ldap.go index e91972ff4c1..1620aaea6ee 100644 --- a/Godeps/_workspace/src/github.com/go-ldap/ldap/ldap.go +++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/ldap.go @@ -6,7 +6,6 @@ package ldap import ( "errors" - "fmt" "io/ioutil" "os" @@ -60,98 +59,6 @@ var ApplicationMap = map[uint8]string{ ApplicationExtendedResponse: "Extended Response", } -// LDAP Result Codes -const ( - LDAPResultSuccess = 0 - LDAPResultOperationsError = 1 - LDAPResultProtocolError = 2 - LDAPResultTimeLimitExceeded = 3 - LDAPResultSizeLimitExceeded = 4 - LDAPResultCompareFalse = 5 - LDAPResultCompareTrue = 6 - LDAPResultAuthMethodNotSupported = 7 - LDAPResultStrongAuthRequired = 8 - LDAPResultReferral = 10 - LDAPResultAdminLimitExceeded = 11 - LDAPResultUnavailableCriticalExtension = 12 - LDAPResultConfidentialityRequired = 13 - LDAPResultSaslBindInProgress = 14 - LDAPResultNoSuchAttribute = 16 - LDAPResultUndefinedAttributeType = 17 - LDAPResultInappropriateMatching = 18 - LDAPResultConstraintViolation = 19 - LDAPResultAttributeOrValueExists = 20 - LDAPResultInvalidAttributeSyntax = 21 - LDAPResultNoSuchObject = 32 - LDAPResultAliasProblem = 33 - LDAPResultInvalidDNSyntax = 34 - LDAPResultAliasDereferencingProblem = 36 - LDAPResultInappropriateAuthentication = 48 - LDAPResultInvalidCredentials = 49 - LDAPResultInsufficientAccessRights = 50 - LDAPResultBusy = 51 - LDAPResultUnavailable = 52 - LDAPResultUnwillingToPerform = 53 - LDAPResultLoopDetect = 54 - LDAPResultNamingViolation = 64 - LDAPResultObjectClassViolation = 65 - LDAPResultNotAllowedOnNonLeaf = 66 - LDAPResultNotAllowedOnRDN = 67 - LDAPResultEntryAlreadyExists = 68 - LDAPResultObjectClassModsProhibited = 69 - LDAPResultAffectsMultipleDSAs = 71 - LDAPResultOther = 80 - - ErrorNetwork = 200 - ErrorFilterCompile = 201 - ErrorFilterDecompile = 202 - ErrorDebugging = 203 - ErrorUnexpectedMessage = 204 - ErrorUnexpectedResponse = 205 -) - -var LDAPResultCodeMap = map[uint8]string{ - LDAPResultSuccess: "Success", - LDAPResultOperationsError: "Operations Error", - LDAPResultProtocolError: "Protocol Error", - LDAPResultTimeLimitExceeded: "Time Limit Exceeded", - LDAPResultSizeLimitExceeded: "Size Limit Exceeded", - LDAPResultCompareFalse: "Compare False", - LDAPResultCompareTrue: "Compare True", - LDAPResultAuthMethodNotSupported: "Auth Method Not Supported", - LDAPResultStrongAuthRequired: "Strong Auth Required", - LDAPResultReferral: "Referral", - LDAPResultAdminLimitExceeded: "Admin Limit Exceeded", - LDAPResultUnavailableCriticalExtension: "Unavailable Critical Extension", - LDAPResultConfidentialityRequired: "Confidentiality Required", - LDAPResultSaslBindInProgress: "Sasl Bind In Progress", - LDAPResultNoSuchAttribute: "No Such Attribute", - LDAPResultUndefinedAttributeType: "Undefined Attribute Type", - LDAPResultInappropriateMatching: "Inappropriate Matching", - LDAPResultConstraintViolation: "Constraint Violation", - LDAPResultAttributeOrValueExists: "Attribute Or Value Exists", - LDAPResultInvalidAttributeSyntax: "Invalid Attribute Syntax", - LDAPResultNoSuchObject: "No Such Object", - LDAPResultAliasProblem: "Alias Problem", - LDAPResultInvalidDNSyntax: "Invalid DN Syntax", - LDAPResultAliasDereferencingProblem: "Alias Dereferencing Problem", - LDAPResultInappropriateAuthentication: "Inappropriate Authentication", - LDAPResultInvalidCredentials: "Invalid Credentials", - LDAPResultInsufficientAccessRights: "Insufficient Access Rights", - LDAPResultBusy: "Busy", - LDAPResultUnavailable: "Unavailable", - LDAPResultUnwillingToPerform: "Unwilling To Perform", - LDAPResultLoopDetect: "Loop Detect", - LDAPResultNamingViolation: "Naming Violation", - LDAPResultObjectClassViolation: "Object Class Violation", - LDAPResultNotAllowedOnNonLeaf: "Not Allowed On Non Leaf", - LDAPResultNotAllowedOnRDN: "Not Allowed On RDN", - LDAPResultEntryAlreadyExists: "Entry Already Exists", - LDAPResultObjectClassModsProhibited: "Object Class Mods Prohibited", - LDAPResultAffectsMultipleDSAs: "Affects Multiple DSAs", - LDAPResultOther: "Other", -} - // Ldap Behera Password Policy Draft 10 (https://tools.ietf.org/html/draft-behera-ldap-password-policy-10) const ( BeheraPasswordExpired = 0 @@ -318,8 +225,8 @@ func addRequestDescriptions(packet *ber.Packet) { } func addDefaultLDAPResponseDescriptions(packet *ber.Packet) { - resultCode := packet.Children[1].Children[0].Value.(int64) - packet.Children[1].Children[0].Description = "Result Code (" + LDAPResultCodeMap[uint8(resultCode)] + ")" + resultCode, _ := getLDAPResultCode(packet) + packet.Children[1].Children[0].Description = "Result Code (" + LDAPResultCodeMap[resultCode] + ")" packet.Children[1].Children[1].Description = "Matched DN" packet.Children[1].Children[2].Description = "Error Message" if len(packet.Children[1].Children) > 3 { @@ -343,30 +250,6 @@ func DebugBinaryFile(fileName string) error { return nil } -type Error struct { - Err error - ResultCode uint8 -} - -func (e *Error) Error() string { - return fmt.Sprintf("LDAP Result Code %d %q: %s", e.ResultCode, LDAPResultCodeMap[e.ResultCode], e.Err.Error()) -} - -func NewError(resultCode uint8, err error) error { - return &Error{ResultCode: resultCode, Err: err} -} - -func getLDAPResultCode(packet *ber.Packet) (code uint8, description string) { - if len(packet.Children) >= 2 { - response := packet.Children[1] - if response.ClassType == ber.ClassApplication && response.TagType == ber.TypeConstructed && len(response.Children) >= 3 { - return uint8(response.Children[0].Value.(int64)), response.Children[2].Value.(string) - } - } - - return ErrorNetwork, "Invalid packet format" -} - var hex = "0123456789abcdef" func mustEscape(c byte) bool { diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/ldap_test.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/ldap_test.go index e9933f99a69..9f430518001 100644 --- a/Godeps/_workspace/src/github.com/go-ldap/ldap/ldap_test.go +++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/ldap_test.go @@ -1,9 +1,11 @@ -package ldap +package ldap_test import ( "crypto/tls" "fmt" "testing" + + "gopkg.in/ldap.v2" ) var ldapServer = "ldap.itd.umich.edu" @@ -21,7 +23,7 @@ var attributes = []string{ func TestDial(t *testing.T) { fmt.Printf("TestDial: starting...\n") - l, err := Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort)) + l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort)) if err != nil { t.Errorf(err.Error()) return @@ -32,7 +34,7 @@ func TestDial(t *testing.T) { func TestDialTLS(t *testing.T) { fmt.Printf("TestDialTLS: starting...\n") - l, err := DialTLS("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapTLSPort), &tls.Config{InsecureSkipVerify: true}) + l, err := ldap.DialTLS("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapTLSPort), &tls.Config{InsecureSkipVerify: true}) if err != nil { t.Errorf(err.Error()) return @@ -43,7 +45,7 @@ func TestDialTLS(t *testing.T) { func TestStartTLS(t *testing.T) { fmt.Printf("TestStartTLS: starting...\n") - l, err := Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort)) + l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort)) if err != nil { t.Errorf(err.Error()) return @@ -58,16 +60,16 @@ func TestStartTLS(t *testing.T) { func TestSearch(t *testing.T) { fmt.Printf("TestSearch: starting...\n") - l, err := Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort)) + l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort)) if err != nil { t.Errorf(err.Error()) return } defer l.Close() - searchRequest := NewSearchRequest( + searchRequest := ldap.NewSearchRequest( baseDN, - ScopeWholeSubtree, DerefAlways, 0, 0, false, + ldap.ScopeWholeSubtree, ldap.DerefAlways, 0, 0, false, filter[0], attributes, nil) @@ -83,16 +85,16 @@ func TestSearch(t *testing.T) { func TestSearchStartTLS(t *testing.T) { fmt.Printf("TestSearchStartTLS: starting...\n") - l, err := Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort)) + l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort)) if err != nil { t.Errorf(err.Error()) return } defer l.Close() - searchRequest := NewSearchRequest( + searchRequest := ldap.NewSearchRequest( baseDN, - ScopeWholeSubtree, DerefAlways, 0, 0, false, + ldap.ScopeWholeSubtree, ldap.DerefAlways, 0, 0, false, filter[0], attributes, nil) @@ -123,7 +125,7 @@ func TestSearchStartTLS(t *testing.T) { func TestSearchWithPaging(t *testing.T) { fmt.Printf("TestSearchWithPaging: starting...\n") - l, err := Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort)) + l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort)) if err != nil { t.Errorf(err.Error()) return @@ -136,9 +138,9 @@ func TestSearchWithPaging(t *testing.T) { return } - searchRequest := NewSearchRequest( + searchRequest := ldap.NewSearchRequest( baseDN, - ScopeWholeSubtree, DerefAlways, 0, 0, false, + ldap.ScopeWholeSubtree, ldap.DerefAlways, 0, 0, false, filter[2], attributes, nil) @@ -149,12 +151,38 @@ func TestSearchWithPaging(t *testing.T) { } fmt.Printf("TestSearchWithPaging: %s -> num of entries = %d\n", searchRequest.Filter, len(sr.Entries)) + + searchRequest = ldap.NewSearchRequest( + baseDN, + ldap.ScopeWholeSubtree, ldap.DerefAlways, 0, 0, false, + filter[2], + attributes, + []ldap.Control{ldap.NewControlPaging(5)}) + sr, err = l.SearchWithPaging(searchRequest, 5) + if err != nil { + t.Errorf(err.Error()) + return + } + + fmt.Printf("TestSearchWithPaging: %s -> num of entries = %d\n", searchRequest.Filter, len(sr.Entries)) + + searchRequest = ldap.NewSearchRequest( + baseDN, + ldap.ScopeWholeSubtree, ldap.DerefAlways, 0, 0, false, + filter[2], + attributes, + []ldap.Control{ldap.NewControlPaging(500)}) + sr, err = l.SearchWithPaging(searchRequest, 5) + if err == nil { + t.Errorf("expected an error when paging size in control in search request doesn't match size given in call, got none") + return + } } -func searchGoroutine(t *testing.T, l *Conn, results chan *SearchResult, i int) { - searchRequest := NewSearchRequest( +func searchGoroutine(t *testing.T, l *ldap.Conn, results chan *ldap.SearchResult, i int) { + searchRequest := ldap.NewSearchRequest( baseDN, - ScopeWholeSubtree, DerefAlways, 0, 0, false, + ldap.ScopeWholeSubtree, ldap.DerefAlways, 0, 0, false, filter[i], attributes, nil) @@ -169,17 +197,17 @@ func searchGoroutine(t *testing.T, l *Conn, results chan *SearchResult, i int) { func testMultiGoroutineSearch(t *testing.T, TLS bool, startTLS bool) { fmt.Printf("TestMultiGoroutineSearch: starting...\n") - var l *Conn + var l *ldap.Conn var err error if TLS { - l, err = DialTLS("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapTLSPort), &tls.Config{InsecureSkipVerify: true}) + l, err = ldap.DialTLS("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapTLSPort), &tls.Config{InsecureSkipVerify: true}) if err != nil { t.Errorf(err.Error()) return } defer l.Close() } else { - l, err = Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort)) + l, err = ldap.Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort)) if err != nil { t.Errorf(err.Error()) return @@ -195,9 +223,9 @@ func testMultiGoroutineSearch(t *testing.T, TLS bool, startTLS bool) { } } - results := make([]chan *SearchResult, len(filter)) + results := make([]chan *ldap.SearchResult, len(filter)) for i := range filter { - results[i] = make(chan *SearchResult) + results[i] = make(chan *ldap.SearchResult) go searchGoroutine(t, l, results[i], i) } for i := range filter { @@ -217,17 +245,17 @@ func TestMultiGoroutineSearch(t *testing.T) { } func TestEscapeFilter(t *testing.T) { - if got, want := EscapeFilter("a\x00b(c)d*e\\f"), `a\00b\28c\29d\2ae\5cf`; got != want { + if got, want := ldap.EscapeFilter("a\x00b(c)d*e\\f"), `a\00b\28c\29d\2ae\5cf`; got != want { t.Errorf("Got %s, expected %s", want, got) } - if got, want := EscapeFilter("Lučić"), `Lu\c4\8di\c4\87`; got != want { + if got, want := ldap.EscapeFilter("Lučić"), `Lu\c4\8di\c4\87`; got != want { t.Errorf("Got %s, expected %s", want, got) } } func TestCompare(t *testing.T) { fmt.Printf("TestCompare: starting...\n") - l, err := Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort)) + l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort)) if err != nil { t.Fatal(err.Error()) } @@ -243,5 +271,5 @@ func TestCompare(t *testing.T) { return } - fmt.Printf("TestCompare: -> num of entries = %d\n", sr) + fmt.Printf("TestCompare: -> %v\n", sr) } diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/search.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/search.go index 5ae3c449477..23a2cf2b202 100644 --- a/Godeps/_workspace/src/github.com/go-ldap/ldap/search.go +++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/search.go @@ -62,6 +62,7 @@ package ldap import ( "errors" "fmt" + "sort" "strings" "gopkg.in/asn1-ber.v1" @@ -93,6 +94,26 @@ var DerefMap = map[int]string{ DerefAlways: "DerefAlways", } +// NewEntry returns an Entry object with the specified distinguished name and attribute key-value pairs. +// The map of attributes is accessed in alphabetical order of the keys in order to ensure that, for the +// same input map of attributes, the output entry will contain the same order of attributes +func NewEntry(dn string, attributes map[string][]string) *Entry { + var attributeNames []string + for attributeName := range attributes { + attributeNames = append(attributeNames, attributeName) + } + sort.Strings(attributeNames) + + var encodedAttributes []*EntryAttribute + for _, attributeName := range attributeNames { + encodedAttributes = append(encodedAttributes, NewEntryAttribute(attributeName, attributes[attributeName])) + } + return &Entry{ + DN: dn, + Attributes: encodedAttributes, + } +} + type Entry struct { DN string Attributes []*EntryAttribute @@ -146,6 +167,19 @@ func (e *Entry) PrettyPrint(indent int) { } } +// NewEntryAttribute returns a new EntryAttribute with the desired key-value pair +func NewEntryAttribute(name string, values []string) *EntryAttribute { + var bytes [][]byte + for _, value := range values { + bytes = append(bytes, []byte(value)) + } + return &EntryAttribute{ + Name: name, + Values: values, + ByteValues: bytes, + } +} + type EntryAttribute struct { Name string Values []string @@ -234,13 +268,32 @@ func NewSearchRequest( } } +// SearchWithPaging accepts a search request and desired page size in order to execute LDAP queries to fulfill the +// search request. All paged LDAP query responses will be buffered and the final result will be returned atomically. +// The following four cases are possible given the arguments: +// - given SearchRequest missing a control of type ControlTypePaging: we will add one with the desired paging size +// - given SearchRequest contains a control of type ControlTypePaging that isn't actually a ControlPaging: fail without issuing any queries +// - given SearchRequest contains a control of type ControlTypePaging with pagingSize equal to the size requested: no change to the search request +// - given SearchRequest contains a control of type ControlTypePaging with pagingSize not equal to the size requested: fail without issuing any queries +// A requested pagingSize of 0 is interpreted as no limit by LDAP servers. func (l *Conn) SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) (*SearchResult, error) { - if searchRequest.Controls == nil { - searchRequest.Controls = make([]Control, 0) + var pagingControl *ControlPaging + + control := FindControl(searchRequest.Controls, ControlTypePaging) + if control == nil { + pagingControl = NewControlPaging(pagingSize) + searchRequest.Controls = append(searchRequest.Controls, pagingControl) + } else { + castControl, ok := control.(*ControlPaging) + if !ok { + return nil, fmt.Errorf("Expected paging control to be of type *ControlPaging, got %v", control) + } + if castControl.PagingSize != pagingSize { + return nil, fmt.Errorf("Paging size given in search request (%d) conflicts with size given in search call (%d)", castControl.PagingSize, pagingSize) + } + pagingControl = castControl } - pagingControl := NewControlPaging(pagingSize) - searchRequest.Controls = append(searchRequest.Controls, pagingControl) searchResult := new(SearchResult) for { result, err := l.Search(searchRequest) diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/search_test.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/search_test.go new file mode 100644 index 00000000000..efb8147d1a5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/search_test.go @@ -0,0 +1,31 @@ +package ldap + +import ( + "reflect" + "testing" +) + +// TestNewEntry tests that repeated calls to NewEntry return the same value with the same input +func TestNewEntry(t *testing.T) { + dn := "testDN" + attributes := map[string][]string{ + "alpha": {"value"}, + "beta": {"value"}, + "gamma": {"value"}, + "delta": {"value"}, + "epsilon": {"value"}, + } + exectedEntry := NewEntry(dn, attributes) + + iteration := 0 + for { + if iteration == 100 { + break + } + testEntry := NewEntry(dn, attributes) + if !reflect.DeepEqual(exectedEntry, testEntry) { + t.Fatalf("consequent calls to NewEntry did not yield the same result:\n\texpected:\n\t%s\n\tgot:\n\t%s\n", exectedEntry, testEntry) + } + iteration = iteration + 1 + } +}