Initial QSfera import
This commit is contained in:
+333
@@ -0,0 +1,333 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
||||
Reference in New Issue
Block a user