diff --git a/core/ldapauth/ldapauth.go b/core/ldapauth/ldapauth.go index 33f79f90e..9af22d4a5 100644 --- a/core/ldapauth/ldapauth.go +++ b/core/ldapauth/ldapauth.go @@ -176,7 +176,7 @@ func Sources(ctx context.Context) []AuthSource { func Authenticate(ctx context.Context, ds model.DataStore, sourceID, username, password string) (*model.User, error) { if sourceID == "" || sourceID == "internal" { - u, found, err := authInternal(ctx, ds, username, password) + u, found, err := authInternal(ctx, ds, username, password, sourceID == "") if err != nil || found { return u, err } @@ -205,7 +205,7 @@ func Authenticate(ctx context.Context, ds model.DataStore, sourceID, username, p } return nil, nil } -func authInternal(ctx context.Context, ds model.DataStore, username, password string) (*model.User, bool, error) { +func authInternal(ctx context.Context, ds model.DataStore, username, password string, skipExternal bool) (*model.User, bool, error) { u, err := ds.User(ctx).FindByUsernameWithPassword(username) if errors.Is(err, model.ErrNotFound) { return nil, false, nil @@ -213,6 +213,10 @@ func authInternal(ctx context.Context, ds model.DataStore, username, password st if err != nil { return nil, true, err } + if skipExternal && u.AuthSource != "" { + log.Debug(ctx, "Skipping internal password check for externally managed user", "username", username, "authSource", u.AuthSource, "authSourceId", u.AuthSourceID) + return nil, false, nil + } if u.Password != password { return nil, true, nil } diff --git a/core/ldapauth/ldapauth_test.go b/core/ldapauth/ldapauth_test.go index d1d10d615..da831ff1c 100644 --- a/core/ldapauth/ldapauth_test.go +++ b/core/ldapauth/ldapauth_test.go @@ -1,6 +1,11 @@ package ldapauth -import "testing" +import ( + "testing" + + "github.com/navidrome/navidrome/model" + "github.com/navidrome/navidrome/tests" +) func TestLoginUserFilter(t *testing.T) { t.Parallel() @@ -55,3 +60,27 @@ func TestDedupeStrings(t *testing.T) { } } } + +func TestAuthInternalSkipsExternalUsersForFallback(t *testing.T) { + t.Parallel() + + repo := tests.CreateMockUserRepo() + repo.Data["firehawk"] = &model.User{ID: "1", UserName: "firehawk", Password: "generated", AuthSource: "ldap", AuthSourceID: "freeipa"} + ds := &tests.MockDataStore{MockedUser: repo} + + user, found, err := authInternal(t.Context(), ds, "firehawk", "ldap-password", true) + if err != nil { + t.Fatalf("authInternal() unexpected error: %v", err) + } + if found || user != nil { + t.Fatalf("authInternal() found=%v user=%#v, want external user to be skipped for fallback", found, user) + } + + user, found, err = authInternal(t.Context(), ds, "firehawk", "ldap-password", false) + if err != nil { + t.Fatalf("authInternal() unexpected error: %v", err) + } + if !found || user != nil { + t.Fatalf("authInternal() found=%v user=%#v, want explicit internal auth to stop", found, user) + } +}