Commit f27ce7e1 by Torkel Ödegaard

Merge pull request #4461 from dfwarden/ldap_nested_groups

LDAP Nested Groups
parents 91c5515d 87aca5bf
...@@ -160,8 +160,8 @@ ...@@ -160,8 +160,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))
......
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
}
}
...@@ -28,8 +28,31 @@ search_base_dns = ["dc=grafana,dc=org"] ...@@ -28,8 +28,31 @@ search_base_dns = ["dc=grafana,dc=org"]
# This is done by enabling group_search_filter below. You must also set member_of= "cn" # This is done by enabling group_search_filter below. You must also set member_of= "cn"
# in [servers.attributes] below. # in [servers.attributes] below.
# Users with nested/recursive group membership and an LDAP server that supports LDAP_MATCHING_RULE_IN_CHAIN
# can set group_search_filter, group_search_filter_user_attribute, group_search_base_dns and member_of
# below in such a way that the user's recursive group membership is considered.
#
# Nested Groups + Active Directory (AD) Example:
#
# AD groups store the Distinguished Names (DNs) of members, so your filter must
# recursively search your groups for the authenticating user's DN. For example:
#
# group_search_filter = "(member:1.2.840.113556.1.4.1941:=%s)"
# group_search_filter_user_attribute = "distinguishedName"
# group_search_base_dns = ["ou=groups,dc=grafana,dc=org"]
#
# [servers.attributes]
# ...
# member_of = "distinguishedName"
## Group search filter, to retrieve the groups of which the user is a member (only set if memberOf attribute is not available) ## Group search filter, to retrieve the groups of which the user is a member (only set if memberOf attribute is not available)
# group_search_filter = "(&(objectClass=posixGroup)(memberUid=%s))" # group_search_filter = "(&(objectClass=posixGroup)(memberUid=%s))"
## Group search filter user attribute defines what user attribute gets substituted for %s in group_search_filter.
## Defaults to the value of username in [server.attributes]
## Valid options are any of your values in [servers.attributes]
## If you are using nested groups you probably want to set this and member_of in
## [servers.attributes] to "distinguishedName"
# group_search_filter_user_attribute = "distinguishedName"
## An array of the base DNs to search through for groups. Typically uses ou=groups ## An array of the base DNs to search through for groups. Typically uses ou=groups
# group_search_base_dns = ["ou=groups,dc=grafana,dc=org"] # group_search_base_dns = ["ou=groups,dc=grafana,dc=org"]
......
...@@ -318,7 +318,12 @@ func (a *ldapAuther) searchForUser(username string) (*ldapUserInfo, error) { ...@@ -318,7 +318,12 @@ func (a *ldapAuther) searchForUser(username string) (*ldapUserInfo, error) {
// If we are using a POSIX LDAP schema it won't support memberOf, so we manually search the groups // If we are using a POSIX LDAP schema it won't support memberOf, so we manually search the groups
var groupSearchResult *ldap.SearchResult var groupSearchResult *ldap.SearchResult
for _, groupSearchBase := range a.server.GroupSearchBaseDNs { for _, groupSearchBase := range a.server.GroupSearchBaseDNs {
filter := strings.Replace(a.server.GroupSearchFilter, "%s", username, -1) var filter_replace string
filter_replace = getLdapAttr(a.server.GroupSearchFilterUserAttribute, searchResult)
if a.server.GroupSearchFilterUserAttribute == "" {
filter_replace = getLdapAttr(a.server.Attr.Username, searchResult)
}
filter := strings.Replace(a.server.GroupSearchFilter, "%s", filter_replace, -1)
if ldapCfg.VerboseLogging { if ldapCfg.VerboseLogging {
log.Info("LDAP: Searching for user's groups: %s", filter) log.Info("LDAP: Searching for user's groups: %s", filter)
......
...@@ -28,6 +28,7 @@ type LdapServerConf struct { ...@@ -28,6 +28,7 @@ type LdapServerConf struct {
SearchBaseDNs []string `toml:"search_base_dns"` SearchBaseDNs []string `toml:"search_base_dns"`
GroupSearchFilter string `toml:"group_search_filter"` GroupSearchFilter string `toml:"group_search_filter"`
GroupSearchFilterUserAttribute string `toml:"group_search_filter_user_attribute"`
GroupSearchBaseDNs []string `toml:"group_search_base_dns"` GroupSearchBaseDNs []string `toml:"group_search_base_dns"`
LdapGroups []*LdapGroupToOrgRole `toml:"group_mappings"` LdapGroups []*LdapGroupToOrgRole `toml:"group_mappings"`
......
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