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)} }