/
githubmirror
/
photoprism
Обзор
Документация
Войти
/
githubmirror
/
photoprism
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
develop
internal/entity/auth_user_test.go
2 888 строк
77 KB
Michael Mayer
Downloads: Add signed tokens and file-level album selection #5733 #5743
24 июл 2026, 18:55
24 июл 2026, 18:55
1cea6cc
Код
Авторство
О чём код?
package entity import ( "flag" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/zitadel/oidc/v3/pkg/oidc" "github.com/urfave/cli/v2" "github.com/photoprism/photoprism/internal/auth/acl" "github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/pkg/authn" "github.com/photoprism/photoprism/pkg/list" "github.com/photoprism/photoprism/pkg/rnd" "github.com/photoprism/photoprism/pkg/time/unix" ) func createScopedTestUser(t *testing.T) *User { t.Helper() user := NewUser() user.UserName = "scope-" + rnd.Base36(6) user.DisplayName = "Scoped User" user.UserEmail = user.UserName + "@example.com" user.UserRole = acl.RoleAdmin.String() user.SuperAdmin = true user.CanLogin = true require.NoError(t, user.Create()) t.Cleanup(func() { if err := UnscopedDb().Delete(&Password{}, "uid = ?", user.UserUID).Error; err != nil { t.Fatalf("cleanup password: %v", err) } if err := UnscopedDb().Delete(&User{}, "user_uid = ?", user.UserUID).Error; err != nil { t.Fatalf("cleanup user: %v", err) } }) return user } func TestNewUser(t *testing.T) { m := NewUser() assert.True(t, rnd.IsRefID(m.RefID)) assert.True(t, rnd.IsUID(m.UserUID, UserUID)) } func TestOidcUser(t *testing.T) { t.Run("Success", func(t *testing.T) { info := &oidc.UserInfo{} info.Name = "Jane Doe" info.GivenName = "Jane" info.FamilyName = "Doe" info.Email = "jane@doe.com" info.EmailVerified = true info.Subject = "e3a9f4a6-9d60-47cb-9bf5-02bd15b0c68d" info.PreferredUsername = "Jane Doe" m := OidcUser(info, "", "jane.doe") assert.Equal(t, "oidc", m.AuthProvider) assert.Equal(t, "", m.AuthIssuer) assert.Equal(t, "e3a9f4a6-9d60-47cb-9bf5-02bd15b0c68d", m.AuthID) assert.Equal(t, "jane@doe.com", m.UserEmail) assert.Equal(t, "jane.doe", m.UserName) assert.Equal(t, "Jane Doe", m.DisplayName) }) t.Run("LongNumberAsSubject", func(t *testing.T) { info := &oidc.UserInfo{} info.Name = "Jane Doe" info.GivenName = "Jane" info.FamilyName = "Doe" info.Email = "jane@doe.com" info.EmailVerified = true info.Subject = "12345678901234567890" info.PreferredUsername = "Jane Doe" m := OidcUser(info, "", "jane.doe") assert.Equal(t, "oidc", m.AuthProvider) assert.Equal(t, "", m.AuthIssuer) assert.Equal(t, "12345678901234567890", m.AuthID) assert.Equal(t, "jane@doe.com", m.UserEmail) assert.Equal(t, "jane.doe", m.UserName) assert.Equal(t, "Jane Doe", m.DisplayName) }) t.Run("NoUsername", func(t *testing.T) { info := &oidc.UserInfo{} info.Name = "Jane Doe" info.GivenName = "Jane" info.FamilyName = "Doe" info.Email = "jane@doe.com" info.EmailVerified = true info.Subject = "e3a9f4a6-9d60-47cb-9bf5-02bd15b0c68d" info.PreferredUsername = "Jane Doe" m := OidcUser(info, "https://accounts.google.com", "") assert.Equal(t, "oidc", m.AuthProvider) assert.Equal(t, "https://accounts.google.com", m.AuthIssuer) assert.Equal(t, "e3a9f4a6-9d60-47cb-9bf5-02bd15b0c68d", m.AuthID) assert.Equal(t, "jane@doe.com", m.UserEmail) assert.Equal(t, "", m.UserName) assert.Equal(t, "Jane Doe", m.DisplayName) }) t.Run("NoSubject", func(t *testing.T) { info := &oidc.UserInfo{} info.Name = "Jane Doe" info.GivenName = "Jane" info.FamilyName = "Doe" info.Nickname = "Jens Mander" info.Email = "jane@doe.com" info.EmailVerified = true info.Subject = "" m := OidcUser(info, "https://accounts.google.com", "jane.doe") assert.Equal(t, "", m.AuthProvider) assert.Equal(t, "", m.AuthIssuer) assert.Equal(t, "", m.AuthID) assert.Equal(t, "", m.UserEmail) assert.Equal(t, "", m.UserName) assert.Equal(t, "", m.DisplayName) }) } func TestLdapUser(t *testing.T) { m := LdapUser("user-ldap", "ldap@test.com") assert.Equal(t, "ldap", m.AuthProvider) } func TestFindLocalUser(t *testing.T) { t.Run("Admin", func(t *testing.T) { m := FindLocalUser("admin") if m == nil { t.Fatal("result must not be nil") } assert.Equal(t, 1, m.ID) assert.NotEmpty(t, m.UserUID) assert.Equal(t, "admin", m.UserName) assert.Equal(t, "admin", m.Username()) m.UserName = "Admin " assert.Equal(t, "admin", m.Username()) assert.Equal(t, "Admin ", m.UserName) assert.Equal(t, "Admin", m.DisplayName) assert.Equal(t, acl.RoleAdmin, m.AclRole()) assert.Equal(t, "", m.Attr()) assert.False(t, m.IsVisitor()) assert.False(t, m.HasSharedAccessOnly(acl.ResourcePhotos)) assert.False(t, m.HasSharedAccessOnly(acl.ResourceAlbums)) assert.True(t, m.SuperAdmin) assert.True(t, m.CanLogin) assert.True(t, m.CanInvite) assert.NotEmpty(t, m.CreatedAt) assert.NotEmpty(t, m.UpdatedAt) }) t.Run("Alice", func(t *testing.T) { m := FindLocalUser("alice") if m == nil { t.Fatal("result must not be nil") } assert.Equal(t, 5, m.ID) assert.Equal(t, "uqxetse3cy5eo9z2", m.UserUID) assert.Equal(t, "alice", m.UserName) assert.Equal(t, "Alice", m.DisplayName) assert.Equal(t, "alice@example.com", m.UserEmail) assert.True(t, m.SuperAdmin) assert.Equal(t, acl.RoleAdmin, m.AclRole()) assert.NotEqual(t, acl.RoleVisitor, m.AclRole()) assert.False(t, m.IsVisitor()) assert.False(t, m.HasSharedAccessOnly(acl.ResourcePhotos)) assert.False(t, m.HasSharedAccessOnly(acl.ResourceAlbums)) assert.True(t, m.CanLogin) assert.NotEmpty(t, m.CreatedAt) assert.NotEmpty(t, m.UpdatedAt) }) t.Run("Bob", func(t *testing.T) { m := FindLocalUser("bob") if m == nil { t.Fatal("result must not be nil") } assert.Equal(t, 7, m.ID) assert.Equal(t, "uqxc08w3d0ej2283", m.UserUID) assert.Equal(t, "bob", m.UserName) assert.Equal(t, "Robert Rich", m.DisplayName) assert.Equal(t, "bob@example.com", m.UserEmail) assert.False(t, m.SuperAdmin) assert.False(t, m.IsVisitor()) assert.False(t, m.HasSharedAccessOnly(acl.ResourcePhotos)) assert.False(t, m.HasSharedAccessOnly(acl.ResourceAlbums)) assert.True(t, m.CanLogin) assert.NotEmpty(t, m.CreatedAt) assert.NotEmpty(t, m.UpdatedAt) }) t.Run("Unknown", func(t *testing.T) { m := FindLocalUser("") if m != nil { t.Fatal("result should be nil") } }) t.Run("NotFound", func(t *testing.T) { m := FindLocalUser("xxx") if m != nil { t.Fatal("result should be nil") } }) } func TestFindUserByName(t *testing.T) { t.Run("Admin", func(t *testing.T) { m := FindUserByName("admin") if m == nil { t.Fatal("result must not be nil") } assert.Equal(t, 1, m.ID) assert.NotEmpty(t, m.UserUID) assert.Equal(t, "admin", m.UserName) assert.Equal(t, "admin", m.Username()) m.UserName = "Admin " assert.Equal(t, "admin", m.Username()) assert.Equal(t, "Admin ", m.UserName) assert.Equal(t, "Admin", m.DisplayName) assert.Equal(t, acl.RoleAdmin, m.AclRole()) assert.Equal(t, "", m.Attr()) assert.False(t, m.IsVisitor()) assert.True(t, m.SuperAdmin) assert.True(t, m.CanLogin) assert.True(t, m.CanInvite) assert.NotEmpty(t, m.CreatedAt) assert.NotEmpty(t, m.UpdatedAt) }) t.Run("Alice", func(t *testing.T) { m := FindUserByName("alice") if m == nil { t.Fatal("result must not be nil") } assert.Equal(t, 5, m.ID) assert.Equal(t, "uqxetse3cy5eo9z2", m.UserUID) assert.Equal(t, "alice", m.UserName) assert.Equal(t, "Alice", m.DisplayName) assert.Equal(t, "alice@example.com", m.UserEmail) assert.True(t, m.SuperAdmin) assert.Equal(t, acl.RoleAdmin, m.AclRole()) assert.NotEqual(t, acl.RoleVisitor, m.AclRole()) assert.False(t, m.IsVisitor()) assert.True(t, m.CanLogin) assert.NotEmpty(t, m.CreatedAt) assert.NotEmpty(t, m.UpdatedAt) }) t.Run("Bob", func(t *testing.T) { m := FindUserByName("bob") if m == nil { t.Fatal("result must not be nil") } assert.Equal(t, 7, m.ID) assert.Equal(t, "uqxc08w3d0ej2283", m.UserUID) assert.Equal(t, "bob", m.UserName) assert.Equal(t, "Robert Rich", m.DisplayName) assert.Equal(t, "bob@example.com", m.UserEmail) assert.False(t, m.SuperAdmin) assert.False(t, m.IsVisitor()) assert.True(t, m.CanLogin) assert.NotEmpty(t, m.CreatedAt) assert.NotEmpty(t, m.UpdatedAt) }) t.Run("Unknown", func(t *testing.T) { m := FindUserByName("") if m != nil { t.Fatal("result should be nil") } }) t.Run("NotFound", func(t *testing.T) { m := FindUserByName("xxx") if m != nil { t.Fatal("result should be nil") } }) } func TestUser_Create(t *testing.T) { t.Run("Slug", func(t *testing.T) { var m = User{ UserName: "example", UserRole: acl.RoleAdmin.String(), DisplayName: "Example", SuperAdmin: false, CanLogin: true, } if err := m.Create(); err != nil { t.Fatal(err) } assert.Equal(t, "example", m.Username()) assert.Equal(t, "example", m.UserName) if err := m.UpdateUsername("example-editor"); err == nil { t.Fatal("error expected") } }) t.Run("NewUser", func(t *testing.T) { if err := NewUser().Create(); err != nil { t.Fatal(err) } }) t.Run("LongNumericAuthID", func(t *testing.T) { useruid := rnd.GenerateUID(UserUID) var m = User{ UserUID: useruid, UserName: "examplelong", UserRole: string(acl.RoleGuest), DisplayName: "Example Long", SuperAdmin: false, CanLogin: true, AuthID: "012345678901234567890", AuthProvider: string(authn.ProviderOIDC), } if err := m.Create(); err != nil { t.Fatal(err) } t.Cleanup(func() { assert.NoError(t, m.Delete()) UnscopedDb().Delete(m) }) assert.Equal(t, "examplelong", m.Username()) assert.Equal(t, "examplelong", m.UserName) assert.Equal(t, "012345678901234567890", m.AuthID) }) } func TestUser_UpdateUsername(t *testing.T) { t.Run("Exists", func(t *testing.T) { var m = User{ ID: 2, UserName: "foo", UserRole: acl.RoleAdmin.String(), DisplayName: "Admin", SuperAdmin: false, CanLogin: true, } if err := m.UpdateUsername("admin"); err == nil { t.Fatal("error expected") } else { t.Logf("expected error: %s", err) } }) t.Run("Success", func(t *testing.T) { var m = User{ ID: 5000, UserName: "", UserRole: "user", DisplayName: "Foo", SuperAdmin: false, CanLogin: true, } err := m.Save() if err != nil { t.Fatal(err) } assert.Nil(t, FindUserByName("bar")) err2 := m.UpdateUsername("bar") if err2 != nil { t.Fatal(err2) } assert.NotNil(t, FindUserByName("bar")) }) } func TestUser_SetUsername(t *testing.T) { t.Run("PhotoPrism", func(t *testing.T) { m := FindUserByName("admin") if m == nil { t.Fatal("result must not be nil") } assert.Equal(t, "admin", m.Username()) assert.Equal(t, "admin", m.UserName) if err := m.SetUsername("photoprism"); err != nil { t.Fatal(err) } assert.Equal(t, "photoprism", m.Username()) assert.Equal(t, "photoprism", m.UserName) }) t.Run("SystemUsersCannotBeModified", func(t *testing.T) { assert.Equal(t, "system users cannot be modified", Visitor.SetUsername("newname").Error()) }) t.Run("UsernameIsEmpty", func(t *testing.T) { assert.Equal(t, "username is empty", Admin.SetUsername("").Error()) }) t.Run("SameName", func(t *testing.T) { assert.Nil(t, Admin.SetUsername("admin")) }) } func TestUser_InvalidPassword(t *testing.T) { t.Run("Admin", func(t *testing.T) { m := FindUserByName("admin") if m == nil { t.Fatal("result must not be nil") } assert.False(t, m.InvalidPassword("photoprism")) }) t.Run("AdminInvalidPassword", func(t *testing.T) { m := FindUserByName("admin") if m == nil { t.Fatal("result must not be nil") } assert.True(t, m.InvalidPassword("wrong-password")) }) t.Run("NoPasswordExisting", func(t *testing.T) { p := User{UserUID: "u000000000000010", UserName: "Hans", DisplayName: ""} err := p.Save() if err != nil { t.Fatal(err) } assert.True(t, p.InvalidPassword("abcdef")) }) t.Run("NotRegistered", func(t *testing.T) { p := User{UserUID: "u12", UserName: "", DisplayName: ""} assert.True(t, p.InvalidPassword("abcdef")) }) t.Run("PasswordEmpty", func(t *testing.T) { p := User{UserUID: "u000000000000011", UserName: "User", DisplayName: ""} assert.True(t, p.InvalidPassword("")) }) } func TestUser_Save(t *testing.T) { t.Run("Ok", func(t *testing.T) { p := User{} err := p.Save() if err != nil { t.Fatal(err) } }) t.Run("NewUser", func(t *testing.T) { if err := NewUser().Save(); err != nil { t.Fatal(err) } }) } func TestFirstOrCreateUser(t *testing.T) { t.Run("NotExisting", func(t *testing.T) { p := &User{ID: 555} result := FirstOrCreateUser(p) if result == nil { t.Fatal("result must not be nil") } assert.NotEmpty(t, result.ID) }) t.Run("Existing", func(t *testing.T) { p := &User{ID: 1234} err := p.Save() if err != nil { t.Fatal(err) } result := FirstOrCreateUser(p) if result == nil { t.Fatal("result must not be nil") } assert.NotEmpty(t, result.ID) }) } func TestUserEmailAvailable(t *testing.T) { alice := FindUserByName("alice") require.NotNil(t, alice) require.NotEmpty(t, alice.UserEmail) t.Run("UnusedEmail", func(t *testing.T) { assert.True(t, UserEmailAvailable("brand-new-unused-email@example.com", "")) }) t.Run("TakenByOtherAccount", func(t *testing.T) { assert.False(t, UserEmailAvailable(alice.UserEmail, "")) assert.False(t, UserEmailAvailable(alice.UserEmail, "u00000000000other")) }) t.Run("FreeForSameAccount", func(t *testing.T) { assert.True(t, UserEmailAvailable(alice.UserEmail, alice.UserUID)) }) t.Run("EmptyEmail", func(t *testing.T) { assert.False(t, UserEmailAvailable("", "")) assert.False(t, UserEmailAvailable(" ", "")) }) } func TestUser_EmailVerified(t *testing.T) { t.Run("NilVerifiedAt", func(t *testing.T) { assert.False(t, (&User{}).EmailVerified()) }) t.Run("ZeroVerifiedAt", func(t *testing.T) { zero := time.Time{} assert.False(t, (&User{VerifiedAt: &zero}).EmailVerified()) }) t.Run("SetVerifiedAt", func(t *testing.T) { assert.True(t, (&User{VerifiedAt: TimeStamp()}).EmailVerified()) }) } func TestFindUser(t *testing.T) { t.Run("ID", func(t *testing.T) { m := FindUser(User{ID: 1}) if m == nil { t.Fatal("result must not be nil") } assert.Equal(t, 1, m.ID) assert.NotEmpty(t, m.UserUID) assert.Equal(t, "admin", m.UserName) assert.NotEmpty(t, m.CreatedAt) assert.NotEmpty(t, m.UpdatedAt) }) t.Run("Admin", func(t *testing.T) { m := FindUser(User{ID: 2, UserName: "admin"}) if m == nil { t.Fatal("result must not be nil") } assert.Equal(t, 1, m.ID) assert.NotEmpty(t, m.UserUID) assert.Equal(t, "admin", m.UserName) assert.NotEmpty(t, m.CreatedAt) assert.NotEmpty(t, m.UpdatedAt) }) t.Run("UserUID", func(t *testing.T) { m := FindUser(User{UserUID: "u000000000000002"}) if m == nil { t.Fatal("result must not be nil") } assert.Equal(t, -2, m.ID) assert.NotEmpty(t, m.UserUID) assert.Equal(t, "", m.UserName) assert.Equal(t, "Visitor", m.DisplayName) assert.NotEmpty(t, m.CreatedAt) assert.NotEmpty(t, m.UpdatedAt) }) t.Run("OIDCUser", func(t *testing.T) { info := &oidc.UserInfo{} info.Name = "Jane Doe" info.Email = "jane@doe.com" info.EmailVerified = true info.Subject = "e3a9f4a6-9d60-47cb-9bf5-02bd15b0c888" info.PreferredUsername = "Jane Doe" u := OidcUser(info, "", "jane.doe") err := u.Save() if err != nil { t.Fatal(err) } m := FindUser(User{UserUID: u.UserUID}) if m == nil { t.Fatal("result must not be nil") } assert.NotEmpty(t, m.UserUID) assert.Equal(t, "jane.doe", m.UserName) assert.Equal(t, "oidc", m.AuthProvider) n := FindUser(User{AuthProvider: authn.ProviderOIDC.String(), AuthID: info.Subject}) require.NotNil(t, n) assert.NotEmpty(t, n.UserUID) assert.Equal(t, "jane.doe", n.UserName) assert.Equal(t, "oidc", n.AuthProvider) }) t.Run("UserName", func(t *testing.T) { m := FindUser(User{UserName: "admin"}) if m == nil { t.Fatal("result must not be nil") } assert.Equal(t, 1, m.ID) assert.NotEmpty(t, m.UserUID) assert.Equal(t, "admin", m.UserName) assert.NotEmpty(t, m.CreatedAt) assert.NotEmpty(t, m.UpdatedAt) }) t.Run("Unknown", func(t *testing.T) { m := FindUser(User{}) if m != nil { t.Fatal("result should be nil") } }) t.Run("NotFound", func(t *testing.T) { m := FindUser(User{UserUID: "xxx"}) if m != nil { t.Fatal("result should be nil") } }) } func TestFindUserByUID(t *testing.T) { t.Run("Visitor", func(t *testing.T) { m := FindUserByUID("u000000000000002") if m == nil { t.Fatal("result must not be nil") } assert.Equal(t, -2, m.ID) assert.NotEmpty(t, m.UserUID) assert.Equal(t, "", m.UserName) assert.Equal(t, "Visitor", m.DisplayName) assert.NotEmpty(t, m.CreatedAt) assert.NotEmpty(t, m.UpdatedAt) }) t.Run("Unknown", func(t *testing.T) { m := FindUserByUID("") if m != nil { t.Fatal("result should be nil") } }) t.Run("NotFound", func(t *testing.T) { m := FindUserByUID("xxx") if m != nil { t.Fatal("result should be nil") } }) t.Run("Alice", func(t *testing.T) { m := FindUserByUID("uqxetse3cy5eo9z2") if m == nil { t.Fatal("result must not be nil") } assert.Equal(t, 5, m.ID) assert.Equal(t, "uqxetse3cy5eo9z2", m.UserUID) assert.Equal(t, "alice", m.Username()) assert.Equal(t, "Alice", m.DisplayName) assert.Equal(t, "alice@example.com", m.UserEmail) assert.True(t, m.SuperAdmin) assert.True(t, m.IsAdmin()) assert.True(t, m.IsSuperAdmin()) assert.False(t, m.IsVisitor()) assert.True(t, m.CanLogin) assert.NotEmpty(t, m.CreatedAt) assert.NotEmpty(t, m.UpdatedAt) }) t.Run("Bob", func(t *testing.T) { m := FindUserByUID("uqxc08w3d0ej2283") if m == nil { t.Fatal("result must not be nil") } assert.Equal(t, 7, m.ID) assert.Equal(t, "uqxc08w3d0ej2283", m.UserUID) assert.Equal(t, "bob", m.UserName) assert.Equal(t, "Robert Rich", m.DisplayName) assert.Equal(t, "bob@example.com", m.UserEmail) assert.False(t, m.SuperAdmin) assert.True(t, m.IsAdmin()) assert.False(t, m.IsSuperAdmin()) assert.False(t, m.IsVisitor()) assert.True(t, m.CanLogin) assert.NotEmpty(t, m.CreatedAt) assert.NotEmpty(t, m.UpdatedAt) }) t.Run("Friend", func(t *testing.T) { m := FindUserByUID("uqxqg7i1kperxvu7") if m == nil { t.Fatal("result must not be nil") } assert.Equal(t, 8, m.ID) assert.Equal(t, "uqxqg7i1kperxvu7", m.UserUID) assert.False(t, m.SuperAdmin) assert.True(t, m.IsAdmin()) assert.False(t, m.IsVisitor()) assert.True(t, m.CanLogin) assert.NotEmpty(t, m.CreatedAt) assert.NotEmpty(t, m.UpdatedAt) assert.True(t, m.HasUID()) assert.False(t, m.InvalidUID()) }) } func TestUser_SameUID(t *testing.T) { t.Run("True", func(t *testing.T) { m := FindUserByUID("uqxc08w3d0ej2283") if m == nil { t.Fatal("result must not be nil") } assert.True(t, m.SameUID("uqxc08w3d0ej2283")) assert.True(t, m.HasUID()) assert.False(t, m.InvalidUID()) }) t.Run("False", func(t *testing.T) { m := FindUserByUID("uqxc08w3d0ej2283") if m == nil { t.Fatal("result must not be nil") } assert.False(t, m.SameUID("uqxc08w3d0ej2276")) }) t.Run("Invalid", func(t *testing.T) { m := FindUserByUID("uqxc08w3d0ej2283") if m == nil { t.Fatal("result must not be nil") } assert.False(t, m.SameUID("xxx")) }) } func TestUser_String(t *testing.T) { t.Run("UID", func(t *testing.T) { p := User{UserUID: "abc123", UserName: "", DisplayName: ""} assert.Equal(t, "abc123", p.String()) }) t.Run("FullName", func(t *testing.T) { p := User{UserUID: "abc123", UserName: "", DisplayName: "Test"} assert.Equal(t, "'Test'", p.String()) }) t.Run("UserName", func(t *testing.T) { p := User{UserUID: "abc123", UserName: "Super-User ", DisplayName: "Test"} assert.Equal(t, "'super-user'", p.String()) }) } func TestUser_Admin(t *testing.T) { t.Run("SuperAdmin", func(t *testing.T) { p := User{ID: 8, UserUID: "u000000000000008", UserName: "Hanna", DisplayName: "", SuperAdmin: true} assert.True(t, p.IsAdmin()) }) t.Run("RoleAdmin", func(t *testing.T) { p := User{ID: 8, UserUID: "u000000000000008", UserName: "Hanna", DisplayName: "", UserRole: acl.RoleAdmin.String()} assert.True(t, p.IsAdmin()) }) t.Run("NoID", func(t *testing.T) { p := User{UserUID: "u000000000000008", UserName: "Hanna", DisplayName: "", UserRole: acl.RoleAdmin.String()} assert.False(t, p.IsAdmin()) }) t.Run("False", func(t *testing.T) { p := User{ID: 8, UserUID: "u000000000000008", UserName: "Hanna", DisplayName: "", SuperAdmin: false, UserRole: ""} assert.False(t, p.IsAdmin()) }) } func TestUser_IsUnknown(t *testing.T) { t.Run("ID", func(t *testing.T) { p := User{ID: UnknownUser.ID, UserUID: "u000000000000008", UserName: "", DisplayName: "", SuperAdmin: false, UserRole: acl.RoleAdmin.String()} assert.True(t, p.IsUnknown()) }) t.Run("UID", func(t *testing.T) { p := User{ID: 123, UserUID: "", UserName: "Hanna", DisplayName: "", SuperAdmin: false, UserRole: acl.RoleAdmin.String()} assert.True(t, p.IsUnknown()) }) t.Run("Name", func(t *testing.T) { p := User{ID: 123, UserUID: "u000000000000008", UserName: "", DisplayName: "", SuperAdmin: false, UserRole: acl.RoleAdmin.String()} assert.False(t, p.IsUnknown()) }) t.Run("Role", func(t *testing.T) { p := User{ID: 123, UserUID: "u000000000000008", UserName: "Hanna", DisplayName: "", SuperAdmin: false, UserRole: ""} assert.True(t, p.IsUnknown()) }) t.Run("Admin", func(t *testing.T) { p := User{ID: 123, UserUID: "u000000000000008", UserName: "Hanna", DisplayName: "", SuperAdmin: false, UserRole: acl.RoleAdmin.String()} assert.False(t, p.IsUnknown()) }) t.Run("SuperAdmin", func(t *testing.T) { p := User{ID: 123, UserUID: "u000000000000008", UserName: "Hanna", DisplayName: "", SuperAdmin: true, UserRole: ""} assert.False(t, p.IsUnknown()) }) } func TestUser_IsVisitor(t *testing.T) { t.Run("True", func(t *testing.T) { p := User{UserUID: "u000000000000008", UserName: "Hanna", DisplayName: "", UserRole: acl.RoleVisitor.String()} assert.True(t, p.IsVisitor()) }) t.Run("Unknown", func(t *testing.T) { p := User{UserUID: "u000000000000008", UserName: "Hanna", DisplayName: ""} assert.False(t, p.IsVisitor()) }) t.Run("Admin", func(t *testing.T) { p := User{UserUID: "u000000000000008", UserName: "Hanna", DisplayName: "", UserRole: acl.RoleAdmin.String()} assert.False(t, p.IsVisitor()) }) } func TestUser_SetPassword(t *testing.T) { t.Run("Ok", func(t *testing.T) { m := User{ID: 8, UserUID: "u000000000000008", UserName: "Hanna", DisplayName: "", UserRole: acl.RoleAdmin.String()} assert.NoError(t, m.SetPassword("insecure")) assert.NoError(t, m.DeletePassword()) assert.NoError(t, m.SetPassword("insecure")) }) t.Run("NotRegistered", func(t *testing.T) { m := User{ID: 0, UserUID: "", UserName: "Hanna", DisplayName: "", UserRole: acl.RoleAdmin.String()} assert.Error(t, m.SetPassword("insecure")) }) t.Run("PasswordTooShort", func(t *testing.T) { m := User{ID: 8, UserUID: "u000000000000008", UserName: "Hanna", DisplayName: "", UserRole: acl.RoleAdmin.String()} assert.Error(t, m.SetPassword("cat")) }) t.Run("PasswordTooLong", func(t *testing.T) { m := User{ID: 8, UserUID: "u000000000000008", UserName: "Hanna", DisplayName: "", UserRole: acl.RoleAdmin.String()} assert.Error(t, m.SetPassword("hfnoehurhgfoeuro7584othgiyruifh85hglhiryhgbbyeirygbubgirgtheuogfugfkhsbdgiyerbgeuigbdtiyrgehbik")) }) } func TestUser_InitAccount(t *testing.T) { t.Run("Ok", func(t *testing.T) { p := User{ID: 9, UserUID: "u000000000000009", UserName: "Hanna", DisplayName: "", UserRole: acl.RoleAdmin.String(), AuthProvider: authn.ProviderLocal.String(), CanLogin: true} assert.Nil(t, FindPassword("u000000000000009")) assert.True(t, p.InitAccount("Hanna", "insecure", "")) m := FindPassword("u000000000000009") if m == nil { t.Fatal("result must not be nil") } }) t.Run("AlreadyExists", func(t *testing.T) { p := User{ID: 10, UserUID: "u000000000000010", UserName: "Hans", DisplayName: "", UserRole: acl.RoleAdmin.String()} if err := p.Save(); err != nil { t.Logf("failed to create user: %s", err) } if err := p.SetPassword("insecure"); err != nil { t.Fatal(err) } assert.NotNil(t, FindPassword("u000000000000010")) assert.False(t, p.InitAccount("admin", "insecure", "")) m := FindPassword("u000000000000010") if m == nil { t.Fatal("result must not be nil") } }) t.Run("NotRegistered", func(t *testing.T) { p := User{UserUID: "u12", UserName: "", DisplayName: ""} assert.Nil(t, FindPassword("u12")) assert.False(t, p.InitAccount("admin", "insecure", "")) assert.Nil(t, FindPassword("u12")) }) t.Run("EmptyPassword", func(t *testing.T) { p := User{UserUID: "u000000000000011", UserName: "User", DisplayName: ""} assert.Nil(t, FindPassword("u000000000000011")) assert.False(t, p.InitAccount("admin", "", "")) assert.Nil(t, FindPassword("u000000000000011")) }) t.Run("SetsScope", func(t *testing.T) { user := createScopedTestUser(t) assert.True(t, user.InitAccount(user.UserName, "insecure", "photos:view")) assert.Equal(t, "photos:view", user.UserScope) assert.True(t, user.HasScope()) m := FindPassword(user.UserUID) if m == nil { t.Fatal("expected password to be created") } }) } func TestUser_AclRole(t *testing.T) { t.Run("SuperAdmin", func(t *testing.T) { p := User{ID: 8, UserUID: "u000000000000008", UserName: "Hanna", DisplayName: "", SuperAdmin: true, UserRole: ""} assert.Equal(t, acl.RoleAdmin, p.AclRole()) assert.True(t, p.IsAdmin()) assert.False(t, p.IsVisitor()) }) t.Run("RoleAdmin", func(t *testing.T) { p := User{ID: 8, UserUID: "u000000000000008", UserName: "Hanna", DisplayName: "", SuperAdmin: false, UserRole: acl.RoleAdmin.String()} assert.Equal(t, acl.RoleAdmin, p.AclRole()) assert.True(t, p.IsAdmin()) assert.False(t, p.IsVisitor()) }) t.Run("NoName", func(t *testing.T) { p := User{ID: 8, UserUID: "u000000000000008", UserName: "", DisplayName: "", UserRole: acl.RoleAdmin.String()} assert.Equal(t, acl.RoleVisitor, p.AclRole()) assert.False(t, p.IsAdmin()) assert.True(t, p.IsVisitor()) }) t.Run("NoID", func(t *testing.T) { p := User{ID: 0, UserUID: "u000000000000008", UserName: "Hanna", DisplayName: "", SuperAdmin: false, UserRole: acl.RoleAdmin.String()} assert.Equal(t, acl.RoleAdmin, p.AclRole()) assert.False(t, p.IsAdmin()) assert.False(t, p.IsVisitor()) }) t.Run("Unauthorized", func(t *testing.T) { p := User{ID: 8, UserUID: "u000000000000008", UserName: "Hanna", DisplayName: ""} assert.Equal(t, acl.RoleNone, p.AclRole()) assert.False(t, p.IsAdmin()) assert.False(t, p.IsVisitor()) }) } func TestUser_Validate(t *testing.T) { t.Run("NameValid", func(t *testing.T) { u := &User{ UserName: "validate", DisplayName: "Validate", UserEmail: "validate@example.com", UserRole: acl.RoleAdmin.String(), } assert.NoError(t, u.Validate()) }) t.Run("NameEmpty", func(t *testing.T) { u := &User{ UserName: "", DisplayName: "Validate", UserEmail: "validate@example.com", UserRole: acl.RoleAdmin.String(), } assert.Error(t, u.Validate()) }) t.Run("NameNotUnique", func(t *testing.T) { FirstOrCreateUser(&User{ UserName: "notunique1", }) u := &User{ UserName: "notunique1", DisplayName: "Not Unique", UserEmail: "notunique1@example.com", UserRole: acl.RoleAdmin.String(), } assert.Error(t, u.Validate()) }) t.Run("EmailNotUnique", func(t *testing.T) { FirstOrCreateUser(&User{ UserEmail: "notunique2@example.com", }) u := &User{ UserName: "notunique2", UserEmail: "notunique2@example.com", DisplayName: "Not Unique", UserRole: acl.RoleAdmin.String(), } assert.Error(t, u.Validate()) }) t.Run("EmailNotUnique", func(t *testing.T) { FirstOrCreateUser(&User{ UserName: "notunique3", UserEmail: "notunique3@example.com", DisplayName: "Not Unique", UserRole: acl.RoleAdmin.String(), }) u := FirstOrCreateUser(&User{ UserName: "notunique30", UserEmail: "notunique3@example.com", DisplayName: "Not Unique", UserRole: acl.RoleAdmin.String(), }) u.UserName = "notunique3" assert.Error(t, u.Validate()) }) t.Run("EmailInvalid", func(t *testing.T) { u := &User{ UserName: "12", DisplayName: "Validate", UserEmail: "validate", UserRole: acl.RoleAdmin.String(), } assert.Error(t, u.Validate()) }) t.Run("EmailInvalid2", func(t *testing.T) { u := &User{ UserName: "12", DisplayName: "Validate", UserEmail: "validate@", UserRole: acl.RoleAdmin.String(), } assert.Error(t, u.Validate()) }) t.Run("EmailEmpty", func(t *testing.T) { FirstOrCreateUser(&User{ UserName: "nnomail", }) u := &User{ UserName: "nomail", UserEmail: "", DisplayName: "No Mail", UserRole: acl.RoleAdmin.String(), } assert.NoError(t, u.Validate()) }) t.Run("RoleEmpty", func(t *testing.T) { u := &User{ UserName: "test.example", UserEmail: "test@example.com", DisplayName: "Test Example", UserRole: "", } assert.Error(t, u.Validate()) }) t.Run("RoleAdmin", func(t *testing.T) { u := &User{ UserName: "test.example", UserEmail: "test@example.com", DisplayName: "Test Example", UserRole: acl.RoleAdmin.String(), } assert.NoError(t, u.Validate()) }) t.Run("RoleInvalid", func(t *testing.T) { u := &User{ UserName: "test.example", UserEmail: "test@example.com", DisplayName: "Test Example", UserRole: "foobar", } assert.Error(t, u.Validate()) }) } func TestAddUser(t *testing.T) { t.Run("TooShort", func(t *testing.T) { u := form.User{ UserName: "thomas1", UserEmail: "thomas1@example.com", Password: "hel", } err := AddUser(u) assert.Error(t, err) }) t.Run("TooLong", func(t *testing.T) { u := form.User{ UserName: "thomas3", UserEmail: "thomas3@example.com", Password: "1234567725244364789969hhkvnsgjlb;ghfnbn nd;dhewy8ortfgbkryeti7gfbie57yteoubgvlsiruwojflger", UserRole: acl.RoleAdmin.String(), } err := AddUser(u) assert.Error(t, err) }) t.Run("InvalidRole", func(t *testing.T) { u := form.User{ UserName: "thomas4", UserEmail: "thomas4@example.com", Password: "helloworld", UserRole: "invalid", } err := AddUser(u) assert.Error(t, err) }) t.Run("ValidLocalUser", func(t *testing.T) { u := form.User{ UserName: "thomas2", UserEmail: "thomas2@example.com", Password: "helloworld", UserRole: acl.RoleAdmin.String(), AuthProvider: "local", } err := AddUser(u) assert.Nil(t, err) }) t.Run("ValidOidcUser", func(t *testing.T) { u := form.User{ UserName: "thomasoidc", UserEmail: "thomasoidc@example.com", Password: "helloworld", UserRole: acl.RoleAdmin.String(), AuthProvider: "oidc", AuthID: "12378696", } err := AddUser(u) assert.Nil(t, err) }) t.Run("AuthIDMissing", func(t *testing.T) { u := form.User{ UserName: "thomasoidc", UserEmail: "thomasoidc@example.com", Password: "helloworld", UserRole: acl.RoleAdmin.String(), AuthProvider: "oidc", } err := AddUser(u) assert.Error(t, err) }) } func TestDeleteUser(t *testing.T) { t.Run("Ok", func(t *testing.T) { u := &User{ UserName: "thomasdel", UserEmail: "thomasdel@example.com", DisplayName: "Thomas Delete", UserRole: acl.RoleAdmin.String(), } u = FirstOrCreateUser(u) err := u.Delete() assert.NoError(t, err) }) t.Run("DoesNotExist", func(t *testing.T) { u := &User{ UserName: "thomasdel2", UserEmail: "thomasdel2@example.com", DisplayName: "Thomas Delete 2", UserRole: acl.RoleNone.String(), } err := u.Delete() assert.Error(t, err) }) t.Run("EmptyUid", func(t *testing.T) { u := NewUser() u.UserUID = "" u.ID = 500 err := u.Delete() assert.Error(t, err) }) } func TestUser_Deleted(t *testing.T) { assert.False(t, UserFixtures.Pointer("alice").IsDeleted()) assert.True(t, UserFixtures.Pointer("deleted").IsDeleted()) } func TestUser_Expired(t *testing.T) { t.Run("False", func(t *testing.T) { assert.False(t, UserFixtures.Pointer("alice").IsExpired()) assert.False(t, UserFixtures.Pointer("deleted").IsExpired()) }) t.Run("True", func(t *testing.T) { u := NewUser() var expired = time.Date(2020, 3, 6, 2, 6, 51, 0, time.UTC) u.ExpiresAt = &expired assert.True(t, u.IsExpired()) }) } func TestUser_Disabled(t *testing.T) { assert.False(t, UserFixtures.Pointer("alice").IsDisabled()) assert.True(t, UserFixtures.Pointer("deleted").IsDisabled()) } func TestUser_UpdateLoginTime(t *testing.T) { t.Run("Success", func(t *testing.T) { alice := UserFixtures.Get("alice") time1 := alice.LoginAt assert.Nil(t, time1) alice.UpdateLoginTime() time2 := alice.LoginAt assert.NotNil(t, time2) alice.UpdateLoginTime() time3 := alice.LoginAt assert.NotNil(t, time3) assert.True(t, time3.After(*time2) || time3.Equal(*time2)) }) t.Run("UserDeleted", func(t *testing.T) { u := NewUser() var deleted = time.Date(2020, 3, 6, 2, 6, 51, 0, time.UTC) u.DeletedAt = &deleted assert.Nil(t, u.UpdateLoginTime()) }) t.Run("NormalizesOutOfEditionSuperAdminRole", func(t *testing.T) { u := NewUser() u.UserName = "normalize-login" u.DisplayName = "Normalize Login" u.SuperAdmin = true u.UserRole = acl.RoleAdmin.String() u.CanLogin = true if err := u.Save(); err != nil { t.Fatal(err) } // Simulate a leftover out-of-edition role written outside this edition's // validation (e.g. a Portal cluster_admin row on a Plus instance). if err := Db().Model(u).UpdateColumn("UserRole", acl.RoleClusterAdmin.String()).Error; err != nil { t.Fatal(err) } u.UserRole = acl.RoleClusterAdmin.String() // Logging in normalizes the stored role back to the edition's admin role. // CE/Plus/Pro register only admin (not cluster_admin), so the target is admin. u.UpdateLoginTime() assert.Equal(t, acl.RoleAdmin.String(), u.UserRole) m := FindUser(User{UserUID: u.UserUID}) if m == nil { t.Fatal("result must not be nil") } assert.Equal(t, acl.RoleAdmin.String(), m.UserRole) }) } func TestUser_NormalizeAdminRole(t *testing.T) { // CE registers admin but not cluster_admin, so the edition target is admin. t.Run("ForeignSuperAdminRoleReset", func(t *testing.T) { m := &User{UserName: "norm-foreign", SuperAdmin: true, UserRole: acl.RoleClusterAdmin.String()} assert.True(t, m.NormalizeAdminRole()) assert.Equal(t, acl.RoleAdmin.String(), m.UserRole) }) t.Run("RegisteredAdminRoleKept", func(t *testing.T) { m := &User{UserName: "norm-admin", SuperAdmin: true, UserRole: acl.RoleAdmin.String()} assert.False(t, m.NormalizeAdminRole()) assert.Equal(t, acl.RoleAdmin.String(), m.UserRole) }) t.Run("NonAdminTierSuperAdminRoleReset", func(t *testing.T) { m := &User{UserName: "norm-guest", SuperAdmin: true, UserRole: acl.RoleGuest.String()} assert.True(t, m.NormalizeAdminRole()) assert.Equal(t, acl.RoleAdmin.String(), m.UserRole) }) t.Run("RegularAccountUntouched", func(t *testing.T) { m := &User{UserName: "norm-regular", SuperAdmin: false, UserRole: acl.RoleClusterAdmin.String()} assert.False(t, m.NormalizeAdminRole()) assert.Equal(t, acl.RoleClusterAdmin.String(), m.UserRole) }) t.Run("NilUser", func(t *testing.T) { var m *User assert.False(t, m.NormalizeAdminRole()) }) } func TestUser_CanLogIn(t *testing.T) { t.Run("True", func(t *testing.T) { alice := UserFixtures.Get("alice") assert.True(t, alice.CanLogIn()) alice.SetProvider(authn.ProviderNone) assert.False(t, alice.CanLogIn()) alice.SetProvider(authn.ProviderLocal) assert.True(t, alice.CanLogIn()) assert.False(t, UserFixtures.Pointer("deleted").CanLogIn()) }) t.Run("FalseCanlogin", func(t *testing.T) { u := NewUser() u.AuthProvider = "local" u.CanLogin = false assert.False(t, u.CanLogIn()) }) t.Run("FalseUnknownRole", func(t *testing.T) { unknown := UserFixtures.Get("unauthorized") assert.False(t, unknown.CanLogIn()) unknown.SetProvider(authn.ProviderLocal) assert.False(t, unknown.CanLogIn()) unknown.SetProvider(authn.ProviderNone) assert.False(t, unknown.CanLogIn()) }) } func TestUser_CanUseWebDAV(t *testing.T) { alice := UserFixtures.Get("alice") assert.True(t, alice.CanUseWebDAV()) alice.SetProvider(authn.ProviderNone) assert.False(t, alice.CanUseWebDAV()) alice.SetProvider(authn.ProviderLocal) assert.True(t, alice.CanUseWebDAV()) assert.False(t, UserFixtures.Pointer("deleted").CanUseWebDAV()) assert.False(t, UserFixtures.Pointer("friend").CanUseWebDAV()) // Disabling web login must not affect WebDAV access: the API gate denies app // passwords when CanLogin is off, while WebDAV stays governed by CanUseWebDAV. // bob is a non-super-admin with WebDAV enabled (super admins always keep login). webdavUser := UserFixtures.Get("bob") webdavUser.CanLogin = false assert.False(t, webdavUser.CanLogIn()) assert.True(t, webdavUser.CanUseWebDAV()) } func TestUser_CanUpload(t *testing.T) { alice := UserFixtures.Get("alice") assert.True(t, alice.CanUpload()) alice.SetProvider(authn.ProviderNone) assert.False(t, alice.CanUpload()) alice.SetProvider(authn.ProviderLocal) assert.True(t, alice.CanUpload()) assert.False(t, UserFixtures.Pointer("deleted").CanUpload()) assert.True(t, UserFixtures.Pointer("friend").CanUpload()) unknown := UserFixtures.Get("unauthorized") assert.False(t, unknown.CanUpload()) unknown.SetProvider(authn.ProviderLocal) assert.False(t, unknown.CanUpload()) unknown.SetProvider(authn.ProviderNone) assert.False(t, unknown.CanUpload()) } func TestUser_SharedUIDs(t *testing.T) { t.Run("AliceAlbum", func(t *testing.T) { m := UserFixtures.Pointer("alice") assert.NotNil(t, m) result := m.SharedUIDs() assert.NotNil(t, result) assert.Len(t, result, 1) assert.Equal(t, UIDs{"as6sg6bxpogaaba9"}, result) }) } func TestUser_Form(t *testing.T) { t.Run("Alice", func(t *testing.T) { m := FindUserByName("alice") if m == nil { t.Fatal("result must not be nil") } frm, err := m.Form() if err != nil { t.Fatal(err) } t.Logf("User: %#v", m) t.Logf("User.UserDetails: %#v", m.UserDetails) t.Logf("Form: %#v", frm) t.Logf("Form.UserDetails: %#v", frm.UserDetails) }) } func TestUser_PrivilegeLevelChange(t *testing.T) { t.Run("TrueRoleChanged", func(t *testing.T) { m := FindUserByName("alice") if m == nil { t.Fatal("result must not be nil") } frm, err := m.Form() if err != nil { t.Fatal(err) } frm.UserRole = "guest" assert.True(t, m.PrivilegeLevelChange(frm)) }) t.Run("TruePathChanged", func(t *testing.T) { m := FindUserByName("alice") if m == nil { t.Fatal("result must not be nil") } frm, err := m.Form() if err != nil { t.Fatal(err) } frm.UploadPath = "uploads-alice" assert.True(t, m.PrivilegeLevelChange(frm)) }) t.Run("TrueCanLoginChanged", func(t *testing.T) { m := FindUserByName("alice") if m == nil { t.Fatal("result must not be nil") } frm, err := m.Form() if err != nil { t.Fatal(err) } frm.CanLogin = false assert.True(t, m.PrivilegeLevelChange(frm)) }) t.Run("TrueWebdavChanged", func(t *testing.T) { m := FindUserByName("alice") if m == nil { t.Fatal("result must not be nil") } frm, err := m.Form() if err != nil { t.Fatal(err) } frm.WebDAV = false assert.True(t, m.PrivilegeLevelChange(frm)) }) t.Run("FalseNameChanged", func(t *testing.T) { m := FindUserByName("alice") if m == nil { t.Fatal("result must not be nil") } frm, err := m.Form() if err != nil { t.Fatal(err) } frm.UserName = "alice-new" assert.False(t, m.PrivilegeLevelChange(frm)) }) t.Run("FalseNoChange", func(t *testing.T) { m := FindUserByName("alice") if m == nil { t.Fatal("result must not be nil") } frm, err := m.Form() if err != nil { t.Fatal(err) } assert.False(t, m.PrivilegeLevelChange(frm)) }) } func TestUser_SaveForm(t *testing.T) { t.Run("UnknownUser", func(t *testing.T) { frm, err := UnknownUser.Form() assert.NoError(t, err) err = UnknownUser.SaveForm(frm, UserFixtures.Pointer("guest"), false, false) assert.Error(t, err) }) t.Run("Admin", func(t *testing.T) { m := FindUser(Admin) if m == nil { t.Fatal("result must not be nil") } frm, err := m.Form() if err != nil { t.Fatal(err) } frm.UserEmail = "admin@example.com" frm.UserDetails.UserLocation = "GoLand" err = Admin.SaveForm(frm, UserFixtures.Pointer("guest"), false, false) assert.NoError(t, err) assert.Equal(t, "admin@example.com", Admin.UserEmail) assert.Equal(t, "GoLand", Admin.Details().UserLocation) m = FindUserByUID(Admin.UserUID) assert.Equal(t, "admin@example.com", m.UserEmail) assert.Equal(t, "GoLand", m.Details().UserLocation) }) t.Run("UpdateRights", func(t *testing.T) { m := FindUser(Admin) if m == nil { t.Fatal("result must not be nil") } frm, err := m.Form() if err != nil { t.Fatal(err) } frm.UserEmail = "admin@example.com" frm.UserDetails.UserLocation = "GoLand" err = Admin.SaveForm(frm, UserFixtures.Pointer("alice"), true, false) assert.NoError(t, err) assert.Equal(t, "admin@example.com", Admin.UserEmail) assert.Equal(t, "GoLand", Admin.Details().UserLocation) m = FindUserByUID(Admin.UserUID) assert.Equal(t, "admin@example.com", m.UserEmail) assert.Equal(t, "GoLand", m.Details().UserLocation) }) t.Run("ChangeDisplayName", func(t *testing.T) { m := FindUser(Admin) if m == nil { t.Fatal("result must not be nil") } frm, err := m.Form() if err != nil { t.Fatal(err) } frm.DisplayName = "New Name" err = Admin.SaveForm(frm, UserFixtures.Pointer("alice"), true, false) assert.NoError(t, err) assert.Equal(t, "New Name", Admin.DisplayName) m = FindUserByUID(Admin.UserUID) assert.Equal(t, "New Name", m.DisplayName) }) t.Run("AnotherSuperAdminCanDisableInitialAdminLogin", func(t *testing.T) { // The seed admin is no longer special-cased: another super admin may // disable its login (e.g. a cluster-managed instance that keeps no local // super admin). The acting admin still cannot lock itself out. m := FindUser(Admin) if m == nil { t.Fatal("result must not be nil") } frm, err := m.Form() if err != nil { t.Fatal(err) } frm.CanLogin = false err = Admin.SaveForm(frm, UserFixtures.Pointer("alice"), true, false) assert.NoError(t, err) assert.False(t, Admin.CanLogin) m = FindUserByUID(Admin.UserUID) assert.False(t, m.CanLogin) // Restore the seed admin so later subtests see a login-capable account. Admin.CanLogin = true if err = Admin.Save(); err != nil { t.Fatal(err) } }) t.Run("AnotherSuperAdminCanDemoteInitialAdmin", func(t *testing.T) { // With the hard ID-1 lock removed, a second super admin can fully demote // the seed admin (clear super admin, drop to a non-admin role, disable // login). The form must request SuperAdmin=false so the role normalization // does not re-promote it. m := FindUser(Admin) if m == nil { t.Fatal("result must not be nil") } frm, err := m.Form() if err != nil { t.Fatal(err) } frm.UserRole = acl.RoleGuest.String() frm.SuperAdmin = false frm.CanLogin = false err = Admin.SaveForm(frm, UserFixtures.Pointer("alice"), true, false) assert.NoError(t, err) assert.False(t, Admin.SuperAdmin) assert.Equal(t, acl.RoleGuest.String(), Admin.UserRole) assert.False(t, Admin.CanLogin) m = FindUserByUID(Admin.UserUID) assert.False(t, m.SuperAdmin) assert.Equal(t, acl.RoleGuest.String(), m.UserRole) assert.False(t, m.CanLogin) // Restore the seed admin so later subtests see a usable super admin. Admin.SuperAdmin = true Admin.CanLogin = true Admin.SetRole(acl.RoleAdmin.String()) if err = Admin.Save(); err != nil { t.Fatal(err) } }) t.Run("PreventInitialAdminFromDisablingOwnLogin", func(t *testing.T) { m := FindUser(Admin) if m == nil { t.Fatal("result must not be nil") } frm, err := m.Form() if err != nil { t.Fatal(err) } frm.CanLogin = false err = Admin.SaveForm(frm, &Admin, true, false) assert.NoError(t, err) assert.Equal(t, true, Admin.CanLogin) m = FindUserByUID(Admin.UserUID) assert.Equal(t, true, m.CanLogin) }) t.Run("PreventInitialAdminFromSettingAuthProviderNone", func(t *testing.T) { m := FindUser(Admin) if m == nil { t.Fatal("result must not be nil") } frm, err := m.Form() if err != nil { t.Fatal(err) } frm.AuthProvider = authn.ProviderNone.String() err = Admin.SaveForm(frm, &Admin, true, false) assert.NoError(t, err) assert.Equal(t, "local", Admin.AuthProvider) m = FindUserByUID(Admin.UserUID) assert.Equal(t, "local", m.AuthProvider) }) t.Run("PreventAdminFromSettingAuthProviderNone", func(t *testing.T) { alice := UserFixtures.Get("alice") m := FindUser(alice) if m == nil { t.Fatal("result must not be nil") } frm, err := m.Form() if err != nil { t.Fatal(err) } frm.AuthProvider = authn.ProviderNone.String() err = alice.SaveForm(frm, &alice, true, false) assert.NoError(t, err) assert.Equal(t, "local", alice.AuthProvider) m = FindUserByUID(alice.UserUID) assert.Equal(t, "local", m.AuthProvider) }) t.Run("AliceChangeAdminRights", func(t *testing.T) { user := UserFixtures.Get("alice") m := FindUser(user) if m == nil { t.Fatal("result must not be nil") } assert.Equal(t, "admin", m.UserRole) assert.True(t, m.SuperAdmin) assert.True(t, m.CanLogin) frm, err := m.Form() if err != nil { t.Fatal(err) } assert.Equal(t, "admin", frm.UserRole) assert.True(t, frm.SuperAdmin) assert.True(t, frm.CanLogin) frm.UserRole = "user" frm.SuperAdmin = false frm.CanLogin = false err = user.SaveForm(frm, &user, true, false) assert.NoError(t, err) assert.Equal(t, "admin", m.UserRole) assert.True(t, m.SuperAdmin) assert.True(t, m.CanLogin) }) t.Run("TryToChangeRoleOfInitialAdmin", func(t *testing.T) { m := FindUser(Admin) if m == nil { t.Fatal("result must not be nil") } frm, err := m.Form() if err != nil { t.Fatal(err) } frm.UserRole = "user" err = Admin.SaveForm(frm, UserFixtures.Pointer("guest"), false, false) assert.Error(t, err) assert.Equal(t, "super admin must not have a non-admin role", err.Error()) m = FindUserByUID(Admin.UserUID) assert.Equal(t, "admin", m.UserRole) }) t.Run("InvalidBasePath", func(t *testing.T) { m := FindUser(Admin) if m == nil { t.Fatal("result must not be nil") } frm, err := m.Form() if err != nil { t.Fatal(err) } frm.BasePath = "//*?" err = Admin.SaveForm(frm, &Admin, true, false) assert.Error(t, err) assert.Equal(t, "invalid base folder", err.Error()) m = FindUserByUID(Admin.UserUID) assert.Equal(t, "", m.BasePath) }) t.Run("InvalidUploadPath", func(t *testing.T) { m := FindUser(Admin) if m == nil { t.Fatal("result must not be nil") } frm, err := m.Form() if err != nil { t.Fatal(err) } frm.UploadPath = "//*?" err = Admin.SaveForm(frm, &Admin, true, false) assert.Error(t, err) assert.Equal(t, "invalid upload folder", err.Error()) m = FindUserByUID(Admin.UserUID) assert.Equal(t, "", m.UploadPath) }) t.Run("ClusterServicePrincipalDisablesLogin", func(t *testing.T) { // A trusted cluster service principal (e.g. the Portal syncing user // state) has no end-user identity, so SaveForm receives an unknown // actor. With byAdmin set it must still persist privilege fields such as // CanLogin; otherwise the Portal login toggle is silently ignored. u := &User{UserName: "cluster-login-off", UserRole: acl.RoleUser.String(), CanLogin: true} if err := u.Create(); err != nil { t.Fatal(err) } frm, err := u.Form() if err != nil { t.Fatal(err) } frm.CanLogin = false err = u.SaveForm(frm, &User{}, true, false) assert.NoError(t, err) assert.False(t, u.CanLogin) m := FindUserByUID(u.UserUID) assert.False(t, m.CanLogin) }) t.Run("UnprivilegedActorKeepsLogin", func(t *testing.T) { // Without admin authorization the privilege block is skipped, so // CanLogin stays unchanged even when the form requests false. u := &User{UserName: "cluster-login-keep", UserRole: acl.RoleUser.String(), CanLogin: true} if err := u.Create(); err != nil { t.Fatal(err) } frm, err := u.Form() if err != nil { t.Fatal(err) } frm.CanLogin = false err = u.SaveForm(frm, &User{}, false, false) assert.NoError(t, err) assert.True(t, u.CanLogin) m := FindUserByUID(u.UserUID) assert.True(t, m.CanLogin) }) t.Run("ClusterServicePrincipalDisables2FA", func(t *testing.T) { // Disabling 2FA is super-admin-level; the cluster principal isn't a super admin, so // without bySuperAdmin the auth method change would be dropped. u := &User{UserName: "cluster-2fa-off", UserRole: acl.RoleUser.String(), CanLogin: true, AuthMethod: authn.Method2FA.String()} if err := u.Create(); err != nil { t.Fatal(err) } frm, err := u.Form() if err != nil { t.Fatal(err) } frm.AuthMethod = authn.MethodDefault.String() err = u.SaveForm(frm, &User{}, true, true) assert.NoError(t, err) assert.False(t, u.Method().Is(authn.Method2FA)) m := FindUserByUID(u.UserUID) assert.False(t, m.Method().Is(authn.Method2FA)) }) t.Run("UnprivilegedAdminKeeps2FA", func(t *testing.T) { // A regular admin (byAdmin, not super-admin-level) must not disable another user's 2FA. u := &User{UserName: "admin-2fa-keep", UserRole: acl.RoleUser.String(), CanLogin: true, AuthMethod: authn.Method2FA.String()} if err := u.Create(); err != nil { t.Fatal(err) } frm, err := u.Form() if err != nil { t.Fatal(err) } frm.AuthMethod = authn.MethodDefault.String() err = u.SaveForm(frm, &User{}, true, false) assert.NoError(t, err) assert.True(t, u.Method().Is(authn.Method2FA)) m := FindUserByUID(u.UserUID) assert.True(t, m.Method().Is(authn.Method2FA)) }) } func TestUser_SetDisplayName(t *testing.T) { t.Run("BillGates", func(t *testing.T) { user := NewUser() user.SetDisplayName("Sir William Henry Gates III", SrcAuto) d := user.Details() assert.Equal(t, "Sir", d.NameTitle) assert.Equal(t, "William", d.GivenName) assert.Equal(t, "Henry Gates", d.FamilyName) assert.Equal(t, "III", d.NameSuffix) }) t.Run("EmptyName", func(t *testing.T) { user := NewUser() user.SetDisplayName("", SrcAuto) d := user.Details() assert.Equal(t, "", d.NameTitle) assert.Equal(t, "", d.GivenName) assert.Equal(t, "", d.FamilyName) assert.Equal(t, "", d.NameSuffix) }) t.Run("PriorityTooLow", func(t *testing.T) { user := User{ ID: 1234567, UserUID: "urqdrfb72479n047", UserDetails: &UserDetails{ GivenName: "John", MiddleName: "Percival", FamilyName: "Doe", NameSrc: "manual", }, } user.SetDisplayName("Sir William Henry Gates III", SrcAuto) d := user.Details() assert.Equal(t, "", d.NameTitle) assert.Equal(t, "John", d.GivenName) assert.Equal(t, "Doe", d.FamilyName) assert.Equal(t, "", d.NameSuffix) }) } func TestUser_SetGivenName(t *testing.T) { user := User{ ID: 1234567, UserUID: "urqdrfb72479n047", UserDetails: &UserDetails{ GivenName: "John", MiddleName: "Percival", FamilyName: "Doe", }, } assert.Equal(t, "John", user.Details().GivenName) user.SetGivenName("Jane") assert.Equal(t, "Jane", user.Details().GivenName) user.SetGivenName("") assert.Equal(t, "Jane", user.Details().GivenName) } func TestUser_SetFamilyName(t *testing.T) { user := User{ ID: 1234567, UserUID: "urqdrfb72479n047", UserDetails: &UserDetails{ GivenName: "John", MiddleName: "Percival", FamilyName: "Doe", }, } assert.Equal(t, "Doe", user.Details().FamilyName) user.SetFamilyName("Maier") assert.Equal(t, "Maier", user.Details().FamilyName) user.SetFamilyName("") assert.Equal(t, "Maier", user.Details().FamilyName) } func TestUser_SetAvatar(t *testing.T) { t.Run("Visitor", func(t *testing.T) { assert.False(t, Visitor.HasAvatar()) err := Visitor.SetAvatar("ebfc0aea7d3fd018b5fff57c76806b35181855ed", SrcManual) assert.Error(t, err) }) t.Run("UnknownUser", func(t *testing.T) { assert.False(t, UnknownUser.HasAvatar()) err := UnknownUser.SetAvatar("ebfc0aea7d3fd018b5fff57c76806b35181855ed", SrcManual) assert.Error(t, err) }) t.Run("Admin", func(t *testing.T) { assert.False(t, Admin.HasAvatar()) err := Admin.SetAvatar("ebfc0aea7d3fd018b5fff57c76806b35181855ed", SrcManual) assert.NoError(t, err) assert.Equal(t, "ebfc0aea7d3fd018b5fff57c76806b35181855ed", Admin.Thumb) assert.Equal(t, SrcManual, Admin.ThumbSrc) assert.True(t, Admin.HasAvatar()) m := FindUserByUID(Admin.UserUID) assert.Equal(t, "ebfc0aea7d3fd018b5fff57c76806b35181855ed", m.Thumb) assert.Equal(t, SrcManual, m.ThumbSrc) assert.True(t, Admin.HasAvatar()) }) t.Run("NoPermissions", func(t *testing.T) { err := Admin.SetAvatar("ebfc0aea7d3fd018b5fff57c76806b35181855ed", SrcAuto) assert.Error(t, err) }) } func TestUser_Username(t *testing.T) { t.Run("Visitor", func(t *testing.T) { assert.Equal(t, "", Visitor.Username()) }) t.Run("UnknownUser", func(t *testing.T) { assert.Equal(t, "", UnknownUser.Username()) }) t.Run("Admin", func(t *testing.T) { assert.Equal(t, "admin", Admin.Username()) }) } func TestUser_Provider(t *testing.T) { t.Run("Visitor", func(t *testing.T) { assert.Equal(t, authn.ProviderLink, Visitor.Provider()) Visitor.AuthProvider = "" assert.Equal(t, authn.ProviderLink, Visitor.Provider()) Visitor.SetProvider(authn.ProviderLink) }) t.Run("UnknownUser", func(t *testing.T) { assert.Equal(t, authn.ProviderNone, UnknownUser.Provider()) }) t.Run("Admin", func(t *testing.T) { assert.Equal(t, authn.ProviderLocal, Admin.Provider()) Admin.AuthProvider = "" assert.Equal(t, authn.ProviderLocal, Admin.Provider()) Admin.SetProvider(authn.ProviderLocal) }) t.Run("UsernameEmpty", func(t *testing.T) { user := NewUser() user.ID = 500 user.AuthProvider = "" user.UserName = "test" assert.Equal(t, authn.ProviderDefault, user.Provider()) }) } func TestUser_SetProvider(t *testing.T) { t.Run("TwoFa", func(t *testing.T) { m := UserFixtures.Get("alice") assert.Equal(t, authn.ProviderLocal, m.Provider()) assert.Equal(t, authn.MethodDefault, m.Method()) m.SetMethod(authn.Method2FA) assert.Equal(t, authn.ProviderLocal, m.Provider()) assert.Equal(t, authn.Method2FA, m.Method()) m.SetProvider(authn.ProviderNone) assert.Equal(t, authn.ProviderNone, m.Provider()) assert.Equal(t, authn.MethodUndefined, m.Method()) m.SetProvider(authn.ProviderLocal) }) } func TestUser_SetMethod(t *testing.T) { t.Run("TwoFa", func(t *testing.T) { m := UserFixtures.Get("unauthorized") assert.Equal(t, authn.ProviderNone, m.Provider()) assert.Equal(t, authn.MethodDefault, m.Method()) m.SetMethod(authn.Method2FA) assert.Equal(t, authn.ProviderNone, m.Provider()) assert.Equal(t, authn.MethodDefault, m.Method()) }) } func TestUser_SetAuthID(t *testing.T) { uuid := rnd.UUID() issuer := "http://dummy-oidc:9998" t.Run("UUID", func(t *testing.T) { m := UserFixtures.Get("guest") m.SetAuthID(uuid, issuer) assert.Equal(t, uuid, m.AuthID) assert.Equal(t, issuer, m.AuthIssuer) m.SetAuthID(uuid, "") assert.Equal(t, uuid, m.AuthID) assert.Equal(t, "", m.AuthIssuer) m.SetAuthID("", issuer) assert.Equal(t, uuid, m.AuthID) assert.Equal(t, "", m.AuthIssuer) }) t.Run("DupeAuthProviderAndID", func(t *testing.T) { m := UserFixtures.Get("guest") n := NewUser() n.UserName = "guest2" n.DisplayName = "Guest User2" n.UserEmail = "guest2@example.com" n.UserRole = acl.RoleGuest.String() n.AuthProvider = authn.ProviderOIDC.String() n.AuthMethod = authn.MethodDefault.String() n.SuperAdmin = false n.CanLogin = true n.SetAuthID(uuid, issuer) assert.NoError(t, n.Save()) t.Cleanup(func() { assert.NoError(t, n.Delete()) UnscopedDb().Delete(n) }) newUserUID := n.UserUID m.SetAuthID(uuid, issuer) assert.Equal(t, uuid, m.AuthID) assert.Equal(t, issuer, m.AuthIssuer) n = FindUserByUID(newUserUID) require.NotNil(t, n) assert.Equal(t, "guest2", n.UserName) assert.Equal(t, "", n.AuthID) assert.Equal(t, authn.ProviderNone.String(), n.AuthProvider) }) } func TestUser_UpdateAuthID(t *testing.T) { uuid := rnd.UUID() issuer := "http://dummy-oidc:9998" longnumber := "12345678901234567890" t.Run("UUID", func(t *testing.T) { m := UserFixtures.Get("friend") m.SetAuthID("", issuer) assert.Equal(t, "", m.AuthID) assert.Equal(t, "", m.AuthIssuer) m.SetAuthID(uuid, issuer) assert.Equal(t, uuid, m.AuthID) assert.Equal(t, issuer, m.AuthIssuer) err := m.UpdateAuthID(uuid, "") assert.NoError(t, err) assert.Equal(t, uuid, m.AuthID) assert.Equal(t, "", m.AuthIssuer) }) t.Run("InvalidUUID", func(t *testing.T) { m := User{UserUID: "123"} assert.Equal(t, "", m.AuthIssuer) m.SetAuthID(uuid, issuer) assert.Equal(t, uuid, m.AuthID) assert.Equal(t, issuer, m.AuthIssuer) err := m.UpdateAuthID(uuid, "") assert.Error(t, err) }) t.Run("LongNumber", func(t *testing.T) { m := UserFixtures.Get("friend") m.SetAuthID("", issuer) assert.Equal(t, "", m.AuthID) assert.Equal(t, "", m.AuthIssuer) m.SetAuthID(longnumber, issuer) assert.Equal(t, longnumber, m.AuthID) assert.Equal(t, issuer, m.AuthIssuer) err := m.UpdateAuthID(longnumber, "") assert.NoError(t, err) assert.Equal(t, longnumber, m.AuthID) assert.Equal(t, "", m.AuthIssuer) }) } func TestUser_AuthInfo(t *testing.T) { t.Run("Alice", func(t *testing.T) { m := UserFixtures.Get("alice") assert.Equal(t, "Local", m.AuthInfo()) }) t.Run("Jane", func(t *testing.T) { m := UserFixtures.Get("jane") assert.Equal(t, "Local (2FA)", m.AuthInfo()) }) t.Run("Unauthorized", func(t *testing.T) { m := UserFixtures.Get("unauthorized") assert.Equal(t, "None", m.AuthInfo()) }) } func TestUser_Passcode(t *testing.T) { t.Run("Jane", func(t *testing.T) { m := UserFixtures.Get("jane") assert.IsType(t, &Passcode{}, m.Passcode("totp")) }) } func TestUser_GetBasePath(t *testing.T) { t.Run("Visitor", func(t *testing.T) { assert.Equal(t, "", Visitor.GetBasePath()) }) t.Run("UnknownUser", func(t *testing.T) { assert.Equal(t, "", UnknownUser.GetBasePath()) }) t.Run("Admin", func(t *testing.T) { assert.Equal(t, "", Admin.GetBasePath()) }) } func TestUser_SetBasePath(t *testing.T) { t.Run("Test", func(t *testing.T) { u := User{ ID: 1234567, UserUID: "urqdrfb72479n047", UserName: "test", UserRole: acl.RoleAdmin.String(), DisplayName: "Test", SuperAdmin: false, CanLogin: true, WebDAV: true, CanInvite: false, } assert.Equal(t, "", u.SetBasePath("./").GetBasePath()) assert.Equal(t, "base", u.SetBasePath("base").GetBasePath()) assert.Equal(t, "users/test", u.SetBasePath("~").GetBasePath()) assert.Equal(t, "users/test", u.DefaultBasePath()) assert.Equal(t, "users/test", u.GetUploadPath()) }) } func TestUser_GetUploadPath(t *testing.T) { t.Run("Visitor", func(t *testing.T) { assert.Equal(t, "", Visitor.GetUploadPath()) }) t.Run("UnknownUser", func(t *testing.T) { assert.Equal(t, "", UnknownUser.GetUploadPath()) }) t.Run("Admin", func(t *testing.T) { assert.Equal(t, "", Admin.GetUploadPath()) }) t.Run("UseBasePath", func(t *testing.T) { user := NewUser() user.UserName = "test" user.UploadPath = "./" user.BasePath = "mybase" assert.Equal(t, "mybase", user.GetUploadPath()) }) t.Run("UploadPathIncludesBasePath", func(t *testing.T) { user := NewUser() user.UserName = "test" user.UploadPath = "mybase/upload" user.BasePath = "mybase" assert.Equal(t, "mybase/upload", user.GetUploadPath()) }) } func TestUser_SetUploadPath(t *testing.T) { t.Run("Test", func(t *testing.T) { u := User{ ID: 1234567, UserUID: "urqdrfb72479n047", UserName: "test", UserRole: acl.RoleAdmin.String(), DisplayName: "Test", SuperAdmin: false, CanLogin: true, WebDAV: true, CanInvite: false, } assert.Equal(t, "upload", u.SetUploadPath("upload").GetUploadPath()) assert.Equal(t, "", u.SetUploadPath("./").GetUploadPath()) assert.Equal(t, "users/test", u.SetUploadPath("~").GetUploadPath()) assert.Equal(t, "base/users/test", u.SetBasePath("base").GetUploadPath()) assert.Equal(t, "base", u.SetUploadPath("~").GetUploadPath()) assert.Equal(t, "users/test", u.DefaultBasePath()) assert.Equal(t, "users/test", u.SetBasePath("~").GetUploadPath()) }) } func TestUser_Handle(t *testing.T) { t.Run("Default", func(t *testing.T) { u := User{ ID: 1234567, UserUID: "urqdrfb72479n047", UserName: "mr-happy@cat.com", UserRole: acl.RoleAdmin.String(), DisplayName: "", SuperAdmin: false, CanLogin: true, WebDAV: true, CanInvite: false, } assert.Equal(t, "mr-happy@cat.com", u.Username()) assert.Equal(t, "mr-happy", u.Handle()) u.UserName = "mr.happy@cat.com" assert.Equal(t, "mr.happy", u.Handle()) u.UserName = "mr.happy" assert.Equal(t, "mr.happy", u.Handle()) }) } func TestUser_FullName(t *testing.T) { t.Run("Default", func(t *testing.T) { u := User{ ID: 1234567, UserUID: "urqdrfb72479n047", UserName: "mr-happy@cat.com", UserRole: acl.RoleAdmin.String(), DisplayName: "", SuperAdmin: false, CanLogin: true, WebDAV: true, CanInvite: false, } assert.Equal(t, "Mr-Happy", u.FullName()) u.UserName = "mr.happy@cat.com" assert.Equal(t, "Mr Happy", u.FullName()) u.UserName = "mr.happy" assert.Equal(t, "Mr Happy", u.FullName()) u.UserName = "foo@bar.com" assert.Equal(t, "Foo", u.FullName()) u.SetDisplayName("Jane Doe", SrcManual) assert.Equal(t, "Jane Doe", u.FullName()) }) t.Run("NameFromDetails", func(t *testing.T) { u := User{ ID: 1234567, UserUID: "urqdrfb72479n047", UserName: "mr-happy@cat.com", UserRole: acl.RoleAdmin.String(), DisplayName: "", SuperAdmin: false, CanLogin: true, WebDAV: true, CanInvite: false, UserDetails: &UserDetails{ GivenName: "John", FamilyName: "Doe", }, } assert.Equal(t, "John Doe", u.FullName()) }) t.Run("Windows", func(t *testing.T) { u := User{ ID: 1234567, UserUID: "urqdrfb72479n047", UserName: "DOMAIN\\Jens Mander", UserRole: acl.RoleAdmin.String(), DisplayName: "", SuperAdmin: false, CanLogin: true, WebDAV: true, CanInvite: false, } assert.Equal(t, "jens.mander", u.Handle()) assert.Equal(t, "domain\\jens mander", u.Username()) assert.Equal(t, "Jens Mander", u.FullName()) }) } func TestUser_Settings(t *testing.T) { t.Run("ReturnSettings", func(t *testing.T) { u := User{ ID: 1234567, UserUID: "urqdrfb72479n047", UserName: "test", UserRole: "user", UserSettings: &UserSettings{ UserUID: "", UITheme: "vanta", UILanguage: "de", MapsStyle: "street", IndexPath: "/photos", }, } assert.Equal(t, "de", u.Settings().UILanguage) assert.Equal(t, "vanta", u.Settings().UITheme) }) t.Run("EmptyUid", func(t *testing.T) { u := User{ ID: 1234567, UserUID: "", UserName: "test", UserRole: "user", } assert.Equal(t, "", u.Settings().UILanguage) assert.Equal(t, "", u.Settings().UITheme) }) } func TestUser_Details(t *testing.T) { t.Run("ReturnDetails", func(t *testing.T) { u := User{ ID: 1234567, UserUID: "urqdrfb72479n047", UserName: "test", UserRole: "user", UserDetails: &UserDetails{ GivenName: "John", FamilyName: "Doe", }, } assert.Equal(t, "John", u.Details().GivenName) assert.Equal(t, "Doe", u.Details().FamilyName) }) t.Run("EmptyUid", func(t *testing.T) { u := User{ ID: 1234567, UserUID: "", UserName: "test", UserRole: "user", } assert.Equal(t, "", u.Details().GivenName) assert.Equal(t, "", u.Details().FamilyName) }) } func TestUser_Equal(t *testing.T) { assert.True(t, Admin.Equal(&Admin)) assert.False(t, Admin.Equal(&Visitor)) } func TestUser_RevokeDerivedSessions(t *testing.T) { t.Run("EmptyUid", func(t *testing.T) { u := User{ ID: 1234567, UserUID: "", UserName: "test", UserRole: "user", } assert.Equal(t, 0, u.RevokeDerivedSessions([]string{})) }) t.Run("Alice", func(t *testing.T) { m := FindLocalUser("alice") assert.Equal(t, 0, m.RevokeDerivedSessions([]string{rnd.SessionID("69be27ac5ca305b394046a83f6fda18167ca3d3f2dbe7ac0")})) assert.Equal(t, 1, m.RevokeDerivedSessions([]string{})) }) } // revokeTestSession creates a persisted session of the given type for a synthetic user. func revokeTestSession(t *testing.T, uid string, provider authn.ProviderType, method authn.MethodType, authID string) *Session { s := NewSession(86400, 0) s.UserUID = uid s.UserName = "revoke-test" s.SetProvider(provider) s.SetMethod(method) if authID != "" { s.SetAuthID(authID, uid) } require.NoError(t, s.Create()) return s } // countUserSessions returns the number of sessions stored for the given user uid. func countUserSessions(t *testing.T, uid string) int { var sess Sessions require.NoError(t, Db().Where("user_uid = ?", uid).Find(&sess).Error) return len(sess) } // newRevokeTestUser creates a synthetic user with one regular login, a parent app // password, a derived child app session, and a client access token. func newRevokeTestUser(t *testing.T) *User { uid := rnd.GenerateUID(UserUID) revokeTestSession(t, uid, authn.ProviderLocal, authn.MethodDefault, "") parent := revokeTestSession(t, uid, authn.ProviderApplication, authn.MethodDefault, "") revokeTestSession(t, uid, authn.ProviderApplication, authn.MethodSession, parent.ID) revokeTestSession(t, uid, authn.ProviderClient, authn.MethodOAuth2, "") return &User{ID: 100, UserUID: uid, UserName: "revoke-test", UserRole: "admin", AuthProvider: authn.ProviderLocal.String(), RefID: "usrevoke0001"} } func TestUser_RevokeSessions(t *testing.T) { t.Run("EmptyUid", func(t *testing.T) { u := &User{ID: 1234567, UserUID: "", UserName: "test", UserRole: "user"} assert.Equal(t, 0, u.RevokeSessions(nil, authn.RevokeAllSessions)) }) t.Run("LoginSessions", func(t *testing.T) { u := newRevokeTestUser(t) assert.Equal(t, 4, countUserSessions(t, u.UserUID)) assert.Equal(t, 1, u.RevokeSessions(nil, authn.RevokeLoginSessions)) assert.Equal(t, 3, countUserSessions(t, u.UserUID)) }) t.Run("DerivedSessions", func(t *testing.T) { u := newRevokeTestUser(t) assert.Equal(t, 2, u.RevokeSessions(nil, authn.RevokeDerivedSessions)) assert.Equal(t, 2, countUserSessions(t, u.UserUID)) }) t.Run("AllSessions", func(t *testing.T) { u := newRevokeTestUser(t) assert.Equal(t, 4, u.RevokeSessions(nil, authn.RevokeAllSessions)) assert.Equal(t, 0, countUserSessions(t, u.UserUID)) }) t.Run("OmitKeepsSession", func(t *testing.T) { u := newRevokeTestUser(t) var login Sessions require.NoError(t, Db().Where("user_uid = ? AND auth_provider = ?", u.UserUID, authn.ProviderLocal.String()).Find(&login).Error) require.Len(t, login, 1) assert.Equal(t, 0, u.RevokeSessions([]string{login[0].ID}, authn.RevokeLoginSessions)) assert.Equal(t, 4, countUserSessions(t, u.UserUID)) }) t.Run("CrossUserIsolation", func(t *testing.T) { // The `auth_method = 'session'` clause must stay scoped to the target user, so // revoking one user's derived sessions must never touch another user's sessions. uidA := rnd.GenerateUID(UserUID) uidB := rnd.GenerateUID(UserUID) parentA := revokeTestSession(t, uidA, authn.ProviderApplication, authn.MethodDefault, "") revokeTestSession(t, uidA, authn.ProviderApplication, authn.MethodSession, parentA.ID) parentB := revokeTestSession(t, uidB, authn.ProviderApplication, authn.MethodDefault, "") derivedB := revokeTestSession(t, uidB, authn.ProviderApplication, authn.MethodSession, parentB.ID) uA := &User{ID: 101, UserUID: uidA, UserName: "iso-a", RefID: "usiso0000a01"} assert.Equal(t, 1, uA.RevokeDerivedSessions(nil)) assert.Equal(t, 1, countUserSessions(t, uidA)) // parent A kept assert.Equal(t, 2, countUserSessions(t, uidB)) // user B untouched sB, err := FindSession(derivedB.ID) require.NoError(t, err) require.NotNil(t, sB) }) } func TestUser_DenyLogIn(t *testing.T) { t.Run("Active", func(t *testing.T) { assert.False(t, UserFixtures.Pointer("alice").DenyLogIn()) }) t.Run("ProviderNone", func(t *testing.T) { u := UserFixtures.Get("bob") u.SetProvider(authn.ProviderNone) assert.True(t, u.DenyLogIn()) }) t.Run("WebLoginDisabled", func(t *testing.T) { u := UserFixtures.Get("bob") u.CanLogin = false assert.True(t, u.DenyLogIn()) }) t.Run("Nil", func(t *testing.T) { var u *User assert.True(t, u.DenyLogIn()) }) } func TestUser_VerifyPassword(t *testing.T) { assert.True(t, Admin.VerifyPassword("photoprism")) assert.False(t, Admin.VerifyPassword("wrong")) } func TestUser_InvalidPasscode(t *testing.T) { m := UserFixtures.Get("jane") passcode := m.Passcode("totp") assert.True(t, m.InvalidPasscode("xxxxxx")) assert.False(t, m.InvalidPasscode(passcode.RecoveryCode)) } func TestUser_Passcodes(t *testing.T) { t.Run("Alice", func(t *testing.T) { m := UserFixtures.Get("alice") assert.Equal(t, "default", m.AuthMethod) passcode, err := m.ActivatePasscode() assert.Error(t, err) assert.Equal(t, "default", m.AuthMethod) code, err := passcode.GenerateCode() if err != nil { t.Fatal(err) } _, _, errInvalid := m.VerifyPasscode("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx") assert.Error(t, errInvalid) assert.Equal(t, authn.ErrInvalidPasscode, errInvalid) v, _, err := m.VerifyPasscode(code) if err != nil { t.Fatal(err) } assert.True(t, v) _, errActivate := m.ActivatePasscode() if errActivate != nil { t.Fatal(errActivate) } assert.Equal(t, "2fa", m.AuthMethod) _, errDeactivate := m.DeactivatePasscode() if errDeactivate != nil { t.Fatal(errDeactivate) } assert.Equal(t, "default", m.AuthMethod) }) t.Run("PassCodeNotSetup", func(t *testing.T) { m := UserFixtures.Get("bob") assert.Equal(t, "default", m.AuthMethod) passcode, err := m.ActivatePasscode() assert.Error(t, err) assert.Equal(t, authn.ErrPasscodeNotSetUp, err) code, err := passcode.GenerateCode() assert.Error(t, err) v, _, err := m.VerifyPasscode(code) assert.False(t, v) assert.Error(t, err) assert.Equal(t, authn.ErrPasscodeNotSetUp, err) assert.Equal(t, "default", m.AuthMethod) code2, err := m.DeactivatePasscode() if err != nil { t.Fatal(err) } assert.Nil(t, code2) }) t.Run("PassCodeNotSupported", func(t *testing.T) { m := UserFixtures.Get("unauthorized") _, err := m.ActivatePasscode() assert.Error(t, err) assert.Equal(t, authn.ErrPasscodeNotSupported, err) }) } func TestUser_RegenerateTokens(t *testing.T) { t.Run("Visitor", func(t *testing.T) { assert.Nil(t, Visitor.RegenerateTokens()) }) t.Run("Admin", func(t *testing.T) { preview := Admin.PreviewToken // Register the current tokens in the lookup cache. PreviewToken.Set("user-regen-session", preview) err := Admin.RegenerateTokens() if err != nil { t.Fatal(err) } assert.NotEqual(t, preview, Admin.PreviewToken) // The replaced tokens are dropped from the lookup cache. assert.True(t, PreviewToken.MissingValue(preview)) }) } func TestUser_RegenerateTokens_ReleaseSurvivesReCache(t *testing.T) { // Regression guard for #5733: StringMap.Set does not retract a key from a replaced // value's reverse-lookup list, so RegenerateTokens must explicitly release the old // token. Otherwise re-caching a session (which reassigns the same session key to the // new token without retracting the old value) leaves the old preview/download token // resolvable via HasValue. The prior coverage used a bare map insert that was never // re-cached and could not catch this. sessID := rnd.SessionID("44be27ac5ca305b394046a83f6fda18167ca3d3f2dbe7ac1") oldPreview := Admin.PreviewToken // Cache a session that currently holds the user's tokens, registering them for lookup. sess := &Session{ID: sessID, PreviewToken: oldPreview} CacheSession(sess, time.Hour) require.False(t, InvalidPreviewToken(oldPreview)) // Regenerate the user's tokens; the previous values must be released from the cache. require.NoError(t, Admin.RegenerateTokens()) require.NotEqual(t, oldPreview, Admin.PreviewToken) // Refresh the session to the user's current tokens and re-cache it. sess.SetUser(&Admin) CacheSession(sess, time.Hour) // The regenerated tokens resolve; the released old tokens do not resurface. assert.False(t, InvalidPreviewToken(Admin.PreviewToken)) assert.True(t, InvalidPreviewToken(oldPreview)) } func TestUser_RegenerateTokens_StaleReloadDoesNotResurrect(t *testing.T) { // Regression guard for #5733: an app password survives a password change with its // preview/download token stored in its sessions row. RegenerateTokens must rewrite // that row, otherwise a reload from the database (after the 15-minute cache expiry or // a restart) re-registers the revoked token via CacheSession, resurrecting it. u := &User{ UserUID: rnd.GenerateUID(UserUID), UserName: "regen-stale-reload", UserRole: acl.RoleAdmin.String(), CanLogin: true, PreviewToken: GenerateToken(), } require.NoError(t, u.Save()) oldPreview := u.PreviewToken // Mint and persist an app-password session that inherits the user's tokens. appPw := NewClientSession("regen-stale-client", unix.Day, "*", authn.GrantPassword, u) require.NoError(t, appPw.Save()) require.Equal(t, oldPreview, appPw.PreviewToken) // Change the account password, which regenerates the user's tokens. require.NoError(t, u.RegenerateTokens()) require.NotEqual(t, oldPreview, u.PreviewToken) // The app-password session row must now carry the new token, not the revoked one. reloaded := &Session{} require.NoError(t, UnscopedDb().First(reloaded, "id = ?", appPw.ID).Error) assert.Equal(t, u.PreviewToken, reloaded.PreviewToken) // Simulate a fresh reload after cache eviction: caching the row must not resurrect // the revoked token, and the current token resolves. DeleteFromSessionCache(appPw.ID) CacheSession(reloaded, time.Hour) assert.True(t, InvalidPreviewToken(oldPreview)) assert.False(t, InvalidPreviewToken(u.PreviewToken)) // Cleanup. require.NoError(t, reloaded.Delete()) } func TestUser_HasShares(t *testing.T) { t.Run("Visitor", func(t *testing.T) { assert.False(t, Visitor.HasShares()) }) t.Run("Alice", func(t *testing.T) { m := FindLocalUser("alice") assert.True(t, m.HasShares()) }) } func TestUser_HasShare(t *testing.T) { m := FindLocalUser("alice") m.RefreshShares() assert.True(t, m.HasShare("as6sg6bxpogaaba9")) assert.False(t, m.HasShare("as6sg6bxpogaaba8")) assert.False(t, Visitor.HasShare("as6sg6bxpogaaba8")) } func TestUser_RedeemToken(t *testing.T) { t.Run("Visitor", func(t *testing.T) { assert.Equal(t, 0, Visitor.RedeemToken("1234")) }) t.Run("Alice", func(t *testing.T) { m := FindLocalUser("alice") m.RefreshShares() assert.Equal(t, "as6sg6bxpogaaba9", m.UserShares[0].ShareUID) assert.Equal(t, 0, m.RedeemToken("1234")) m.RefreshShares() assert.Equal(t, "as6sg6bxpogaaba9", m.UserShares[0].ShareUID) assert.Equal(t, 1, m.RedeemToken("4jxf3jfn2k")) m.RefreshShares() assert.Equal(t, "as6sg6bxpogaaba7", m.UserShares[0].ShareUID) assert.Equal(t, "as6sg6bxpogaaba9", m.UserShares[1].ShareUID) }) } func TestUser_ScopeHelpers(t *testing.T) { t.Run("Default", func(t *testing.T) { u := &User{} assert.Equal(t, "*", u.Scope()) assert.False(t, u.HasScope()) assert.True(t, u.NoScope()) }) t.Run("AnyScope", func(t *testing.T) { u := &User{UserScope: list.Any} assert.Equal(t, "*", u.Scope()) assert.False(t, u.HasScope()) assert.True(t, u.NoScope()) }) t.Run("RestrictedScope", func(t *testing.T) { u := &User{UserScope: "Photos:View"} assert.Equal(t, "photos:view", u.Scope()) assert.True(t, u.HasScope()) assert.False(t, u.NoScope()) }) } func TestUser_UpdateScope(t *testing.T) { user := createScopedTestUser(t) err := user.UpdateScope(" photos:view LOGS:* ") assert.NoError(t, err) assert.Equal(t, "logs:* photos:view", user.UserScope) assert.Equal(t, "logs:* photos:view", user.Scope()) assert.True(t, user.HasScope()) } func TestUser_UpdateAttr(t *testing.T) { user := createScopedTestUser(t) err := user.UpdateAttr(" photos:view logs:false ") assert.NoError(t, err) assert.Equal(t, "logs:false photos:view", user.UserAttr) } func TestUser_SetFormValuesScope(t *testing.T) { formValues := form.User{ UserName: "scopeuser", UserEmail: "scope@example.com", UserRole: acl.RoleAdmin.String(), UserScope: "photos:view logs:*", } user := NewUser() user.SetFormValues(formValues) assert.Equal(t, "logs:* photos:view", user.UserScope) formValues.UserScope = "" user.UserScope = "*" user.SetFormValues(formValues) assert.Equal(t, "*", user.UserScope) } func TestUser_SetValuesFromCliScope(t *testing.T) { user := FindLocalUser("alice") require.NotNil(t, user) original := user.UserScope t.Cleanup(func() { user.UserScope = original }) app := cli.NewApp() set := flag.NewFlagSet("users mod", flag.ContinueOnError) _ = set.String("scope", "", "") require.NoError(t, set.Parse([]string{"--scope", "videos:view"})) ctx := cli.NewContext(app, set, nil) require.NoError(t, user.SetValuesFromCli(ctx)) assert.Equal(t, "videos:view", user.UserScope) } func TestUser_AuthIDSQLite(t *testing.T) { user := FindLocalUser("alice") require.NotNil(t, user) original := user.AuthID t.Cleanup(func() { user.AuthID = original assert.NoError(t, user.Save()) }) expected := "012345678901234567890123456789" user.AuthID = expected assert.NoError(t, user.Save()) user2 := FindLocalUser("alice") require.NotNil(t, user2) assert.Equal(t, expected, user2.AuthID) }