mirror of
https://github.com/owncloud/ocis
synced 2026-05-09 08:33:09 +02:00
334 lines
9.0 KiB
Go
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
|
|
}
|