Commit 2bf94d8d by David Warden

version bump go-ldap to v2.2.1

parent 18543814
...@@ -159,8 +159,8 @@ ...@@ -159,8 +159,8 @@
}, },
{ {
"ImportPath": "github.com/go-ldap/ldap", "ImportPath": "github.com/go-ldap/ldap",
"Comment": "v1-19-g83e6542", "Comment": "v2.2.1",
"Rev": "83e65426fd1c06626e88aa8a085e5bfed0208e29" "Rev": "07a7330929b9ee80495c88a4439657d89c7dbd87"
}, },
{ {
"ImportPath": "github.com/go-macaron/binding", "ImportPath": "github.com/go-macaron/binding",
......
...@@ -2,10 +2,13 @@ language: go ...@@ -2,10 +2,13 @@ language: go
go: go:
- 1.2 - 1.2
- 1.3 - 1.3
- 1.4
- 1.5
- tip - tip
go_import_path: gopkg.in/ldap.v2
install: install:
- go get gopkg.in/asn1-ber.v1 - 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 get code.google.com/p/go.tools/cmd/cover || go get golang.org/x/tools/cmd/cover
- go build -v ./... - go build -v ./...
script: script:
......
[![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. # 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 - gopkg.in/asn1-ber.v1
...@@ -14,6 +26,9 @@ ...@@ -14,6 +26,9 @@
- Compiling string filters to LDAP filters - Compiling string filters to LDAP filters
- Paging Search Results - Paging Search Results
- Modify Requests / Responses - Modify Requests / Responses
- Add Requests / Responses
- Delete Requests / Responses
- Better Unicode support
## Examples: ## Examples:
...@@ -26,23 +41,15 @@ ...@@ -26,23 +41,15 @@
## TODO: ## TODO:
- Add Requests / Responses - [x] Add Requests / Responses
- Delete Requests / Responses - [x] Delete Requests / Responses
- Modify DN Requests / Responses - [x] Modify DN Requests / Responses
- Compare Requests / Responses - [ ] Compare Requests / Responses
- Implement Tests / Benchmarks - [ ] 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 Go gopher was designed by Renee French. (http://reneefrench.blogspot.com/)
The design is licensed under the Creative Commons 3.0 Attributions license. The design is licensed under the Creative Commons 3.0 Attributions license.
Read this article for more details: http://blog.golang.org/gopher Read this article for more details: http://blog.golang.org/gopher
//
// 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
}
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)
}
...@@ -8,11 +8,12 @@ import ( ...@@ -8,11 +8,12 @@ import (
"crypto/tls" "crypto/tls"
"errors" "errors"
"fmt" "fmt"
"gopkg.in/asn1-ber.v1"
"log" "log"
"net" "net"
"sync" "sync"
"time" "time"
"gopkg.in/asn1-ber.v1"
) )
const ( const (
...@@ -53,6 +54,8 @@ type Conn struct { ...@@ -53,6 +54,8 @@ type Conn struct {
messageMutex sync.Mutex messageMutex sync.Mutex
} }
var _ Client = &Conn{}
// DefaultTimeout is a package-level variable that sets the timeout value // DefaultTimeout is a package-level variable that sets the timeout value
// used for the Dial and DialTLS methods. // used for the Dial and DialTLS methods.
// //
...@@ -176,7 +179,7 @@ func (l *Conn) StartTLS(config *tls.Config) error { ...@@ -176,7 +179,7 @@ func (l *Conn) StartTLS(config *tls.Config) error {
ber.PrintPacket(packet) 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) conn := tls.Client(l.conn, config)
if err := conn.Handshake(); err != nil { if err := conn.Handshake(); err != nil {
...@@ -186,6 +189,8 @@ func (l *Conn) StartTLS(config *tls.Config) error { ...@@ -186,6 +189,8 @@ func (l *Conn) StartTLS(config *tls.Config) error {
l.isTLS = true l.isTLS = true
l.conn = conn l.conn = conn
} else {
return NewError(resultCode, fmt.Errorf("ldap: cannot StartTLS (%s)", message))
} }
go l.reader() go l.reader()
......
...@@ -16,11 +16,13 @@ const ( ...@@ -16,11 +16,13 @@ const (
ControlTypeBeheraPasswordPolicy = "1.3.6.1.4.1.42.2.27.8.5.1" ControlTypeBeheraPasswordPolicy = "1.3.6.1.4.1.42.2.27.8.5.1"
ControlTypeVChuPasswordMustChange = "2.16.840.1.113730.3.4.4" ControlTypeVChuPasswordMustChange = "2.16.840.1.113730.3.4.4"
ControlTypeVChuPasswordWarning = "2.16.840.1.113730.3.4.5" ControlTypeVChuPasswordWarning = "2.16.840.1.113730.3.4.5"
ControlTypeManageDsaIT = "2.16.840.1.113730.3.4.2"
) )
var ControlTypeMap = map[string]string{ var ControlTypeMap = map[string]string{
ControlTypePaging: "Paging", ControlTypePaging: "Paging",
ControlTypeBeheraPasswordPolicy: "Password Policy - Behera Draft", ControlTypeBeheraPasswordPolicy: "Password Policy - Behera Draft",
ControlTypeManageDsaIT: "Manage DSA IT",
} }
type Control interface { type Control interface {
...@@ -165,6 +167,36 @@ func (c *ControlVChuPasswordWarning) String() string { ...@@ -165,6 +167,36 @@ func (c *ControlVChuPasswordWarning) String() string {
c.Expire) 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 { func FindControl(controls []Control, controlType string) Control {
for _, c := range controls { for _, c := range controls {
if c.GetControlType() == controlType { if c.GetControlType() == controlType {
......
//
// 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
}
...@@ -47,10 +47,10 @@ package ldap ...@@ -47,10 +47,10 @@ package ldap
import ( import (
"bytes" "bytes"
enchex "encoding/hex"
"errors" "errors"
"fmt" "fmt"
"strings" "strings"
enchex "encoding/hex"
ber "gopkg.in/asn1-ber.v1" ber "gopkg.in/asn1-ber.v1"
) )
...@@ -71,7 +71,7 @@ type DN struct { ...@@ -71,7 +71,7 @@ type DN struct {
func ParseDN(str string) (*DN, error) { func ParseDN(str string) (*DN, error) {
dn := new(DN) dn := new(DN)
dn.RDNs = make([]*RelativeDN, 0) dn.RDNs = make([]*RelativeDN, 0)
rdn := new (RelativeDN) rdn := new(RelativeDN)
rdn.Attributes = make([]*AttributeTypeAndValue, 0) rdn.Attributes = make([]*AttributeTypeAndValue, 0)
buffer := bytes.Buffer{} buffer := bytes.Buffer{}
attribute := new(AttributeTypeAndValue) attribute := new(AttributeTypeAndValue)
...@@ -115,7 +115,7 @@ func ParseDN(str string) (*DN, error) { ...@@ -115,7 +115,7 @@ func ParseDN(str string) (*DN, error) {
index := strings.IndexAny(str[i:], ",+") index := strings.IndexAny(str[i:], ",+")
data := str data := str
if index > 0 { if index > 0 {
data = str[i:i+index] data = str[i : i+index]
} else { } else {
data = str[i:] data = str[i:]
} }
...@@ -126,7 +126,7 @@ func ParseDN(str string) (*DN, error) { ...@@ -126,7 +126,7 @@ func ParseDN(str string) (*DN, error) {
} }
packet := ber.DecodePacket(raw_ber) packet := ber.DecodePacket(raw_ber)
buffer.WriteString(packet.Data.String()) buffer.WriteString(packet.Data.String())
i += len(data)-1 i += len(data) - 1
} }
} else if char == ',' || char == '+' { } else if char == ',' || char == '+' {
// We're done with this RDN or value, push it // We're done with this RDN or value, push it
......
package ldap package ldap_test
import ( import (
"reflect" "reflect"
"testing" "testing"
"gopkg.in/ldap.v2"
) )
func TestSuccessfulDNParsing(t *testing.T) { func TestSuccessfulDNParsing(t *testing.T) {
testcases := map[string]DN { testcases := map[string]ldap.DN{
"": DN{[]*RelativeDN{}}, "": ldap.DN{[]*ldap.RelativeDN{}},
"cn=Jim\\2C \\22Hasse Hö\\22 Hansson!,dc=dummy,dc=com": DN{[]*RelativeDN{ "cn=Jim\\2C \\22Hasse Hö\\22 Hansson!,dc=dummy,dc=com": ldap.DN{[]*ldap.RelativeDN{
&RelativeDN{[]*AttributeTypeAndValue{&AttributeTypeAndValue{"cn", "Jim, \"Hasse Hö\" Hansson!"},}}, &ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"cn", "Jim, \"Hasse Hö\" Hansson!"}}},
&RelativeDN{[]*AttributeTypeAndValue{&AttributeTypeAndValue{"dc", "dummy"},}}, &ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"dc", "dummy"}}},
&RelativeDN{[]*AttributeTypeAndValue{&AttributeTypeAndValue{"dc", "com"}, }},}}, &ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"dc", "com"}}}}},
"UID=jsmith,DC=example,DC=net": DN{[]*RelativeDN{ "UID=jsmith,DC=example,DC=net": ldap.DN{[]*ldap.RelativeDN{
&RelativeDN{[]*AttributeTypeAndValue{&AttributeTypeAndValue{"UID", "jsmith"},}}, &ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"UID", "jsmith"}}},
&RelativeDN{[]*AttributeTypeAndValue{&AttributeTypeAndValue{"DC", "example"},}}, &ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"DC", "example"}}},
&RelativeDN{[]*AttributeTypeAndValue{&AttributeTypeAndValue{"DC", "net"}, }},}}, &ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"DC", "net"}}}}},
"OU=Sales+CN=J. Smith,DC=example,DC=net": DN{[]*RelativeDN{ "OU=Sales+CN=J. Smith,DC=example,DC=net": ldap.DN{[]*ldap.RelativeDN{
&RelativeDN{[]*AttributeTypeAndValue{ &ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{
&AttributeTypeAndValue{"OU", "Sales"}, &ldap.AttributeTypeAndValue{"OU", "Sales"},
&AttributeTypeAndValue{"CN", "J. Smith"},}}, &ldap.AttributeTypeAndValue{"CN", "J. Smith"}}},
&RelativeDN{[]*AttributeTypeAndValue{&AttributeTypeAndValue{"DC", "example"},}}, &ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"DC", "example"}}},
&RelativeDN{[]*AttributeTypeAndValue{&AttributeTypeAndValue{"DC", "net"}, }},}}, &ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"DC", "net"}}}}},
"1.3.6.1.4.1.1466.0=#04024869": DN{[]*RelativeDN{ "1.3.6.1.4.1.1466.0=#04024869": ldap.DN{[]*ldap.RelativeDN{
&RelativeDN{[]*AttributeTypeAndValue{&AttributeTypeAndValue{"1.3.6.1.4.1.1466.0", "Hi"},}},}}, &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": DN{[]*RelativeDN{ "1.3.6.1.4.1.1466.0=#04024869,DC=net": ldap.DN{[]*ldap.RelativeDN{
&RelativeDN{[]*AttributeTypeAndValue{&AttributeTypeAndValue{"1.3.6.1.4.1.1466.0", "Hi"},}}, &ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"1.3.6.1.4.1.1466.0", "Hi"}}},
&RelativeDN{[]*AttributeTypeAndValue{&AttributeTypeAndValue{"DC", "net"}, }},}}, &ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"DC", "net"}}}}},
"CN=Lu\\C4\\8Di\\C4\\87": DN{[]*RelativeDN{ "CN=Lu\\C4\\8Di\\C4\\87": ldap.DN{[]*ldap.RelativeDN{
&RelativeDN{[]*AttributeTypeAndValue{&AttributeTypeAndValue{"CN", "Lučić"},}},}}, &ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"CN", "Lučić"}}}}},
} }
for test, answer := range testcases { for test, answer := range testcases {
dn, err := ParseDN(test) dn, err := ldap.ParseDN(test)
if err != nil { if err != nil {
t.Errorf(err.Error()) t.Errorf(err.Error())
continue continue
...@@ -49,7 +51,7 @@ func TestSuccessfulDNParsing(t *testing.T) { ...@@ -49,7 +51,7 @@ func TestSuccessfulDNParsing(t *testing.T) {
} }
func TestErrorDNParsing(t *testing.T) { func TestErrorDNParsing(t *testing.T) {
testcases := map[string]string { testcases := map[string]string{
"*": "DN ended with incomplete type, value pair", "*": "DN ended with incomplete type, value pair",
"cn=Jim\\0Test": "Failed to decode escaped character: encoding/hex: invalid byte: U+0054 'T'", "cn=Jim\\0Test": "Failed to decode escaped character: encoding/hex: invalid byte: U+0054 'T'",
"cn=Jim\\0": "Got corrupted escaped character", "cn=Jim\\0": "Got corrupted escaped character",
...@@ -58,7 +60,7 @@ func TestErrorDNParsing(t *testing.T) { ...@@ -58,7 +60,7 @@ func TestErrorDNParsing(t *testing.T) {
} }
for test, answer := range testcases { for test, answer := range testcases {
_, err := ParseDN(test) _, err := ldap.ParseDN(test)
if err == nil { if err == nil {
t.Errorf("Expected %s to fail parsing but succeeded\n", test) t.Errorf("Expected %s to fail parsing but succeeded\n", test)
} else if err.Error() != answer { } else if err.Error() != answer {
...@@ -66,5 +68,3 @@ func TestErrorDNParsing(t *testing.T) { ...@@ -66,5 +68,3 @@ func TestErrorDNParsing(t *testing.T) {
} }
} }
} }
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
}
...@@ -5,10 +5,10 @@ import ( ...@@ -5,10 +5,10 @@ import (
"fmt" "fmt"
"log" "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 // allowing access to restricted attrabutes that user has access to
func ExampleConn_Bind() { func ExampleConn_Bind() {
l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389)) l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389))
......
...@@ -5,9 +5,12 @@ ...@@ -5,9 +5,12 @@
package ldap package ldap
import ( import (
"bytes"
hexpac "encoding/hex"
"errors" "errors"
"fmt" "fmt"
"strings" "strings"
"unicode/utf8"
"gopkg.in/asn1-ber.v1" "gopkg.in/asn1-ber.v1"
) )
...@@ -50,6 +53,20 @@ var FilterSubstringsMap = map[uint64]string{ ...@@ -50,6 +53,20 @@ var FilterSubstringsMap = map[uint64]string{
FilterSubstringsFinal: "Substrings Final", 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) { func CompileFilter(filter string) (*ber.Packet, error) {
if len(filter) == 0 || filter[0] != '(' { if len(filter) == 0 || filter[0] != '(' {
return nil, NewError(ErrorFilterCompile, errors.New("ldap: filter does not start with an '('")) 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) { ...@@ -108,7 +125,7 @@ func DecompileFilter(packet *ber.Packet) (ret string, err error) {
if i == 0 && child.Tag != FilterSubstringsInitial { if i == 0 && child.Tag != FilterSubstringsInitial {
ret += "*" ret += "*"
} }
ret += ber.DecodeString(child.Data.Bytes()) ret += EscapeFilter(ber.DecodeString(child.Data.Bytes()))
if child.Tag != FilterSubstringsFinal { if child.Tag != FilterSubstringsFinal {
ret += "*" ret += "*"
} }
...@@ -116,22 +133,53 @@ func DecompileFilter(packet *ber.Packet) (ret string, err error) { ...@@ -116,22 +133,53 @@ func DecompileFilter(packet *ber.Packet) (ret string, err error) {
case FilterEqualityMatch: case FilterEqualityMatch:
ret += ber.DecodeString(packet.Children[0].Data.Bytes()) ret += ber.DecodeString(packet.Children[0].Data.Bytes())
ret += "=" ret += "="
ret += ber.DecodeString(packet.Children[1].Data.Bytes()) ret += EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes()))
case FilterGreaterOrEqual: case FilterGreaterOrEqual:
ret += ber.DecodeString(packet.Children[0].Data.Bytes()) ret += ber.DecodeString(packet.Children[0].Data.Bytes())
ret += ">=" ret += ">="
ret += ber.DecodeString(packet.Children[1].Data.Bytes()) ret += EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes()))
case FilterLessOrEqual: case FilterLessOrEqual:
ret += ber.DecodeString(packet.Children[0].Data.Bytes()) ret += ber.DecodeString(packet.Children[0].Data.Bytes())
ret += "<=" ret += "<="
ret += ber.DecodeString(packet.Children[1].Data.Bytes()) ret += EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes()))
case FilterPresent: case FilterPresent:
ret += ber.DecodeString(packet.Data.Bytes()) ret += ber.DecodeString(packet.Data.Bytes())
ret += "=*" ret += "=*"
case FilterApproxMatch: case FilterApproxMatch:
ret += ber.DecodeString(packet.Children[0].Data.Bytes()) ret += ber.DecodeString(packet.Children[0].Data.Bytes())
ret += "~=" 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 += ")" ret += ")"
...@@ -155,58 +203,143 @@ func compileFilterSet(filter string, pos int, parent *ber.Packet) (int, error) { ...@@ -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) { func compileFilter(filter string, pos int) (*ber.Packet, int, error) {
var packet *ber.Packet var (
var err error packet *ber.Packet
err error
)
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
err = NewError(ErrorFilterCompile, errors.New("ldap: error compiling filter")) err = NewError(ErrorFilterCompile, errors.New("ldap: error compiling filter"))
} }
}() }()
newPos := pos 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 '(': case '(':
packet, newPos, err = compileFilter(filter, pos+1) packet, newPos, err = compileFilter(filter, pos+currentWidth)
newPos++ newPos++
return packet, newPos, err return packet, newPos, err
case '&': case '&':
packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterAnd, nil, FilterMap[FilterAnd]) 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 return packet, newPos, err
case '|': case '|':
packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterOr, nil, FilterMap[FilterOr]) 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 return packet, newPos, err
case '!': case '!':
packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterNot, nil, FilterMap[FilterNot]) packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterNot, nil, FilterMap[FilterNot])
var child *ber.Packet var child *ber.Packet
child, newPos, err = compileFilter(filter, pos+1) child, newPos, err = compileFilter(filter, pos+currentWidth)
packet.AppendChild(child) packet.AppendChild(child)
return packet, newPos, err return packet, newPos, err
default: default:
READING_ATTR := 0
READING_EXTENSIBLE_MATCHING_RULE := 1
READING_CONDITION := 2
state := READING_ATTR
attribute := "" attribute := ""
extensibleDNAttributes := false
extensibleMatchingRule := ""
condition := "" condition := ""
for newPos < len(filter) && 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 { switch {
case packet != nil: // Extensible rule, with only DN-matching
condition += fmt.Sprintf("%c", filter[newPos]) case currentRune == ':' && strings.HasPrefix(remainingFilter, ":dn:="):
case filter[newPos] == '=': 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]) packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterEqualityMatch, nil, FilterMap[FilterEqualityMatch])
case filter[newPos] == '>' && filter[newPos+1] == '=': 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]) packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterGreaterOrEqual, nil, FilterMap[FilterGreaterOrEqual])
newPos++ state = READING_CONDITION
case filter[newPos] == '<' && filter[newPos+1] == '=': newPos += 2
// Less-than or equal
case currentRune == '<' && strings.HasPrefix(remainingFilter, "<="):
packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterLessOrEqual, nil, FilterMap[FilterLessOrEqual]) packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterLessOrEqual, nil, FilterMap[FilterLessOrEqual])
newPos++ state = READING_CONDITION
case filter[newPos] == '~' && filter[newPos+1] == '=': newPos += 2
packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterApproxMatch, nil, FilterMap[FilterLessOrEqual])
newPos++ // Approx
case packet == nil: case currentRune == '~' && strings.HasPrefix(remainingFilter, "~="):
attribute += fmt.Sprintf("%c", filter[newPos]) 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
} }
newPos++
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
}
}
if newPos == len(filter) { if newPos == len(filter) {
err = NewError(ErrorFilterCompile, errors.New("ldap: unexpected end of filter")) err = NewError(ErrorFilterCompile, errors.New("ldap: unexpected end of filter"))
return packet, newPos, err return packet, newPos, err
...@@ -217,6 +350,36 @@ func compileFilter(filter string, pos int) (*ber.Packet, int, error) { ...@@ -217,6 +350,36 @@ func compileFilter(filter string, pos int) (*ber.Packet, int, error) {
} }
switch { 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 == "*": case packet.Tag == FilterEqualityMatch && condition == "*":
packet = ber.NewString(ber.ClassContext, ber.TypePrimitive, FilterPresent, attribute, FilterMap[FilterPresent]) packet = ber.NewString(ber.ClassContext, ber.TypePrimitive, FilterPresent, attribute, FilterMap[FilterPresent])
case packet.Tag == FilterEqualityMatch && strings.Contains(condition, "*"): case packet.Tag == FilterEqualityMatch && strings.Contains(condition, "*"):
...@@ -238,15 +401,56 @@ func compileFilter(filter string, pos int) (*ber.Packet, int, error) { ...@@ -238,15 +401,56 @@ func compileFilter(filter string, pos int) (*ber.Packet, int, error) {
default: default:
tag = FilterSubstringsAny 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) packet.AppendChild(seq)
default: 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, 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 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
}
package ldap package ldap_test
import ( import (
"strings"
"testing" "testing"
"gopkg.in/asn1-ber.v1" "gopkg.in/asn1-ber.v1"
"gopkg.in/ldap.v2"
) )
type compileTest struct { type compileTest struct {
filterStr string filterStr string
filterType int
expectedFilter string
expectedType int
expectedErr string
} }
var testFilters = []compileTest{ var testFilters = []compileTest{
compileTest{filterStr: "(&(sn=Miller)(givenName=Bob))", filterType: FilterAnd}, compileTest{
compileTest{filterStr: "(|(sn=Miller)(givenName=Bob))", filterType: FilterOr}, filterStr: "(&(sn=Miller)(givenName=Bob))",
compileTest{filterStr: "(!(sn=Miller))", filterType: FilterNot}, expectedFilter: "(&(sn=Miller)(givenName=Bob))",
compileTest{filterStr: "(sn=Miller)", filterType: FilterEqualityMatch}, expectedType: ldap.FilterAnd,
compileTest{filterStr: "(sn=Mill*)", filterType: FilterSubstrings}, },
compileTest{filterStr: "(sn=*Mill)", filterType: FilterSubstrings}, compileTest{
compileTest{filterStr: "(sn=*Mill*)", filterType: FilterSubstrings}, filterStr: "(|(sn=Miller)(givenName=Bob))",
compileTest{filterStr: "(sn=*i*le*)", filterType: FilterSubstrings}, expectedFilter: "(|(sn=Miller)(givenName=Bob))",
compileTest{filterStr: "(sn=Mi*l*r)", filterType: FilterSubstrings}, expectedType: ldap.FilterOr,
compileTest{filterStr: "(sn=Mi*le*)", filterType: FilterSubstrings}, },
compileTest{filterStr: "(sn=*i*ler)", filterType: FilterSubstrings}, compileTest{
compileTest{filterStr: "(sn>=Miller)", filterType: FilterGreaterOrEqual}, filterStr: "(!(sn=Miller))",
compileTest{filterStr: "(sn<=Miller)", filterType: FilterLessOrEqual}, expectedFilter: "(!(sn=Miller))",
compileTest{filterStr: "(sn=*)", filterType: FilterPresent}, expectedType: ldap.FilterNot,
compileTest{filterStr: "(sn~=Miller)", filterType: FilterApproxMatch}, },
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 }, // compileTest{ filterStr: "()", filterType: FilterExtensibleMatch },
} }
var testInvalidFilters = []string{
`(objectGUID=\zz)`,
`(objectGUID=\a)`,
}
func TestFilter(t *testing.T) { func TestFilter(t *testing.T) {
// Test Compiler and Decompiler // Test Compiler and Decompiler
for _, i := range testFilters { for _, i := range testFilters {
filter, err := CompileFilter(i.filterStr) filter, err := ldap.CompileFilter(i.filterStr)
if err != nil { if err != nil {
t.Errorf("Problem compiling %s - %s", i.filterStr, err.Error()) if i.expectedErr == "" || !strings.Contains(err.Error(), i.expectedErr) {
} else if filter.Tag != ber.Tag(i.filterType) { t.Errorf("Problem compiling '%s' - '%v' (expected error to contain '%v')", i.filterStr, err, i.expectedErr)
t.Errorf("%q Expected %q got %q", i.filterStr, FilterMap[uint64(i.filterType)], FilterMap[uint64(filter.Tag)]) }
} 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 { } else {
o, err := DecompileFilter(filter) o, err := ldap.DecompileFilter(filter)
if err != nil { if err != nil {
t.Errorf("Problem compiling %s - %s", i.filterStr, err.Error()) t.Errorf("Problem compiling %s - %s", i.filterStr, err.Error())
} else if i.filterStr != o { } else if i.expectedFilter != o {
t.Errorf("%q expected, got %q", i.filterStr, 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)
} }
} }
} }
...@@ -61,7 +227,7 @@ func BenchmarkFilterCompile(b *testing.B) { ...@@ -61,7 +227,7 @@ func BenchmarkFilterCompile(b *testing.B) {
maxIdx := len(filters) maxIdx := len(filters)
b.StartTimer() b.StartTimer()
for i := 0; i < b.N; i++ { 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) { ...@@ -71,12 +237,12 @@ func BenchmarkFilterDecompile(b *testing.B) {
// Test Compiler and Decompiler // Test Compiler and Decompiler
for idx, i := range testFilters { for idx, i := range testFilters {
filters[idx], _ = CompileFilter(i.filterStr) filters[idx], _ = ldap.CompileFilter(i.filterStr)
} }
maxIdx := len(filters) maxIdx := len(filters)
b.StartTimer() b.StartTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
DecompileFilter(filters[i%maxIdx]) ldap.DecompileFilter(filters[i%maxIdx])
} }
} }
...@@ -6,7 +6,6 @@ package ldap ...@@ -6,7 +6,6 @@ package ldap
import ( import (
"errors" "errors"
"fmt"
"io/ioutil" "io/ioutil"
"os" "os"
...@@ -60,98 +59,6 @@ var ApplicationMap = map[uint8]string{ ...@@ -60,98 +59,6 @@ var ApplicationMap = map[uint8]string{
ApplicationExtendedResponse: "Extended Response", 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) // Ldap Behera Password Policy Draft 10 (https://tools.ietf.org/html/draft-behera-ldap-password-policy-10)
const ( const (
BeheraPasswordExpired = 0 BeheraPasswordExpired = 0
...@@ -318,8 +225,8 @@ func addRequestDescriptions(packet *ber.Packet) { ...@@ -318,8 +225,8 @@ func addRequestDescriptions(packet *ber.Packet) {
} }
func addDefaultLDAPResponseDescriptions(packet *ber.Packet) { func addDefaultLDAPResponseDescriptions(packet *ber.Packet) {
resultCode := packet.Children[1].Children[0].Value.(int64) resultCode, _ := getLDAPResultCode(packet)
packet.Children[1].Children[0].Description = "Result Code (" + LDAPResultCodeMap[uint8(resultCode)] + ")" packet.Children[1].Children[0].Description = "Result Code (" + LDAPResultCodeMap[resultCode] + ")"
packet.Children[1].Children[1].Description = "Matched DN" packet.Children[1].Children[1].Description = "Matched DN"
packet.Children[1].Children[2].Description = "Error Message" packet.Children[1].Children[2].Description = "Error Message"
if len(packet.Children[1].Children) > 3 { if len(packet.Children[1].Children) > 3 {
...@@ -343,30 +250,6 @@ func DebugBinaryFile(fileName string) error { ...@@ -343,30 +250,6 @@ func DebugBinaryFile(fileName string) error {
return nil 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" var hex = "0123456789abcdef"
func mustEscape(c byte) bool { func mustEscape(c byte) bool {
......
package ldap package ldap_test
import ( import (
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"testing" "testing"
"gopkg.in/ldap.v2"
) )
var ldapServer = "ldap.itd.umich.edu" var ldapServer = "ldap.itd.umich.edu"
...@@ -21,7 +23,7 @@ var attributes = []string{ ...@@ -21,7 +23,7 @@ var attributes = []string{
func TestDial(t *testing.T) { func TestDial(t *testing.T) {
fmt.Printf("TestDial: starting...\n") 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 { if err != nil {
t.Errorf(err.Error()) t.Errorf(err.Error())
return return
...@@ -32,7 +34,7 @@ func TestDial(t *testing.T) { ...@@ -32,7 +34,7 @@ func TestDial(t *testing.T) {
func TestDialTLS(t *testing.T) { func TestDialTLS(t *testing.T) {
fmt.Printf("TestDialTLS: starting...\n") 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 { if err != nil {
t.Errorf(err.Error()) t.Errorf(err.Error())
return return
...@@ -43,7 +45,7 @@ func TestDialTLS(t *testing.T) { ...@@ -43,7 +45,7 @@ func TestDialTLS(t *testing.T) {
func TestStartTLS(t *testing.T) { func TestStartTLS(t *testing.T) {
fmt.Printf("TestStartTLS: starting...\n") 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 { if err != nil {
t.Errorf(err.Error()) t.Errorf(err.Error())
return return
...@@ -58,16 +60,16 @@ func TestStartTLS(t *testing.T) { ...@@ -58,16 +60,16 @@ func TestStartTLS(t *testing.T) {
func TestSearch(t *testing.T) { func TestSearch(t *testing.T) {
fmt.Printf("TestSearch: starting...\n") 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 { if err != nil {
t.Errorf(err.Error()) t.Errorf(err.Error())
return return
} }
defer l.Close() defer l.Close()
searchRequest := NewSearchRequest( searchRequest := ldap.NewSearchRequest(
baseDN, baseDN,
ScopeWholeSubtree, DerefAlways, 0, 0, false, ldap.ScopeWholeSubtree, ldap.DerefAlways, 0, 0, false,
filter[0], filter[0],
attributes, attributes,
nil) nil)
...@@ -83,16 +85,16 @@ func TestSearch(t *testing.T) { ...@@ -83,16 +85,16 @@ func TestSearch(t *testing.T) {
func TestSearchStartTLS(t *testing.T) { func TestSearchStartTLS(t *testing.T) {
fmt.Printf("TestSearchStartTLS: starting...\n") 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 { if err != nil {
t.Errorf(err.Error()) t.Errorf(err.Error())
return return
} }
defer l.Close() defer l.Close()
searchRequest := NewSearchRequest( searchRequest := ldap.NewSearchRequest(
baseDN, baseDN,
ScopeWholeSubtree, DerefAlways, 0, 0, false, ldap.ScopeWholeSubtree, ldap.DerefAlways, 0, 0, false,
filter[0], filter[0],
attributes, attributes,
nil) nil)
...@@ -123,7 +125,7 @@ func TestSearchStartTLS(t *testing.T) { ...@@ -123,7 +125,7 @@ func TestSearchStartTLS(t *testing.T) {
func TestSearchWithPaging(t *testing.T) { func TestSearchWithPaging(t *testing.T) {
fmt.Printf("TestSearchWithPaging: starting...\n") 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 { if err != nil {
t.Errorf(err.Error()) t.Errorf(err.Error())
return return
...@@ -136,9 +138,9 @@ func TestSearchWithPaging(t *testing.T) { ...@@ -136,9 +138,9 @@ func TestSearchWithPaging(t *testing.T) {
return return
} }
searchRequest := NewSearchRequest( searchRequest := ldap.NewSearchRequest(
baseDN, baseDN,
ScopeWholeSubtree, DerefAlways, 0, 0, false, ldap.ScopeWholeSubtree, ldap.DerefAlways, 0, 0, false,
filter[2], filter[2],
attributes, attributes,
nil) nil)
...@@ -149,12 +151,38 @@ func TestSearchWithPaging(t *testing.T) { ...@@ -149,12 +151,38 @@ func TestSearchWithPaging(t *testing.T) {
} }
fmt.Printf("TestSearchWithPaging: %s -> num of entries = %d\n", searchRequest.Filter, len(sr.Entries)) 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) { func searchGoroutine(t *testing.T, l *ldap.Conn, results chan *ldap.SearchResult, i int) {
searchRequest := NewSearchRequest( searchRequest := ldap.NewSearchRequest(
baseDN, baseDN,
ScopeWholeSubtree, DerefAlways, 0, 0, false, ldap.ScopeWholeSubtree, ldap.DerefAlways, 0, 0, false,
filter[i], filter[i],
attributes, attributes,
nil) nil)
...@@ -169,17 +197,17 @@ func searchGoroutine(t *testing.T, l *Conn, results chan *SearchResult, i int) { ...@@ -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) { func testMultiGoroutineSearch(t *testing.T, TLS bool, startTLS bool) {
fmt.Printf("TestMultiGoroutineSearch: starting...\n") fmt.Printf("TestMultiGoroutineSearch: starting...\n")
var l *Conn var l *ldap.Conn
var err error var err error
if TLS { 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 { if err != nil {
t.Errorf(err.Error()) t.Errorf(err.Error())
return return
} }
defer l.Close() defer l.Close()
} else { } 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 { if err != nil {
t.Errorf(err.Error()) t.Errorf(err.Error())
return return
...@@ -195,9 +223,9 @@ func testMultiGoroutineSearch(t *testing.T, TLS bool, startTLS bool) { ...@@ -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 { for i := range filter {
results[i] = make(chan *SearchResult) results[i] = make(chan *ldap.SearchResult)
go searchGoroutine(t, l, results[i], i) go searchGoroutine(t, l, results[i], i)
} }
for i := range filter { for i := range filter {
...@@ -217,17 +245,17 @@ func TestMultiGoroutineSearch(t *testing.T) { ...@@ -217,17 +245,17 @@ func TestMultiGoroutineSearch(t *testing.T) {
} }
func TestEscapeFilter(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) 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) t.Errorf("Got %s, expected %s", want, got)
} }
} }
func TestCompare(t *testing.T) { func TestCompare(t *testing.T) {
fmt.Printf("TestCompare: starting...\n") 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 { if err != nil {
t.Fatal(err.Error()) t.Fatal(err.Error())
} }
...@@ -243,5 +271,5 @@ func TestCompare(t *testing.T) { ...@@ -243,5 +271,5 @@ func TestCompare(t *testing.T) {
return return
} }
fmt.Printf("TestCompare: -> num of entries = %d\n", sr) fmt.Printf("TestCompare: -> %v\n", sr)
} }
...@@ -62,6 +62,7 @@ package ldap ...@@ -62,6 +62,7 @@ package ldap
import ( import (
"errors" "errors"
"fmt" "fmt"
"sort"
"strings" "strings"
"gopkg.in/asn1-ber.v1" "gopkg.in/asn1-ber.v1"
...@@ -93,6 +94,26 @@ var DerefMap = map[int]string{ ...@@ -93,6 +94,26 @@ var DerefMap = map[int]string{
DerefAlways: "DerefAlways", 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 { type Entry struct {
DN string DN string
Attributes []*EntryAttribute Attributes []*EntryAttribute
...@@ -146,6 +167,19 @@ func (e *Entry) PrettyPrint(indent int) { ...@@ -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 { type EntryAttribute struct {
Name string Name string
Values []string Values []string
...@@ -234,13 +268,32 @@ func NewSearchRequest( ...@@ -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) { func (l *Conn) SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) (*SearchResult, error) {
if searchRequest.Controls == nil { var pagingControl *ControlPaging
searchRequest.Controls = make([]Control, 0)
}
pagingControl := NewControlPaging(pagingSize) control := FindControl(searchRequest.Controls, ControlTypePaging)
if control == nil {
pagingControl = NewControlPaging(pagingSize)
searchRequest.Controls = append(searchRequest.Controls, pagingControl) 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
}
searchResult := new(SearchResult) searchResult := new(SearchResult)
for { for {
result, err := l.Search(searchRequest) result, err := l.Search(searchRequest)
......
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
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment