545 lines
17 KiB
Go
545 lines
17 KiB
Go
package ldap
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
ber "github.com/go-asn1-ber/asn1-ber"
|
|
)
|
|
|
|
// scope choices
|
|
const (
|
|
ScopeBaseObject = 0
|
|
ScopeSingleLevel = 1
|
|
ScopeWholeSubtree = 2
|
|
)
|
|
|
|
// ScopeMap contains human readable descriptions of scope choices
|
|
var ScopeMap = map[int]string{
|
|
ScopeBaseObject: "Base Object",
|
|
ScopeSingleLevel: "Single Level",
|
|
ScopeWholeSubtree: "Whole Subtree",
|
|
}
|
|
|
|
// derefAliases
|
|
const (
|
|
NeverDerefAliases = 0
|
|
DerefInSearching = 1
|
|
DerefFindingBaseObj = 2
|
|
DerefAlways = 3
|
|
)
|
|
|
|
// DerefMap contains human readable descriptions of derefAliases choices
|
|
var DerefMap = map[int]string{
|
|
NeverDerefAliases: "NeverDerefAliases",
|
|
DerefInSearching: "DerefInSearching",
|
|
DerefFindingBaseObj: "DerefFindingBaseObj",
|
|
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,
|
|
}
|
|
}
|
|
|
|
// Entry represents a single search result entry
|
|
type Entry struct {
|
|
// DN is the distinguished name of the entry
|
|
DN string
|
|
// Attributes are the returned attributes for the entry
|
|
Attributes []*EntryAttribute
|
|
}
|
|
|
|
// GetAttributeValues returns the values for the named attribute, or an empty list
|
|
func (e *Entry) GetAttributeValues(attribute string) []string {
|
|
for _, attr := range e.Attributes {
|
|
if attr.Name == attribute {
|
|
return attr.Values
|
|
}
|
|
}
|
|
return []string{}
|
|
}
|
|
|
|
// GetEqualFoldAttributeValues returns the values for the named attribute, or an
|
|
// empty list. Attribute matching is done with strings.EqualFold.
|
|
func (e *Entry) GetEqualFoldAttributeValues(attribute string) []string {
|
|
for _, attr := range e.Attributes {
|
|
if strings.EqualFold(attribute, attr.Name) {
|
|
return attr.Values
|
|
}
|
|
}
|
|
return []string{}
|
|
}
|
|
|
|
// GetRawAttributeValues returns the byte values for the named attribute, or an empty list
|
|
func (e *Entry) GetRawAttributeValues(attribute string) [][]byte {
|
|
for _, attr := range e.Attributes {
|
|
if attr.Name == attribute {
|
|
return attr.ByteValues
|
|
}
|
|
}
|
|
return [][]byte{}
|
|
}
|
|
|
|
// GetEqualFoldRawAttributeValues returns the byte values for the named attribute, or an empty list
|
|
func (e *Entry) GetEqualFoldRawAttributeValues(attribute string) [][]byte {
|
|
for _, attr := range e.Attributes {
|
|
if strings.EqualFold(attr.Name, attribute) {
|
|
return attr.ByteValues
|
|
}
|
|
}
|
|
return [][]byte{}
|
|
}
|
|
|
|
// GetAttributeValue returns the first value for the named attribute, or ""
|
|
func (e *Entry) GetAttributeValue(attribute string) string {
|
|
values := e.GetAttributeValues(attribute)
|
|
if len(values) == 0 {
|
|
return ""
|
|
}
|
|
return values[0]
|
|
}
|
|
|
|
// GetEqualFoldAttributeValue returns the first value for the named attribute, or "".
|
|
// Attribute comparison is done with strings.EqualFold.
|
|
func (e *Entry) GetEqualFoldAttributeValue(attribute string) string {
|
|
values := e.GetEqualFoldAttributeValues(attribute)
|
|
if len(values) == 0 {
|
|
return ""
|
|
}
|
|
return values[0]
|
|
}
|
|
|
|
// GetRawAttributeValue returns the first value for the named attribute, or an empty slice
|
|
func (e *Entry) GetRawAttributeValue(attribute string) []byte {
|
|
values := e.GetRawAttributeValues(attribute)
|
|
if len(values) == 0 {
|
|
return []byte{}
|
|
}
|
|
return values[0]
|
|
}
|
|
|
|
// GetEqualFoldRawAttributeValue returns the first value for the named attribute, or an empty slice
|
|
func (e *Entry) GetEqualFoldRawAttributeValue(attribute string) []byte {
|
|
values := e.GetEqualFoldRawAttributeValues(attribute)
|
|
if len(values) == 0 {
|
|
return []byte{}
|
|
}
|
|
return values[0]
|
|
}
|
|
|
|
// Print outputs a human-readable description
|
|
func (e *Entry) Print() {
|
|
fmt.Printf("DN: %s\n", e.DN)
|
|
for _, attr := range e.Attributes {
|
|
attr.Print()
|
|
}
|
|
}
|
|
|
|
// PrettyPrint outputs a human-readable description indenting
|
|
func (e *Entry) PrettyPrint(indent int) {
|
|
fmt.Printf("%sDN: %s\n", strings.Repeat(" ", indent), e.DN)
|
|
for _, attr := range e.Attributes {
|
|
attr.PrettyPrint(indent + 2)
|
|
}
|
|
}
|
|
|
|
// Describe the tag to use for struct field tags
|
|
const decoderTagName = "ldap"
|
|
|
|
// readTag will read the reflect.StructField value for
|
|
// the key defined in decoderTagName. If omitempty is
|
|
// specified, the field may not be filled.
|
|
func readTag(f reflect.StructField) (string, bool) {
|
|
val, ok := f.Tag.Lookup(decoderTagName)
|
|
if !ok {
|
|
return f.Name, false
|
|
}
|
|
opts := strings.Split(val, ",")
|
|
omit := false
|
|
if len(opts) == 2 {
|
|
omit = opts[1] == "omitempty"
|
|
}
|
|
return opts[0], omit
|
|
}
|
|
|
|
// Unmarshal parses the Entry in the value pointed to by i
|
|
//
|
|
// Currently, this methods only supports struct fields of type
|
|
// string, []string, int, int64 or []byte. Other field types will not be
|
|
// regarded. If the field type is a string or int but multiple attribute
|
|
// values are returned, the first value will be used to fill the field.
|
|
//
|
|
// Example:
|
|
// type UserEntry struct {
|
|
// // Fields with the tag key `dn` are automatically filled with the
|
|
// // objects distinguishedName. This can be used multiple times.
|
|
// DN string `ldap:"dn"`
|
|
//
|
|
// // This field will be filled with the attribute value for
|
|
// // userPrincipalName. An attribute can be read into a struct field
|
|
// // multiple times. Missing attributes will not result in an error.
|
|
// UserPrincipalName string `ldap:"userPrincipalName"`
|
|
//
|
|
// // memberOf may have multiple values. If you don't
|
|
// // know the amount of attribute values at runtime, use a string array.
|
|
// MemberOf []string `ldap:"memberOf"`
|
|
//
|
|
// // ID is an integer value, it will fail unmarshaling when the given
|
|
// // attribute value cannot be parsed into an integer.
|
|
// ID int `ldap:"id"`
|
|
//
|
|
// // LongID is similar to ID but uses an int64 instead.
|
|
// LongID int64 `ldap:"longId"`
|
|
//
|
|
// // Data is similar to MemberOf a slice containing all attribute
|
|
// // values.
|
|
// Data []byte `ldap:"data"`
|
|
//
|
|
// // This won't work, as the field is not of type string. For this
|
|
// // to work, you'll have to temporarily store the result in string
|
|
// // (or string array) and convert it to the desired type afterwards.
|
|
// UserAccountControl uint32 `ldap:"userPrincipalName"`
|
|
// }
|
|
// user := UserEntry{}
|
|
// if err := result.Unmarshal(&user); err != nil {
|
|
// // ...
|
|
// }
|
|
func (e *Entry) Unmarshal(i interface{}) (err error) {
|
|
// Make sure it's a ptr
|
|
if vo := reflect.ValueOf(i).Kind(); vo != reflect.Ptr {
|
|
return fmt.Errorf("ldap: cannot use %s, expected pointer to a struct", vo)
|
|
}
|
|
|
|
sv, st := reflect.ValueOf(i).Elem(), reflect.TypeOf(i).Elem()
|
|
// Make sure it's pointing to a struct
|
|
if sv.Kind() != reflect.Struct {
|
|
return fmt.Errorf("ldap: expected pointer to a struct, got %s", sv.Kind())
|
|
}
|
|
|
|
for n := 0; n < st.NumField(); n++ {
|
|
// Holds struct field value and type
|
|
fv, ft := sv.Field(n), st.Field(n)
|
|
|
|
// skip unexported fields
|
|
if ft.PkgPath != "" {
|
|
continue
|
|
}
|
|
|
|
// omitempty can be safely discarded, as it's not needed when unmarshalling
|
|
fieldTag, _ := readTag(ft)
|
|
|
|
// Fill the field with the distinguishedName if the tag key is `dn`
|
|
if fieldTag == "dn" {
|
|
fv.SetString(e.DN)
|
|
continue
|
|
}
|
|
|
|
values := e.GetAttributeValues(fieldTag)
|
|
if len(values) == 0 {
|
|
continue
|
|
}
|
|
|
|
switch fv.Interface().(type) {
|
|
case []string:
|
|
for _, item := range values {
|
|
fv.Set(reflect.Append(fv, reflect.ValueOf(item)))
|
|
}
|
|
case string:
|
|
fv.SetString(values[0])
|
|
case []byte:
|
|
fv.SetBytes([]byte(values[0]))
|
|
case int, int64:
|
|
intVal, err := strconv.ParseInt(values[0], 10, 64)
|
|
if err != nil {
|
|
return fmt.Errorf("ldap: could not parse value '%s' into int field", values[0])
|
|
}
|
|
fv.SetInt(intVal)
|
|
default:
|
|
return fmt.Errorf("ldap: expected field to be of type string, []string, int, int64 or []byte, got %v", ft.Type)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// 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,
|
|
}
|
|
}
|
|
|
|
// EntryAttribute holds a single attribute
|
|
type EntryAttribute struct {
|
|
// Name is the name of the attribute
|
|
Name string
|
|
// Values contain the string values of the attribute
|
|
Values []string
|
|
// ByteValues contain the raw values of the attribute
|
|
ByteValues [][]byte
|
|
}
|
|
|
|
// Print outputs a human-readable description
|
|
func (e *EntryAttribute) Print() {
|
|
fmt.Printf("%s: %s\n", e.Name, e.Values)
|
|
}
|
|
|
|
// PrettyPrint outputs a human-readable description with indenting
|
|
func (e *EntryAttribute) PrettyPrint(indent int) {
|
|
fmt.Printf("%s%s: %s\n", strings.Repeat(" ", indent), e.Name, e.Values)
|
|
}
|
|
|
|
// SearchResult holds the server's response to a search request
|
|
type SearchResult struct {
|
|
// Entries are the returned entries
|
|
Entries []*Entry
|
|
// Referrals are the returned referrals
|
|
Referrals []string
|
|
// Controls are the returned controls
|
|
Controls []Control
|
|
}
|
|
|
|
// Print outputs a human-readable description
|
|
func (s *SearchResult) Print() {
|
|
for _, entry := range s.Entries {
|
|
entry.Print()
|
|
}
|
|
}
|
|
|
|
// PrettyPrint outputs a human-readable description with indenting
|
|
func (s *SearchResult) PrettyPrint(indent int) {
|
|
for _, entry := range s.Entries {
|
|
entry.PrettyPrint(indent)
|
|
}
|
|
}
|
|
|
|
// SearchRequest represents a search request to send to the server
|
|
type SearchRequest struct {
|
|
BaseDN string
|
|
Scope int
|
|
DerefAliases int
|
|
SizeLimit int
|
|
TimeLimit int
|
|
TypesOnly bool
|
|
Filter string
|
|
Attributes []string
|
|
Controls []Control
|
|
}
|
|
|
|
func (req *SearchRequest) appendTo(envelope *ber.Packet) error {
|
|
pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationSearchRequest, nil, "Search Request")
|
|
pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, req.BaseDN, "Base DN"))
|
|
pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(req.Scope), "Scope"))
|
|
pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(req.DerefAliases), "Deref Aliases"))
|
|
pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, uint64(req.SizeLimit), "Size Limit"))
|
|
pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, uint64(req.TimeLimit), "Time Limit"))
|
|
pkt.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, req.TypesOnly, "Types Only"))
|
|
// compile and encode filter
|
|
filterPacket, err := CompileFilter(req.Filter)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pkt.AppendChild(filterPacket)
|
|
// encode attributes
|
|
attributesPacket := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Attributes")
|
|
for _, attribute := range req.Attributes {
|
|
attributesPacket.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute, "Attribute"))
|
|
}
|
|
pkt.AppendChild(attributesPacket)
|
|
|
|
envelope.AppendChild(pkt)
|
|
if len(req.Controls) > 0 {
|
|
envelope.AppendChild(encodeControls(req.Controls))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// NewSearchRequest creates a new search request
|
|
func NewSearchRequest(
|
|
BaseDN string,
|
|
Scope, DerefAliases, SizeLimit, TimeLimit int,
|
|
TypesOnly bool,
|
|
Filter string,
|
|
Attributes []string,
|
|
Controls []Control,
|
|
) *SearchRequest {
|
|
return &SearchRequest{
|
|
BaseDN: BaseDN,
|
|
Scope: Scope,
|
|
DerefAliases: DerefAliases,
|
|
SizeLimit: SizeLimit,
|
|
TimeLimit: TimeLimit,
|
|
TypesOnly: TypesOnly,
|
|
Filter: Filter,
|
|
Attributes: Attributes,
|
|
Controls: Controls,
|
|
}
|
|
}
|
|
|
|
// 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) {
|
|
var pagingControl *ControlPaging
|
|
|
|
control := FindControl(searchRequest.Controls, ControlTypePaging)
|
|
if control == nil {
|
|
pagingControl = NewControlPaging(pagingSize)
|
|
searchRequest.Controls = append(searchRequest.Controls, pagingControl)
|
|
} else {
|
|
castControl, ok := control.(*ControlPaging)
|
|
if !ok {
|
|
return nil, fmt.Errorf("expected paging control to be of type *ControlPaging, got %v", control)
|
|
}
|
|
if castControl.PagingSize != pagingSize {
|
|
return nil, fmt.Errorf("paging size given in search request (%d) conflicts with size given in search call (%d)", castControl.PagingSize, pagingSize)
|
|
}
|
|
pagingControl = castControl
|
|
}
|
|
|
|
searchResult := new(SearchResult)
|
|
for {
|
|
result, err := l.Search(searchRequest)
|
|
l.Debug.Printf("Looking for Paging Control...")
|
|
if err != nil {
|
|
return searchResult, err
|
|
}
|
|
if result == nil {
|
|
return searchResult, NewError(ErrorNetwork, errors.New("ldap: packet not received"))
|
|
}
|
|
|
|
searchResult.Entries = append(searchResult.Entries, result.Entries...)
|
|
searchResult.Referrals = append(searchResult.Referrals, result.Referrals...)
|
|
searchResult.Controls = append(searchResult.Controls, result.Controls...)
|
|
|
|
l.Debug.Printf("Looking for Paging Control...")
|
|
pagingResult := FindControl(result.Controls, ControlTypePaging)
|
|
if pagingResult == nil {
|
|
pagingControl = nil
|
|
l.Debug.Printf("Could not find paging control. Breaking...")
|
|
break
|
|
}
|
|
|
|
cookie := pagingResult.(*ControlPaging).Cookie
|
|
if len(cookie) == 0 {
|
|
pagingControl = nil
|
|
l.Debug.Printf("Could not find cookie. Breaking...")
|
|
break
|
|
}
|
|
pagingControl.SetCookie(cookie)
|
|
}
|
|
|
|
if pagingControl != nil {
|
|
l.Debug.Printf("Abandoning Paging...")
|
|
pagingControl.PagingSize = 0
|
|
if _, err := l.Search(searchRequest); err != nil {
|
|
return searchResult, err
|
|
}
|
|
}
|
|
|
|
return searchResult, nil
|
|
}
|
|
|
|
// Search performs the given search request
|
|
func (l *Conn) Search(searchRequest *SearchRequest) (*SearchResult, error) {
|
|
msgCtx, err := l.doRequest(searchRequest)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer l.finishMessage(msgCtx)
|
|
|
|
result := &SearchResult{
|
|
Entries: make([]*Entry, 0),
|
|
Referrals: make([]string, 0),
|
|
Controls: make([]Control, 0),
|
|
}
|
|
|
|
for {
|
|
packet, err := l.readPacket(msgCtx)
|
|
if err != nil {
|
|
return result, err
|
|
}
|
|
|
|
switch packet.Children[1].Tag {
|
|
case 4:
|
|
entry := &Entry{
|
|
DN: packet.Children[1].Children[0].Value.(string),
|
|
Attributes: unpackAttributes(packet.Children[1].Children[1].Children),
|
|
}
|
|
result.Entries = append(result.Entries, entry)
|
|
case 5:
|
|
err := GetLDAPError(packet)
|
|
if err != nil {
|
|
return result, err
|
|
}
|
|
if len(packet.Children) == 3 {
|
|
for _, child := range packet.Children[2].Children {
|
|
decodedChild, err := DecodeControl(child)
|
|
if err != nil {
|
|
return result, fmt.Errorf("failed to decode child control: %s", err)
|
|
}
|
|
result.Controls = append(result.Controls, decodedChild)
|
|
}
|
|
}
|
|
return result, nil
|
|
case 19:
|
|
result.Referrals = append(result.Referrals, packet.Children[1].Children[0].Value.(string))
|
|
}
|
|
}
|
|
}
|
|
|
|
// unpackAttributes will extract all given LDAP attributes and it's values
|
|
// from the ber.Packet
|
|
func unpackAttributes(children []*ber.Packet) []*EntryAttribute {
|
|
entries := make([]*EntryAttribute, len(children))
|
|
for i, child := range children {
|
|
length := len(child.Children[1].Children)
|
|
entry := &EntryAttribute{
|
|
Name: child.Children[0].Value.(string),
|
|
// pre-allocate the slice since we can determine
|
|
// the number of attributes at this point
|
|
Values: make([]string, length),
|
|
ByteValues: make([][]byte, length),
|
|
}
|
|
|
|
for i, value := range child.Children[1].Children {
|
|
entry.ByteValues[i] = value.ByteValue
|
|
entry.Values[i] = value.Value.(string)
|
|
}
|
|
entries[i] = entry
|
|
}
|
|
|
|
return entries
|
|
}
|