feat(auth): add safe Argon2id local user registry
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user