mirror of
https://github.com/minio/minio.git
synced 2024-12-24 06:05:55 -05:00
Add IAM API to attach/detach policies for LDAP (#16182)
This commit is contained in:
parent
dfe73629a3
commit
e06127566d
@ -19,8 +19,10 @@ package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
iampolicy "github.com/minio/pkg/iam/policy"
|
||||
@ -86,3 +88,94 @@ func (a adminAPIHandlers) ListLDAPPolicyMappingEntities(w http.ResponseWriter, r
|
||||
}
|
||||
writeSuccessResponseJSON(w, econfigData)
|
||||
}
|
||||
|
||||
// AttachDetachPolicyLDAP attaches or detaches policies from an LDAP entity
|
||||
// (user or group).
|
||||
//
|
||||
// POST <admin-prefix>/idp/ldap/policy/{operation}
|
||||
func (a adminAPIHandlers) AttachDetachPolicyLDAP(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "AttachDetachPolicyLDAP")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
|
||||
// Check authorization.
|
||||
|
||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.UpdatePolicyAssociationAction)
|
||||
if objectAPI == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if r.ContentLength > maxEConfigJSONSize || r.ContentLength == -1 {
|
||||
// More than maxConfigSize bytes were available
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigTooLarge), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure body content type is opaque to ensure that request body has not
|
||||
// been interpreted as form data.
|
||||
contentType := r.Header.Get("Content-Type")
|
||||
if contentType != "application/octet-stream" {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Validate operation
|
||||
operation := mux.Vars(r)["operation"]
|
||||
if operation != "attach" && operation != "detach" {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminInvalidArgument), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
isAttach := operation == "attach"
|
||||
|
||||
// Validate API arguments in body.
|
||||
password := cred.SecretKey
|
||||
reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err, logger.Application)
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
var par madmin.PolicyAssociationReq
|
||||
err = json.Unmarshal(reqBytes, &par)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
if err := par.IsValid(); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Call IAM subsystem
|
||||
updatedAt, addedOrRemoved, err := globalIAMSys.PolicyDBUpdateLDAP(ctx, isAttach, par)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
respBody := madmin.PolicyAssociationResp{
|
||||
UpdatedAt: updatedAt,
|
||||
}
|
||||
if isAttach {
|
||||
respBody.PoliciesAttached = addedOrRemoved
|
||||
} else {
|
||||
respBody.PoliciesDetached = addedOrRemoved
|
||||
}
|
||||
|
||||
data, err := json.Marshal(respBody)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
encryptedData, err := madmin.EncryptData(password, data)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
writeSuccessResponseJSON(w, encryptedData)
|
||||
}
|
||||
|
@ -192,7 +192,7 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) {
|
||||
|
||||
// LDAP IAM operations
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/idp/ldap/policy-entities").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListLDAPPolicyMappingEntities)))
|
||||
|
||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/idp/ldap/policy/{operation}").HandlerFunc(gz(httpTraceHdrs(adminAPI.AttachDetachPolicyLDAP)))
|
||||
// -- END IAM APIs --
|
||||
|
||||
// GetBucketQuotaConfig
|
||||
|
@ -265,6 +265,7 @@ const (
|
||||
ErrAdminGroupNotEmpty
|
||||
ErrAdminNoSuchJob
|
||||
ErrAdminNoSuchPolicy
|
||||
ErrAdminPolicyChangeAlreadyApplied
|
||||
ErrAdminInvalidArgument
|
||||
ErrAdminInvalidAccessKey
|
||||
ErrAdminInvalidSecretKey
|
||||
@ -1245,6 +1246,12 @@ var errorCodes = errorCodeMap{
|
||||
Description: "The canned policy does not exist.",
|
||||
HTTPStatusCode: http.StatusNotFound,
|
||||
},
|
||||
ErrAdminPolicyChangeAlreadyApplied: {
|
||||
Code: "XMinioAdminPolicyChangeAlreadyApplied",
|
||||
Description: "The specified policy change is already in effect.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
|
||||
ErrAdminInvalidArgument: {
|
||||
Code: "XMinioAdminInvalidArgument",
|
||||
Description: "Invalid arguments specified.",
|
||||
@ -1966,6 +1973,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
||||
apiErr = ErrAdminNoSuchJob
|
||||
case errNoSuchPolicy:
|
||||
apiErr = ErrAdminNoSuchPolicy
|
||||
case errNoPolicyToAttachOrDetach:
|
||||
apiErr = ErrAdminPolicyChangeAlreadyApplied
|
||||
case errSignatureMismatch:
|
||||
apiErr = ErrSignatureDoesNotMatch
|
||||
case errInvalidRange:
|
||||
|
File diff suppressed because one or more lines are too long
@ -862,6 +862,78 @@ func (store *IAMStoreSys) ListGroups(ctx context.Context) (res []string, err err
|
||||
return
|
||||
}
|
||||
|
||||
// PolicyDBUpdate - adds or removes given policies to/from the user or group's
|
||||
// policy associations.
|
||||
func (store *IAMStoreSys) PolicyDBUpdate(ctx context.Context, name string, isGroup bool,
|
||||
userType IAMUserType, policies []string, isAttach bool) (updatedAt time.Time, addedOrRemoved []string,
|
||||
err error,
|
||||
) {
|
||||
if name == "" {
|
||||
return updatedAt, nil, errInvalidArgument
|
||||
}
|
||||
|
||||
cache := store.lock()
|
||||
defer store.unlock()
|
||||
|
||||
// Load existing policy mapping
|
||||
var mp MappedPolicy
|
||||
if !isGroup {
|
||||
mp = cache.iamUserPolicyMap[name]
|
||||
} else {
|
||||
if store.getUsersSysType() == MinIOUsersSysType {
|
||||
g, ok := cache.iamGroupsMap[name]
|
||||
if !ok {
|
||||
return updatedAt, nil, errNoSuchGroup
|
||||
}
|
||||
|
||||
if g.Status == statusDisabled {
|
||||
// TODO: return an error?
|
||||
return updatedAt, nil, nil
|
||||
}
|
||||
}
|
||||
mp = cache.iamGroupPolicyMap[name]
|
||||
}
|
||||
|
||||
// Compute net policy change effect and updated policy mapping
|
||||
existingPolicySet := mp.policySet()
|
||||
policiesToUpdate := set.CreateStringSet(policies...)
|
||||
newPolicyMapping := mp
|
||||
if isAttach {
|
||||
// new policies to attach => inputPolicies - existing (set difference)
|
||||
policiesToUpdate = policiesToUpdate.Difference(existingPolicySet)
|
||||
// validate that new policies to add are defined.
|
||||
for _, p := range policiesToUpdate.ToSlice() {
|
||||
if _, found := cache.iamPolicyDocsMap[p]; !found {
|
||||
return updatedAt, nil, errNoSuchPolicy
|
||||
}
|
||||
}
|
||||
newPolicyMapping.Policies = strings.Join(existingPolicySet.Union(policiesToUpdate).ToSlice(), ",")
|
||||
} else {
|
||||
// policies to detach => inputPolicies ∩ existing (intersection)
|
||||
policiesToUpdate = policiesToUpdate.Intersection(existingPolicySet)
|
||||
newPolicyMapping.Policies = strings.Join(existingPolicySet.Difference(policiesToUpdate).ToSlice(), ",")
|
||||
}
|
||||
newPolicyMapping.UpdatedAt = UTCNow()
|
||||
|
||||
// We return an error if the requested policy update will have no effect.
|
||||
if policiesToUpdate.IsEmpty() {
|
||||
return updatedAt, nil, errNoPolicyToAttachOrDetach
|
||||
}
|
||||
|
||||
addedOrRemoved = policiesToUpdate.ToSlice()
|
||||
|
||||
if err := store.saveMappedPolicy(ctx, name, userType, isGroup, newPolicyMapping); err != nil {
|
||||
return updatedAt, addedOrRemoved, err
|
||||
}
|
||||
if !isGroup {
|
||||
cache.iamUserPolicyMap[name] = newPolicyMapping
|
||||
} else {
|
||||
cache.iamGroupPolicyMap[name] = newPolicyMapping
|
||||
}
|
||||
cache.updatedAt = UTCNow()
|
||||
return cache.updatedAt, addedOrRemoved, nil
|
||||
}
|
||||
|
||||
// PolicyDBSet - update the policy mapping for the given user or group in
|
||||
// storage and in cache. We do not check for the existence of the user here
|
||||
// since users can be virtual, such as for:
|
||||
|
52
cmd/iam.go
52
cmd/iam.go
@ -1497,6 +1497,58 @@ func (sys *IAMSys) PolicyDBSet(ctx context.Context, name, policy string, userTyp
|
||||
return updatedAt, nil
|
||||
}
|
||||
|
||||
// PolicyDBUpdateLDAP - adds or removes policies from a user or a group verified
|
||||
// to be in the LDAP directory.
|
||||
func (sys *IAMSys) PolicyDBUpdateLDAP(ctx context.Context, isAttach bool,
|
||||
r madmin.PolicyAssociationReq,
|
||||
) (updatedAt time.Time, addedOrRemoved []string, err error) {
|
||||
if !sys.Initialized() {
|
||||
return updatedAt, nil, errServerNotInitialized
|
||||
}
|
||||
|
||||
var dn string
|
||||
var isGroup bool
|
||||
if r.User != "" {
|
||||
dn, err = globalLDAPConfig.DoesUsernameExist(r.User)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return updatedAt, nil, err
|
||||
}
|
||||
if dn == "" {
|
||||
return updatedAt, nil, errNoSuchUser
|
||||
}
|
||||
isGroup = false
|
||||
} else {
|
||||
if exists, err := globalLDAPConfig.DoesGroupDNExist(r.Group); err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return updatedAt, nil, err
|
||||
} else if !exists {
|
||||
return updatedAt, nil, errNoSuchGroup
|
||||
}
|
||||
dn = r.Group
|
||||
isGroup = true
|
||||
}
|
||||
|
||||
userType := stsUser
|
||||
updatedAt, addedOrRemoved, err = sys.store.PolicyDBUpdate(ctx, dn, isGroup,
|
||||
userType, r.Policies, isAttach)
|
||||
if err != nil {
|
||||
return updatedAt, nil, err
|
||||
}
|
||||
|
||||
// Notify all other MinIO peers to reload policy
|
||||
if !sys.HasWatcher() {
|
||||
for _, nerr := range globalNotificationSys.LoadPolicyMapping(dn, userType, isGroup) {
|
||||
if nerr.Err != nil {
|
||||
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
||||
logger.LogIf(ctx, nerr.Err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return updatedAt, addedOrRemoved, nil
|
||||
}
|
||||
|
||||
// PolicyDBGet - gets policy set on a user or group. If a list of groups is
|
||||
// given, policies associated with them are included as well.
|
||||
func (sys *IAMSys) PolicyDBGet(name string, isGroup bool, groups ...string) ([]string, error) {
|
||||
|
@ -80,6 +80,10 @@ var errNoSuchAccount = errors.New("Specified account does not exist")
|
||||
// error returned in IAM subsystem when groups doesn't exist.
|
||||
var errNoSuchGroup = errors.New("Specified group does not exist")
|
||||
|
||||
// error returned in IAM subsystem when a policy attach/detach request has no
|
||||
// net effect, i.e. it is already applied.
|
||||
var errNoPolicyToAttachOrDetach = errors.New("Specified policy update has no net effect")
|
||||
|
||||
// error returned in IAM subsystem when a non-empty group needs to be
|
||||
// deleted.
|
||||
var errGroupNotEmpty = errors.New("Specified group is not empty - cannot remove it")
|
||||
|
4
go.mod
4
go.mod
@ -49,9 +49,9 @@ require (
|
||||
github.com/minio/dperf v0.4.2
|
||||
github.com/minio/highwayhash v1.0.2
|
||||
github.com/minio/kes v0.22.0
|
||||
github.com/minio/madmin-go/v2 v2.0.0
|
||||
github.com/minio/madmin-go/v2 v2.0.1
|
||||
github.com/minio/minio-go/v7 v7.0.44
|
||||
github.com/minio/pkg v1.5.5
|
||||
github.com/minio/pkg v1.5.6
|
||||
github.com/minio/selfupdate v0.5.0
|
||||
github.com/minio/sha256-simd v1.0.0
|
||||
github.com/minio/simdjson-go v0.4.2
|
||||
|
8
go.sum
8
go.sum
@ -764,8 +764,8 @@ github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLT
|
||||
github.com/minio/kes v0.22.0 h1:3PGIgjHTC5fAjmqfUcLIcSxV5CaxPyjY3q8q28UF158=
|
||||
github.com/minio/kes v0.22.0/go.mod h1:q5T0uTFrr7l6GosXvF0ufCtUKkbmbSZW1Yhu4KgLKE8=
|
||||
github.com/minio/madmin-go v1.6.6/go.mod h1:ATvkBOLiP3av4D++2v1UEHC/QzsGtgXD5kYvvRYzdKs=
|
||||
github.com/minio/madmin-go/v2 v2.0.0 h1:VR+zCIeoHPveppvMUhZ/vmok1UJp4pH38M4wkqpyT88=
|
||||
github.com/minio/madmin-go/v2 v2.0.0/go.mod h1:5aFi/VLWBHC2DEFfGIlUmAeJhaF4ZAjuYpEWZFU14Zw=
|
||||
github.com/minio/madmin-go/v2 v2.0.1 h1:WFfe12P18k9WSEFUZzUaBOQ78vjMBafM1YjgtXkkJoM=
|
||||
github.com/minio/madmin-go/v2 v2.0.1/go.mod h1:5aFi/VLWBHC2DEFfGIlUmAeJhaF4ZAjuYpEWZFU14Zw=
|
||||
github.com/minio/mc v0.0.0-20221201184114-854b4f123f03 h1:/q0NA3KjhTL+q/R9xEye0lpJECJmwaZnuIBsBn4HP28=
|
||||
github.com/minio/mc v0.0.0-20221201184114-854b4f123f03/go.mod h1:+Jrdvdo6p83JtqUO38UUeTu4aspklp9cF9k6DqFkb0Q=
|
||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||
@ -774,8 +774,8 @@ github.com/minio/minio-go/v7 v7.0.41/go.mod h1:nCrRzjoSUQh8hgKKtu3Y708OLvRLtuASM
|
||||
github.com/minio/minio-go/v7 v7.0.44 h1:9zUJ7iU7ax2P1jOvTp6nVrgzlZq3AZlFm0XfRFDKstM=
|
||||
github.com/minio/minio-go/v7 v7.0.44/go.mod h1:nCrRzjoSUQh8hgKKtu3Y708OLvRLtuASMg2/nvmbarw=
|
||||
github.com/minio/pkg v1.5.4/go.mod h1:2MOaRFdmFKULD+uOLc3qHLGTQTuxCNPKNPfLBTxC8CA=
|
||||
github.com/minio/pkg v1.5.5 h1:z53jAVkXpRD+Y4LBt9cN+EaPUbWD6iOWhZuC90JvGqk=
|
||||
github.com/minio/pkg v1.5.5/go.mod h1:EiGlHS2xaooa2VMxhJsxxAZHDObHVUB3HwtuoEXOCVE=
|
||||
github.com/minio/pkg v1.5.6 h1:4OUvRU1gDWilu/dohkJMVapylXN8q94kU5MgkOJ/x0I=
|
||||
github.com/minio/pkg v1.5.6/go.mod h1:EiGlHS2xaooa2VMxhJsxxAZHDObHVUB3HwtuoEXOCVE=
|
||||
github.com/minio/selfupdate v0.5.0 h1:0UH1HlL49+2XByhovKl5FpYTjKfvrQ2sgL1zEXK6mfI=
|
||||
github.com/minio/selfupdate v0.5.0/go.mod h1:mcDkzMgq8PRcpCRJo/NlPY7U45O5dfYl2Y0Rg7IustY=
|
||||
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
|
||||
|
@ -18,6 +18,7 @@
|
||||
package ldap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -56,6 +57,126 @@ func (l *Config) LookupUserDN(username string) (string, []string, error) {
|
||||
return bindDN, groups, nil
|
||||
}
|
||||
|
||||
// DoesUsernameExist checks if the given username exists in the LDAP directory.
|
||||
// The given username could be just the short "login" username or the full DN.
|
||||
// When the username is found, the full DN is returned, otherwise the returned
|
||||
// string is empty. If the user is not found, err = nil, otherwise, err != nil.
|
||||
func (l *Config) DoesUsernameExist(username string) (string, error) {
|
||||
conn, err := l.LDAP.Connect()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Bind to the lookup user account
|
||||
if err = l.LDAP.LookupBind(conn); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Check if the passed in username is a valid DN.
|
||||
parsedUsernameDN, err := ldap.ParseDN(username)
|
||||
if err != nil {
|
||||
// Since the passed in username was not a DN, we consider it as a login
|
||||
// username and attempt to check it exists in the directory.
|
||||
bindDN, err := l.LDAP.LookupUserDN(conn, username)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "not found") {
|
||||
return "", nil
|
||||
}
|
||||
return "", fmt.Errorf("Unable to find user DN: %w", err)
|
||||
}
|
||||
return bindDN, nil
|
||||
}
|
||||
|
||||
// Since the username is a valid DN, check that it is under a configured
|
||||
// base DN in the LDAP directory.
|
||||
var foundDistName []string
|
||||
for _, baseDN := range l.LDAP.UserDNSearchBaseDistNames {
|
||||
// BaseDN should not fail to parse.
|
||||
baseDNParsed, _ := ldap.ParseDN(baseDN)
|
||||
if baseDNParsed.AncestorOf(parsedUsernameDN) {
|
||||
searchRequest := ldap.NewSearchRequest(username, ldap.ScopeBaseObject, ldap.NeverDerefAliases,
|
||||
0, 0, false, "(objectClass=*)", nil, nil)
|
||||
searchResult, err := conn.Search(searchRequest)
|
||||
if err != nil {
|
||||
// Check if there is no matching result.
|
||||
// Ref: https://ldap.com/ldap-result-code-reference/
|
||||
if ldap.IsErrorWithCode(err, 32) {
|
||||
continue
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
for _, entry := range searchResult.Entries {
|
||||
foundDistName = append(foundDistName, entry.DN)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(foundDistName) == 1 {
|
||||
return foundDistName[0], nil
|
||||
} else if len(foundDistName) > 1 {
|
||||
// FIXME: This error would happen if the multiple base DNs are given and
|
||||
// some base DNs are subtrees of other base DNs - we should validate
|
||||
// and error out in such cases.
|
||||
return "", fmt.Errorf("found multiple DNs for the given username")
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// DoesGroupDNExist checks if the given group DN exists in the LDAP directory.
|
||||
func (l *Config) DoesGroupDNExist(groupDN string) (bool, error) {
|
||||
if len(l.LDAP.GroupSearchBaseDistNames) == 0 {
|
||||
return false, errors.New("no group search Base DNs given")
|
||||
}
|
||||
|
||||
gdn, err := ldap.ParseDN(groupDN)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Given group DN could not be parsed: %s", err)
|
||||
}
|
||||
|
||||
conn, err := l.LDAP.Connect()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Bind to the lookup user account
|
||||
if err = l.LDAP.LookupBind(conn); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
var foundDistName []string
|
||||
for _, baseDN := range l.LDAP.GroupSearchBaseDistNames {
|
||||
// BaseDN should not fail to parse.
|
||||
baseDNParsed, _ := ldap.ParseDN(baseDN)
|
||||
if baseDNParsed.AncestorOf(gdn) {
|
||||
searchRequest := ldap.NewSearchRequest(groupDN, ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false, "(objectClass=*)", nil, nil)
|
||||
searchResult, err := conn.Search(searchRequest)
|
||||
if err != nil {
|
||||
// Check if there is no matching result.
|
||||
// Ref: https://ldap.com/ldap-result-code-reference/
|
||||
if ldap.IsErrorWithCode(err, 32) {
|
||||
continue
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
for _, entry := range searchResult.Entries {
|
||||
foundDistName = append(foundDistName, entry.DN)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(foundDistName) == 1 {
|
||||
return true, nil
|
||||
} else if len(foundDistName) > 1 {
|
||||
// FIXME: This error would happen if the multiple base DNs are given and
|
||||
// some base DNs are subtrees of other base DNs - we should validate
|
||||
// and error out in such cases.
|
||||
return false, fmt.Errorf("found multiple DNs for the given group DN")
|
||||
} else {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Bind - binds to ldap, searches LDAP and returns the distinguished name of the
|
||||
// user and the list of groups.
|
||||
func (l *Config) Bind(username, password string) (string, []string, error) {
|
||||
|
Loading…
Reference in New Issue
Block a user