From fb65713fbdfd626cb033675fb42724a5fb3d06ff Mon Sep 17 00:00:00 2001 From: kt Date: Sun, 19 Jul 2026 06:53:08 +0900 Subject: [PATCH] fix: reject disabled users on all backends in Bind (#470) A user with disabled = true could still bind on the config backend, since configHandler.FindUser and LDAPOpsHelper.Bind never checked Disabled while the SQL backends drop such users at lookup. Enforce it in Bind so every backend behaves the same. --- v2/pkg/handler/disabled_bind_test.go | 56 ++++++++++++++++++++++++++++ v2/pkg/handler/ldapopshelper.go | 6 +++ 2 files changed, 62 insertions(+) create mode 100644 v2/pkg/handler/disabled_bind_test.go diff --git a/v2/pkg/handler/disabled_bind_test.go b/v2/pkg/handler/disabled_bind_test.go new file mode 100644 index 0000000..c4da8a5 --- /dev/null +++ b/v2/pkg/handler/disabled_bind_test.go @@ -0,0 +1,56 @@ +package handler + +import ( + "context" + "encoding/hex" + "net" + "testing" + + "github.com/rs/zerolog" + "go.opentelemetry.io/otel/trace/noop" + "golang.org/x/crypto/bcrypt" + + "github.com/glauth/glauth/v2/pkg/config" + "github.com/glauth/ldap" +) + +func TestConfigBackendDisabledUserCannotBind(t *testing.T) { + pw := "correct horse" + raw, _ := bcrypt.GenerateFromPassword([]byte(pw), bcrypt.MinCost) + hexHash := hex.EncodeToString(raw) + + cfg := &config.Config{} + cfg.Users = []config.User{ + {Name: "activeuser", PrimaryGroup: 5501, PassBcrypt: hexHash, Disabled: false}, + {Name: "disableduser", PrimaryGroup: 5501, PassBcrypt: hexHash, Disabled: true}, + } + + logger := zerolog.Nop() + tracer := noop.NewTracerProvider().Tracer("f001") + helper := NewLDAPOpsHelper(tracer) + h := configHandler{ + backend: config.Backend{ + BaseDN: "dc=example,dc=com", + NameFormatAsArray: []string{"cn"}, + GroupFormatAsArray: []string{"ou"}, + }, + log: &logger, + cfg: cfg, + ldohelper: helper, + tracer: tracer, + } + _, server := net.Pipe() + defer server.Close() + + bind := func(dn, pass string) ldap.LDAPResultCode { + code, _ := helper.Bind(context.Background(), h, dn, pass, server) + return code + } + + if code := bind("cn=activeuser,dc=example,dc=com", pw); code != ldap.LDAPResultSuccess { + t.Fatalf("active user with correct pw should bind, got %d", code) + } + if code := bind("cn=disableduser,dc=example,dc=com", pw); code != ldap.LDAPResultInvalidCredentials { + t.Errorf("disabled user must be rejected; got code %d, want InvalidCredentials(49)", code) + } +} diff --git a/v2/pkg/handler/ldapopshelper.go b/v2/pkg/handler/ldapopshelper.go index 1dc3bb4..3a9fb0a 100644 --- a/v2/pkg/handler/ldapopshelper.go +++ b/v2/pkg/handler/ldapopshelper.go @@ -102,6 +102,12 @@ func (l LDAPOpsHelper) Bind(ctx context.Context, h LDAPOpsHandler, bindDN, bindS return ldapcode, nil } + // a disabled account must never authenticate, on any backend + if user.Disabled { + h.GetLog().Info().Str("binddn", bindDN).Str("src", conn.RemoteAddr().String()).Msg("Bind attempt on disabled account") + return ldap.LDAPResultInvalidCredentials, nil + } + validotp := false if len(user.Yubikey) == 0 && len(user.OTPSecret) == 0 {