Files
ocis/vendor/github.com/libregraph/idm/server/handler/boltdb/handler.go
2023-04-19 20:24:34 +02:00

334 lines
9.0 KiB
Go

/*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2021 The LibreGraph Authors.
*/
package boltdb
import (
"context"
"errors"
"fmt"
"net"
"path/filepath"
"strings"
"github.com/go-ldap/ldap/v3"
"github.com/sirupsen/logrus"
"github.com/libregraph/idm/pkg/ldapdn"
"github.com/libregraph/idm/pkg/ldapentry"
"github.com/libregraph/idm/pkg/ldappassword"
"github.com/libregraph/idm/pkg/ldapserver"
"github.com/libregraph/idm/pkg/ldbbolt"
"github.com/libregraph/idm/server/handler"
)
type boltdbHandler struct {
logger logrus.FieldLogger
dbfile string
baseDN string
adminDN string
allowLocalAnonymousBind bool
ctx context.Context
bdb *ldbbolt.LdbBolt
}
type Options struct {
BaseDN string
AdminDN string
AllowLocalAnonymousBind bool
}
func NewBoltDBHandler(logger logrus.FieldLogger, fn string, options *Options) (handler.Handler, error) {
if fn == "" {
return nil, fmt.Errorf("file name is empty")
}
if options.BaseDN == "" {
return nil, fmt.Errorf("base dn is empty")
}
fn, err := filepath.Abs(fn)
if err != nil {
return nil, err
}
h := &boltdbHandler{
logger: logger,
dbfile: fn,
allowLocalAnonymousBind: options.AllowLocalAnonymousBind,
ctx: context.Background(),
}
if h.baseDN, err = ldapdn.ParseNormalize(options.BaseDN); err != nil {
return nil, err
}
if h.adminDN, err = ldapdn.ParseNormalize(options.AdminDN); err != nil {
return nil, err
}
err = h.setup()
if err != nil {
return nil, err
}
return h, nil
}
func (h *boltdbHandler) setup() error {
bdb := &ldbbolt.LdbBolt{}
if err := bdb.Configure(h.logger, h.baseDN, h.dbfile, nil); err != nil {
return err
}
if err := bdb.Initialize(); err != nil {
return err
}
h.bdb = bdb
return nil
}
func (h *boltdbHandler) Add(boundDN string, req *ldap.AddRequest, conn net.Conn) (ldapserver.LDAPResultCode, error) {
logger := h.logger.WithFields(logrus.Fields{
"op": "add",
"bind_dn": boundDN,
"remote_addr": conn.RemoteAddr().String(),
})
if !h.writeAllowed(boundDN) {
return ldap.LDAPResultInsufficientAccessRights, nil
}
e := ldapentry.EntryFromAddRequest(req)
if err := h.bdb.EntryPut(e); err != nil {
logger.WithError(err).WithField("entrydn", e.DN).Debugln("ldap add failed")
if errors.Is(err, ldbbolt.ErrEntryAlreadyExists) {
return ldap.LDAPResultEntryAlreadyExists, nil
}
return ldap.LDAPResultUnwillingToPerform, err
}
return ldap.LDAPResultSuccess, nil
}
func (h *boltdbHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (ldapserver.LDAPResultCode, error) {
logger := h.logger.WithFields(logrus.Fields{
"op": "bind",
"bind_dn": bindDN,
"remote_addr": conn.RemoteAddr().String(),
})
// Handle anoymous bind
if bindDN == "" {
if !h.allowLocalAnonymousBind {
logger.Debugln("ldap anonymous Bind disabled")
return ldap.LDAPResultInvalidCredentials, nil
} else if bindSimplePw == "" {
return ldap.LDAPResultSuccess, nil
}
}
bindDN, err := ldapdn.ParseNormalize(bindDN)
if err != nil {
logger.WithError(err).Debugln("ldap bind request BindDN validation failed")
return ldap.LDAPResultInvalidDNSyntax, nil
}
if !strings.HasSuffix(bindDN, h.baseDN) {
logger.WithError(err).Debugln("ldap bind request BindDN outside of Database tree")
return ldap.LDAPResultInvalidCredentials, nil
}
// Disallow empty password
if bindSimplePw == "" {
logger.Debugf("BindDN without password")
return ldap.LDAPResultInvalidCredentials, nil
}
return h.validatePassword(logger, bindDN, bindSimplePw)
}
func (h *boltdbHandler) validatePassword(logger logrus.FieldLogger, bindDN, bindSimplePw string) (ldapserver.LDAPResultCode, error) {
// Lookup Bind DN in database
entries, err := h.bdb.Search(bindDN, ldap.ScopeBaseObject)
if err != nil || len(entries) != 1 {
if err != nil {
logger.Error(err)
}
if len(entries) != 1 {
logger.Debugf("Entry '%s' does not exist", bindDN)
}
return ldap.LDAPResultInvalidCredentials, nil
}
userPassword := entries[0].GetEqualFoldAttributeValue("userPassword")
match, err := ldappassword.Validate(bindSimplePw, userPassword)
if err != nil {
logger.Error(err)
return ldap.LDAPResultInvalidCredentials, nil
}
if match {
logger.Debug("success")
return ldap.LDAPResultSuccess, nil
}
return ldap.LDAPResultInvalidCredentials, nil
}
func (h *boltdbHandler) Delete(boundDN string, req *ldap.DelRequest, conn net.Conn) (ldapserver.LDAPResultCode, error) {
logger := h.logger.WithFields(logrus.Fields{
"op": "delete",
"bind_dn": boundDN,
"remote_addr": conn.RemoteAddr().String(),
})
if !h.writeAllowed(boundDN) {
return ldap.LDAPResultInsufficientAccessRights, nil
}
logger.Debug("Calling boltdb delete")
if err := h.bdb.EntryDelete(req.DN); err != nil {
logger.WithError(err).WithField("entrydn", req.DN).Debugln("ldap delete failed")
if errors.Is(err, ldbbolt.ErrEntryAlreadyExists) {
return ldap.LDAPResultEntryAlreadyExists, nil
}
return ldap.LDAPResultUnwillingToPerform, err
}
logger.Debug("delete succeeded")
return ldap.LDAPResultSuccess, nil
}
func (h *boltdbHandler) Modify(boundDN string, req *ldap.ModifyRequest, conn net.Conn) (ldapserver.LDAPResultCode, error) {
logger := h.logger.WithFields(logrus.Fields{
"op": "modify",
"bind_dn": boundDN,
"remote_addr": conn.RemoteAddr().String(),
"entrydn": req.DN,
})
if !h.writeAllowed(boundDN) {
return ldap.LDAPResultInsufficientAccessRights, nil
}
logger.Debug("Calling boltdb modify")
if err := h.bdb.EntryModify(req); err != nil {
logger.WithError(err).Debug("ldap modify failed")
if errors.Is(err, ldbbolt.ErrEntryAlreadyExists) {
return ldap.LDAPResultEntryAlreadyExists, nil
}
ldapError, ok := err.(*ldap.Error)
if !ok {
return ldap.LDAPResultUnwillingToPerform, err
}
return ldapserver.LDAPResultCode(ldapError.ResultCode), ldapError.Err
}
logger.Debug("modify succeeded")
return ldap.LDAPResultSuccess, nil
}
func (h *boltdbHandler) ModifyDN(boundDN string, req *ldap.ModifyDNRequest, conn net.Conn) (ldapserver.LDAPResultCode, error) {
logger := h.logger.WithFields(logrus.Fields{
"op": "modifyDN",
"bind_dn": boundDN,
"remote_addr": conn.RemoteAddr().String(),
"entrydn": req.DN,
})
if !h.writeAllowed(boundDN) {
return ldap.LDAPResultInsufficientAccessRights, nil
}
logger.Debug("Calling boltdb modify DN")
if err := h.bdb.EntryModifyDN(req); err != nil {
logger.WithError(err).Debug("ldap modifyDN failed")
if errors.Is(err, ldbbolt.ErrEntryAlreadyExists) {
return ldap.LDAPResultEntryAlreadyExists, nil
}
ldapError, ok := err.(*ldap.Error)
if !ok {
return ldap.LDAPResultUnwillingToPerform, err
}
return ldapserver.LDAPResultCode(ldapError.ResultCode), ldapError.Err
}
logger.Debug("modify DN succeeded")
return ldap.LDAPResultSuccess, nil
return ldap.LDAPResultUnwillingToPerform, errors.New("unsupported operation")
}
func (h *boltdbHandler) ModifyPasswordExop(boundDN string, req *ldap.PasswordModifyRequest, conn net.Conn) (ldapserver.LDAPResultCode, error) {
logger := h.logger.WithFields(logrus.Fields{
"op": "modpw_exop",
"binddn": boundDN,
"UserIdentity": req.UserIdentity,
"OldPWPresent": req.OldPassword != "",
"NewPwPresent": req.NewPassword != "",
})
if boundDN == "" {
return ldap.LDAPResultInsufficientAccessRights, nil
}
if req.UserIdentity != "" {
pwUserDN, err := ldapdn.ParseNormalize(req.UserIdentity)
if err != nil {
return ldap.LDAPResultOperationsError, errors.New("Error parsing DN in password modify extended operation.")
}
if boundDN != pwUserDN && boundDN != h.adminDN {
return ldap.LDAPResultInsufficientAccessRights, nil
}
}
logger.Debug("Calling boltdb UpdatePassword")
err := h.bdb.UpdatePassword(req)
if err != nil {
logger.Debugf("boltdb UpdatePassword returned '%s'", err)
return ldap.LDAPResultOther, err
}
return ldap.LDAPResultSuccess, err
}
func (h *boltdbHandler) Search(boundDN string, req *ldap.SearchRequest, conn net.Conn) (ldapserver.ServerSearchResult, error) {
logger := h.logger.WithFields(logrus.Fields{
"op": "search",
"binddn": boundDN,
"basedn": req.BaseDN,
"filter": req.Filter,
"attrs": req.Attributes,
})
logger.Debug("Calling boltdb search")
entries, _ := h.bdb.Search(req.BaseDN, req.Scope)
logger.Debugf("boltdb search returned %d entries", len(entries))
return ldapserver.ServerSearchResult{
Entries: entries,
Referrals: []string{},
Controls: []ldap.Control{},
ResultCode: ldap.LDAPResultSuccess,
}, nil
}
func (h *boltdbHandler) Close(boundDN string, conn net.Conn) error {
return nil
}
func (h *boltdbHandler) WithContext(ctx context.Context) handler.Handler {
if ctx == nil {
panic("nil context")
}
h2 := new(boltdbHandler)
*h2 = *h
h2.ctx = ctx
return h2
}
func (h *boltdbHandler) Reload(ctx context.Context) error {
return nil
}
func (h *boltdbHandler) writeAllowed(boundDN string) bool {
if h.adminDN != "" && h.adminDN == boundDN {
return true
}
return false
}