2020-08-09 15:29:54 -07:00
// File contains Search functionality
//
// https://tools.ietf.org/html/rfc4511
//
// SearchRequest ::= [APPLICATION 3] SEQUENCE {
// baseObject LDAPDN,
// scope ENUMERATED {
// baseObject (0),
// singleLevel (1),
// wholeSubtree (2),
// ... },
// derefAliases ENUMERATED {
// neverDerefAliases (0),
// derefInSearching (1),
// derefFindingBaseObj (2),
// derefAlways (3) },
// sizeLimit INTEGER (0 .. maxInt),
// timeLimit INTEGER (0 .. maxInt),
// typesOnly BOOLEAN,
// filter Filter,
// attributes AttributeSelection }
//
// AttributeSelection ::= SEQUENCE OF selector LDAPString
// -- The LDAPString is constrained to
// -- <attributeSelector> in Section 4.5.1.8
//
// Filter ::= CHOICE {
// and [0] SET SIZE (1..MAX) OF filter Filter,
// or [1] SET SIZE (1..MAX) OF filter Filter,
// not [2] Filter,
// equalityMatch [3] AttributeValueAssertion,
// substrings [4] SubstringFilter,
// greaterOrEqual [5] AttributeValueAssertion,
// lessOrEqual [6] AttributeValueAssertion,
// present [7] AttributeDescription,
// approxMatch [8] AttributeValueAssertion,
// extensibleMatch [9] MatchingRuleAssertion,
// ... }
//
// SubstringFilter ::= SEQUENCE {
// type AttributeDescription,
// substrings SEQUENCE SIZE (1..MAX) OF substring CHOICE {
// initial [0] AssertionValue, -- can occur at most once
// any [1] AssertionValue,
// final [2] AssertionValue } -- can occur at most once
// }
//
// MatchingRuleAssertion ::= SEQUENCE {
// matchingRule [1] MatchingRuleId OPTIONAL,
// type [2] AttributeDescription OPTIONAL,
// matchValue [3] AssertionValue,
// dnAttributes [4] BOOLEAN DEFAULT FALSE }
//
//
package ldap
import (
"errors"
"fmt"
"sort"
"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 { }
}
// 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 { }
}
// 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 ]
}
// 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 ]
}
// 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 )
}
}
// 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:
2024-05-24 14:08:09 -07:00
// - 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
//
2020-08-09 15:29:54 -07:00
// 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 )
if err != nil {
return searchResult , err
}
if result == nil {
return searchResult , NewError ( ErrorNetwork , errors . New ( "ldap: packet not received" ) )
}
for _ , entry := range result . Entries {
searchResult . Entries = append ( searchResult . Entries , entry )
}
for _ , referral := range result . Referrals {
searchResult . Referrals = append ( searchResult . Referrals , referral )
}
for _ , control := range result . Controls {
searchResult . Controls = append ( searchResult . Controls , control )
}
pagingResult := FindControl ( result . Controls , ControlTypePaging )
if pagingResult == nil {
pagingControl = nil
break
}
cookie := pagingResult . ( * ControlPaging ) . Cookie
if len ( cookie ) == 0 {
pagingControl = nil
break
}
pagingControl . SetCookie ( cookie )
}
if pagingControl != nil {
pagingControl . PagingSize = 0
l . Search ( searchRequest )
}
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 nil , err
}
switch packet . Children [ 1 ] . Tag {
case 4 :
entry := new ( Entry )
entry . DN = packet . Children [ 1 ] . Children [ 0 ] . Value . ( string )
for _ , child := range packet . Children [ 1 ] . Children [ 1 ] . Children {
attr := new ( EntryAttribute )
attr . Name = child . Children [ 0 ] . Value . ( string )
for _ , value := range child . Children [ 1 ] . Children {
attr . Values = append ( attr . Values , value . Value . ( string ) )
attr . ByteValues = append ( attr . ByteValues , value . ByteValue )
}
entry . Attributes = append ( entry . Attributes , attr )
}
result . Entries = append ( result . Entries , entry )
case 5 :
err := GetLDAPError ( packet )
if err != nil {
return nil , err
}
if len ( packet . Children ) == 3 {
for _ , child := range packet . Children [ 2 ] . Children {
decodedChild , err := DecodeControl ( child )
if err != nil {
return nil , 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 ) )
}
}
}