145 lines
5.2 KiB
Go
145 lines
5.2 KiB
Go
package authconfig
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/aritmolab/thothii/tools/tht/internal/safeio"
|
|
"github.com/aritmolab/thothii/tools/tht/internal/testsupport"
|
|
)
|
|
|
|
const publicFixedHash = "$argon2id$v=19$m=65536,t=3,p=1$AAECAwQFBgcICQoLDA0ODw$DRo8ZSPI8G5OCvnFFapbVEjP69aDjy1Sw9i2743cPC4"
|
|
|
|
func TestLoadReadsTheStrictLocalConfigurationAndRegistry(t *testing.T) {
|
|
directory := writeAuthFiles(t, defaultAuthYAML, registryYAML(adminUserYAML("admin", "Admin", true, "admin")))
|
|
|
|
config, registry, err := Load(directory)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if config.Version != 1 || config.Mode != "local" || config.Local.UsersFile != "users.yaml" {
|
|
t.Fatalf("Load() config = %#v, want local auth.yaml configuration", config)
|
|
}
|
|
if len(registry.Users) != 1 || registry.Users[0].Username != "admin" || registry.Users[0].PasswordHash != publicFixedHash {
|
|
t.Fatalf("Load() registry = %#v, want one saved administrator", registry)
|
|
}
|
|
}
|
|
|
|
func TestLoadRejectsUnsafeConfigurationAndRegistryFiles(t *testing.T) {
|
|
for name, arrange := range map[string]func(t *testing.T, directory string){
|
|
"symlinked directory": func(t *testing.T, directory string) {
|
|
real := directory + "-real"
|
|
if err := os.Rename(directory, real); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
testsupport.SymlinkOrSkip(t, real, directory)
|
|
},
|
|
"symlinked auth file": func(t *testing.T, directory string) {
|
|
if err := os.Remove(filepath.Join(directory, "auth.yaml")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
testsupport.SymlinkOrSkip(t, filepath.Join(directory, "users.yaml"), filepath.Join(directory, "auth.yaml"))
|
|
},
|
|
"symlinked users file": func(t *testing.T, directory string) {
|
|
if err := os.Remove(filepath.Join(directory, "users.yaml")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
testsupport.SymlinkOrSkip(t, filepath.Join(directory, "auth.yaml"), filepath.Join(directory, "users.yaml"))
|
|
},
|
|
"hard linked users file": func(t *testing.T, directory string) {
|
|
linked := filepath.Join(directory, "users-linked.yaml")
|
|
if err := os.Link(filepath.Join(directory, "users.yaml"), linked); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
},
|
|
"world readable users file": func(t *testing.T, directory string) {
|
|
if err := os.Chmod(filepath.Join(directory, "users.yaml"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
},
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
directory := writeAuthFiles(t, defaultAuthYAML, registryYAML(adminUserYAML("admin", "Admin", true, "admin")))
|
|
arrange(t, directory)
|
|
if _, _, err := Load(directory); err == nil {
|
|
t.Fatal("Load() accepted an unsafe authentication path")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLoadRejectsDuplicateAndUnknownYAMLFields(t *testing.T) {
|
|
for name, fixture := range map[string]struct{ auth, users string }{
|
|
"duplicate auth field": {
|
|
auth: "version: 1\nmode: local\nmode: oidc\npublicUrl: http://127.0.0.1:8080\nlocal:\n usersFile: users.yaml\n",
|
|
users: registryYAML(adminUserYAML("admin", "Admin", true, "admin")),
|
|
},
|
|
"unknown auth field": {
|
|
auth: strings.Replace(defaultAuthYAML, "version: 1", "version: 1\nunexpected: true", 1),
|
|
users: registryYAML(adminUserYAML("admin", "Admin", true, "admin")),
|
|
},
|
|
"unknown user field": {
|
|
auth: defaultAuthYAML,
|
|
users: strings.Replace(registryYAML(adminUserYAML("admin", "Admin", true, "admin")), " enabled: true", " enabled: true\n unexpected: true", 1),
|
|
},
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
directory := writeAuthFiles(t, fixture.auth, fixture.users)
|
|
if _, _, err := Load(directory); err == nil {
|
|
t.Fatal("Load() accepted malformed YAML")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLoadRejectsOversizedYAMLBeforeParsing(t *testing.T) {
|
|
directory := writeAuthFiles(t, defaultAuthYAML, registryYAML(adminUserYAML("admin", "Admin", true, "admin")))
|
|
if err := os.WriteFile(filepath.Join(directory, "users.yaml"), []byte(strings.Repeat("#", 1<<20)+"\n"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, _, err := Load(directory); !errors.Is(err, safeio.ErrUnsafeFile) {
|
|
t.Fatalf("Load() error = %v, want bounded unsafe-file error", err)
|
|
}
|
|
}
|
|
|
|
const defaultAuthYAML = "version: 1\nmode: local\npublicUrl: http://127.0.0.1:8080\nlocal:\n usersFile: users.yaml\n"
|
|
|
|
func writeAuthFiles(t *testing.T, auth, users string) string {
|
|
t.Helper()
|
|
temporaryRoot, err := filepath.EvalSymlinks(os.TempDir())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
directory, err := os.MkdirTemp(temporaryRoot, "tht-authconfig-")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { _ = os.RemoveAll(directory) })
|
|
if err := os.Chmod(directory, 0o700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for name, contents := range map[string]string{"auth.yaml": auth, "users.yaml": users} {
|
|
if err := os.WriteFile(filepath.Join(directory, name), []byte(contents), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
return directory
|
|
}
|
|
|
|
func registryYAML(users ...string) string {
|
|
return "version: 1\nusers:\n" + strings.Join(users, "")
|
|
}
|
|
|
|
func adminUserYAML(username, displayName string, enabled bool, role string) string {
|
|
return " - id: 6ba7b810-9dad-4ed1-80b4-00c04fd430c8\n" +
|
|
" username: " + username + "\n" +
|
|
" displayName: " + displayName + "\n" +
|
|
" passwordHash: " + publicFixedHash + "\n" +
|
|
" roles:\n - " + role + "\n" +
|
|
" enabled: " + map[bool]string{true: "true", false: "false"}[enabled] + "\n" +
|
|
" authRevision: 1\n"
|
|
}
|