ldap-upstream/internal/ldap/testutil_test.go
2026-11-01 22:17:59 -06:00

165 lines
5.5 KiB
Go

package ldap
import (
"testing"
"github.com/vjeantet/goldap/message"
ldap "github.com/vjeantet/ldapserver"
)
// The goldap message types have unexported fields with no exported
// constructors, so test messages are built by hand-encoding the DER
// for the requests we care about and running them through the real
// decoder.
func berElement(tag byte, body ...[]byte) []byte {
length := 0
for _, b := range body {
length += len(b)
}
out := []byte{tag, byte(length)}
for _, b := range body {
out = append(out, b...)
}
return out
}
func berInteger(v byte) []byte { return berElement(0x02, []byte{v}) }
func berOctetString(s string) []byte { return berElement(0x04, []byte(s)) }
// berBindRequest encodes an LDAPMessage containing a simple
// BindRequest for the given DN and password.
func berBindRequest(dn, password string) []byte {
bind := berElement(0x60, // [APPLICATION 0] constructed
berInteger(3),
berOctetString(dn),
berElement(0x80, []byte(password)), // [0] simple, primitive
)
return berElement(0x30, berInteger(1), bind)
}
// berSearchRequestWithAttrs encodes a SearchRequest for the given base
// DN with the given attribute selection.
func berSearchRequestWithAttrs(baseDN string, attrs ...string) []byte {
selection := make([][]byte, 0, len(attrs))
for _, a := range attrs {
selection = append(selection, berOctetString(a))
}
search := berElement(0x63, // [APPLICATION 3] constructed
berOctetString(baseDN), // baseObject
berElement(0x0a, []byte{2}), // scope: wholeSubtree
berElement(0x0a, []byte{0}), // derefAliases: never
berInteger(0), // sizeLimit
berInteger(0), // timeLimit
berElement(0x01, []byte{0x00}), // typesOnly: false
berElement(0x87, []byte("uid")), // filter: (uid=*)
berElement(0x30, selection...), // attributes
)
return berElement(0x30, berInteger(2), search)
}
// berSearchRequest encodes an LDAPMessage containing a SearchRequest
// with an attribute-present filter on uid and no attribute selection.
func berSearchRequest(baseDN string) []byte {
return berSearchRequestWithAttrs(baseDN)
}
// berEqualityFilter encodes an equalityMatch [3]
// AttributeValueAssertion filter and decodes it through the real BER
// reader. The filter types have unexported fields and no exported
// constructors, so this is the only way to build one for a test.
func berEqualityFilter(t *testing.T, attr, value string) message.Filter {
t.Helper()
return berDecodeFilter(t, berElement(0xa3, berOctetString(attr), berOctetString(value)))
}
// berPresentFilter encodes a present [7] AttributeDescription filter.
func berPresentFilter(t *testing.T, attr string) message.Filter {
t.Helper()
return berDecodeFilter(t, berElement(0x87, []byte(attr)))
}
// berOrFilter encodes an OR [1] of already-encoded filter elements.
func berOrFilter(t *testing.T, filters ...[]byte) message.Filter {
t.Helper()
return berDecodeFilter(t, berElement(0xa1, filters...))
}
// berAndFilter encodes an AND [0] of already-encoded filter elements.
func berAndFilter(t *testing.T, filters ...[]byte) message.Filter {
t.Helper()
return berDecodeFilter(t, berElement(0xa0, filters...))
}
// berNotFilter encodes a NOT [2] wrapping an already-encoded filter.
func berNotFilter(t *testing.T, filter []byte) message.Filter {
t.Helper()
return berDecodeFilter(t, berElement(0xa2, filter))
}
// berRawEquality returns the encoded bytes of an equality match so it
// can be composed into a nested filter.
func berRawEquality(attr, value string) []byte {
return berElement(0xa3, berOctetString(attr), berOctetString(value))
}
// berRawPresent returns the encoded bytes of a present filter.
func berRawPresent(attr string) []byte {
return berElement(0x87, []byte(attr))
}
// berRawOr returns the encoded bytes of an OR of already-encoded
// filter elements.
func berRawOr(t *testing.T, filters ...[]byte) []byte {
t.Helper()
return berElement(0xa1, filters...)
}
// berSubstringFilter encodes a substrings [4] filter with the given
// initial, any and final components.
func berSubstringFilter(t *testing.T, attr, initial string, any []string, final string) message.Filter {
t.Helper()
var parts [][]byte
if initial != "" {
parts = append(parts, berElement(0x80, []byte(initial)))
}
for _, a := range any {
parts = append(parts, berElement(0x81, []byte(a)))
}
if final != "" {
parts = append(parts, berElement(0x82, []byte(final)))
}
inner := berElement(0x30, parts...)
ava := berElement(0xa4, berOctetString(attr), inner)
return berDecodeFilter(t, ava)
}
// berDecodeFilter wraps a raw filter element in a minimal search
// request, decodes it and returns the parsed filter.
func berDecodeFilter(t *testing.T, filterElement []byte) message.Filter {
t.Helper()
search := berElement(0x63,
berOctetString(""), // baseObject
berElement(0x0a, []byte{2}), // scope wholeSubtree
berElement(0x0a, []byte{0}), // derefAliases never
berInteger(0), // sizeLimit
berInteger(0), // timeLimit
berElement(0x01, []byte{0}), // typesOnly false
filterElement, // filter
berElement(0x30), // empty attribute selection
)
m := mustDecode(t, berElement(0x30, berInteger(1), search))
r := m.GetSearchRequest()
return r.Filter()
}
func mustDecode(t *testing.T, raw []byte) *ldap.Message {
t.Helper()
m, err := message.ReadLDAPMessage(message.NewBytes(0, raw))
if err != nil {
t.Fatalf("decoding test message: %s", err)
}
return &ldap.Message{LDAPMessage: &m, Done: make(chan bool, 2)}
}