226 lines
5.2 KiB
Go
226 lines
5.2 KiB
Go
/*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
* Copyright 2021 The LibreGraph Authors.
|
|
*/
|
|
|
|
package ldif
|
|
|
|
import (
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/armon/go-radix"
|
|
"github.com/libregraph/idm/pkg/ldapserver"
|
|
"github.com/spacewander/go-suffix-tree"
|
|
)
|
|
|
|
var indexAttributes = map[string]string{
|
|
"entryCSN": "eq",
|
|
"entryUUID": "eq",
|
|
"objectClass": "eq",
|
|
"cn": "pres,eq,sub",
|
|
"gidNumber": "eq",
|
|
"mail": "pres,eq,sub",
|
|
"memberUid": "eq",
|
|
"ou": "eq",
|
|
"uid": "eq",
|
|
"uidNumber": "eq",
|
|
"uniqueMember": "eq",
|
|
|
|
"sn": "pres,eq,sub",
|
|
"givenName": "pres,eq,sub",
|
|
|
|
"mailAlternateAddress": "eq",
|
|
|
|
// Additional indexes for attributes usually used with AD.
|
|
"objectGUID": "eq",
|
|
"objectSID": "eq",
|
|
"otherMailbox": "eq",
|
|
"samAccountName": "eq",
|
|
}
|
|
|
|
func AddIndexAttribute(name string, indices string) {
|
|
indexAttributes[name] = indices
|
|
}
|
|
|
|
func RemoveIndexAttribute(name string) {
|
|
delete(indexAttributes, name)
|
|
}
|
|
|
|
type Index interface {
|
|
Add(name, op string, values []string, entry *ldifEntry) bool
|
|
Load(name, op string, params ...string) ([]*ldifEntry, bool)
|
|
}
|
|
|
|
type indexMap map[string][]*ldifEntry
|
|
|
|
func newIndexMap() indexMap {
|
|
return make(indexMap)
|
|
}
|
|
|
|
func (im indexMap) Add(name, op string, values []string, entry *ldifEntry) bool {
|
|
for _, value := range values {
|
|
value = strings.ToLower(value)
|
|
im[value] = append(im[value], entry)
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (im indexMap) Load(name, op string, value ...string) ([]*ldifEntry, bool) {
|
|
entries := im[strings.ToLower(value[0])]
|
|
return entries, true
|
|
}
|
|
|
|
type indexSuffixTree struct {
|
|
t *suffix.Tree
|
|
}
|
|
|
|
func newIndexSuffixTree() *indexSuffixTree {
|
|
return &indexSuffixTree{
|
|
t: suffix.NewTree(),
|
|
}
|
|
}
|
|
|
|
func (ist indexSuffixTree) Add(name, op string, values []string, entry *ldifEntry) bool {
|
|
for _, value := range values {
|
|
sfx := []byte(value)
|
|
var entries []*ldifEntry
|
|
if v, ok := ist.t.Get(sfx); ok {
|
|
entries = v.([]*ldifEntry)
|
|
}
|
|
entries = append(entries, entry)
|
|
ist.t.Insert(sfx, entries)
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (ist indexSuffixTree) Load(name, op string, value ...string) ([]*ldifEntry, bool) {
|
|
var entries []*ldifEntry
|
|
sfx := []byte(value[0])
|
|
ist.t.WalkSuffix(sfx, func(key []byte, value interface{}) bool {
|
|
entries = append(entries, value.([]*ldifEntry)...)
|
|
return false
|
|
})
|
|
return entries, true
|
|
}
|
|
|
|
type indexRadixTree struct {
|
|
t *radix.Tree
|
|
}
|
|
|
|
func newIndexRadixTree() *indexRadixTree {
|
|
return &indexRadixTree{
|
|
t: radix.New(),
|
|
}
|
|
}
|
|
|
|
func (irt *indexRadixTree) Add(name, op string, values []string, entry *ldifEntry) bool {
|
|
for _, value := range values {
|
|
pfx := value
|
|
var entries []*ldifEntry
|
|
if v, ok := irt.t.Get(pfx); ok {
|
|
entries = v.([]*ldifEntry)
|
|
}
|
|
entries = append(entries, entry)
|
|
irt.t.Insert(pfx, entries)
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (irt *indexRadixTree) Load(name, op string, value ...string) ([]*ldifEntry, bool) {
|
|
var entries []*ldifEntry
|
|
pfx := value[0]
|
|
irt.t.WalkPrefix(pfx, func(key string, value interface{}) bool {
|
|
entries = append(entries, value.([]*ldifEntry)...)
|
|
return false
|
|
})
|
|
return entries, true
|
|
}
|
|
|
|
type indexSubTree struct {
|
|
pres indexMap
|
|
irt *indexRadixTree
|
|
ist *indexSuffixTree
|
|
}
|
|
|
|
func newIndexSubTree() *indexSubTree {
|
|
return &indexSubTree{
|
|
pres: newIndexMap(),
|
|
irt: newIndexRadixTree(),
|
|
ist: newIndexSuffixTree(),
|
|
}
|
|
}
|
|
|
|
func (idx *indexSubTree) Add(name, op string, values []string, entry *ldifEntry) bool {
|
|
ok0 := idx.pres.Add(name, op, []string{""}, entry)
|
|
ok1 := idx.irt.Add(name, op, values, entry)
|
|
ok2 := idx.ist.Add(name, op, values, entry)
|
|
return ok0 || ok1 || ok2
|
|
}
|
|
|
|
func (idx *indexSubTree) Load(name, op string, params ...string) ([]*ldifEntry, bool) {
|
|
if len(params) != 2 {
|
|
// Require one value and sub tag.
|
|
return nil, false
|
|
}
|
|
tag, err := strconv.ParseInt(params[1], 10, 64)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
switch tag {
|
|
case ldapserver.FilterSubstringsAny:
|
|
// TODO(longsleep): Find a suitable way for full text search substring
|
|
// matching, for example with Knuth-Morris-Pratt algorithm. Currently
|
|
// we just do a presence match.
|
|
return idx.pres.Load(name, op, "")
|
|
case ldapserver.FilterSubstringsInitial:
|
|
return idx.irt.Load(name, op, params[0])
|
|
case ldapserver.FilterSubstringsFinal:
|
|
return idx.ist.Load(name, op, params[0])
|
|
default:
|
|
return nil, false
|
|
}
|
|
}
|
|
|
|
type indexMapRegister map[string]Index
|
|
|
|
func newIndexMapRegister() indexMapRegister {
|
|
imr := make(indexMapRegister)
|
|
for name, ops := range indexAttributes {
|
|
for _, op := range strings.Split(ops, ",") {
|
|
switch op {
|
|
case "sub":
|
|
imr[imr.getKey(name, op)] = newIndexSubTree()
|
|
case "pres":
|
|
imr[imr.getKey(name, op)] = newIndexMap()
|
|
case "eq":
|
|
imr[imr.getKey(name, op)] = newIndexMap()
|
|
}
|
|
}
|
|
}
|
|
return imr
|
|
}
|
|
|
|
func (imr indexMapRegister) getKey(name, op string) string {
|
|
return strings.ToLower(name) + "," + op
|
|
}
|
|
|
|
func (imr indexMapRegister) Add(name, op string, values []string, entry *ldifEntry) bool {
|
|
index, ok := imr[imr.getKey(name, op)]
|
|
if !ok {
|
|
// No matching index, refuse to add.
|
|
return false
|
|
}
|
|
return index.Add(name, op, values, entry)
|
|
}
|
|
|
|
func (imr indexMapRegister) Load(name, op string, params ...string) ([]*ldifEntry, bool) {
|
|
index, ok := imr[imr.getKey(name, op)]
|
|
if !ok {
|
|
// No such index.
|
|
return nil, false
|
|
}
|
|
return index.Load(name, op, params...)
|
|
}
|