package authconfig import ( "fmt" "strings" "sync" "testing" ) func TestMutateUsersRejectsRemovingTheLastEnabledAdministrator(t *testing.T) { directory := writeAuthFiles(t, defaultAuthYAML, registryYAML(adminUserYAML("admin", "Admin", true, "admin"))) err := MutateUsers(directory, func(registry *Registry) error { registry.Users[0].Roles = []Role{RoleUser} return nil }) if err == nil { t.Fatal("MutateUsers() allowed removal of the last enabled administrator") } _, registry, err := Load(directory) if err != nil { t.Fatal(err) } if len(registry.Users[0].Roles) != 1 || registry.Users[0].Roles[0] != RoleAdmin { t.Fatal("MutateUsers() wrote an invalid last-admin mutation") } } func TestMutateUsersSerializesConcurrentReadCheckWriteTransactions(t *testing.T) { users := adminUserYAML("admin", "Admin", true, "admin") for index := 0; index < 12; index++ { users += userYAML(index) } directory := writeAuthFiles(t, defaultAuthYAML, registryYAML(users)) var group sync.WaitGroup errors := make(chan error, 12) for index := 0; index < 12; index++ { index := index group.Add(1) go func() { defer group.Done() errors <- MutateUsers(directory, func(registry *Registry) error { registry.Users[index+1].DisplayName = fmt.Sprintf("Updated %d", index) return nil }) }() } group.Wait() close(errors) for err := range errors { if err != nil { t.Fatalf("MutateUsers() concurrent mutation error = %v", err) } } _, registry, err := Load(directory) if err != nil { t.Fatal(err) } for index := 0; index < 12; index++ { if got, want := registry.Users[index+1].DisplayName, fmt.Sprintf("Updated %d", index); got != want { t.Fatalf("user %d displayName = %q, want %q; mutation was lost", index, got, want) } } } func TestMutateUsersIncrementsAuthRevisionForSecurityChanges(t *testing.T) { directory := writeAuthFiles(t, defaultAuthYAML, registryYAML( adminUserYAML("admin", "Admin", true, "admin"), userYAML(0), )) if err := MutateUsers(directory, func(registry *Registry) error { registry.Users[1].Enabled = false return nil }); err != nil { t.Fatal(err) } _, registry, err := Load(directory) if err != nil { t.Fatal(err) } if got, want := registry.Users[1].AuthRevision, uint64(2); got != want { t.Fatalf("security mutation authRevision = %d, want %d", got, want) } } func TestRegistryRejectsAmbiguousOrUnsafeUserRecords(t *testing.T) { secondAdmin := strings.Replace(adminUserYAML("Admin", "Admin", true, "admin"), "6ba7b810-9dad-4ed1-80b4-00c04fd430c8", "7ba7b810-9dad-4ed1-80b4-00c04fd430c8", 1) for name, users := range map[string]string{ "duplicate ASCII case-insensitive username": secondAdmin, "invalid username": strings.Replace(userYAML(0), "username: user0", "username: _user", 1), "control display name": strings.Replace(userYAML(0), "displayName: User 0", "displayName: \"User\\t0\"", 1), "unknown role": strings.Replace(userYAML(0), "- user", "- operator", 1), } { t.Run(name, func(t *testing.T) { directory := writeAuthFiles(t, defaultAuthYAML, registryYAML(adminUserYAML("admin", "Admin", true, "admin")+users)) if _, _, err := Load(directory); err == nil { t.Fatal("Load() accepted an ambiguous or unsafe user record") } }) } } func TestNewUserGeneratesAUUIDv4(t *testing.T) { user, err := newUser("operator", "Operator", publicFixedHash, []Role{RoleUser}) if err != nil { t.Fatal(err) } if len(user.ID) != 36 || user.ID[14] != '4' || user.ID[19] != '8' && user.ID[19] != '9' && user.ID[19] != 'a' && user.ID[19] != 'b' { t.Fatalf("newUser() ID = %q, want UUIDv4", user.ID) } } func userYAML(index int) string { return fmt.Sprintf(" - id: 8a0a0b0c-0d0e-4f1a-8b2c-%012d\n username: user%d\n displayName: User %d\n passwordHash: %s\n roles:\n - user\n enabled: true\n authRevision: 1\n", index+1, index, index, publicFixedHash) }