Files

131 lines
4.4 KiB
Go

package credentials
import (
"context"
"errors"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"git.ipao.vip/rogee/creator-hub/internal/account"
)
const testCredentialValue = "sessionid=restart-secret; token=second"
var testCredentialReference = account.CredentialReference{ID: "account-a-cookies", Provider: "os_keyring"}
func TestPersistentCredentialBridgeStoreFailureLeavesNoFile(t *testing.T) {
for _, failure := range []string{"chmod", "directory sync"} {
t.Run(failure, func(t *testing.T) {
directory := t.TempDir()
bridge, err := NewPersistentCredentialBridge(directory, []byte("0123456789abcdef0123456789abcdef"))
if err != nil {
t.Fatal(err)
}
var chmodFile *os.File
switch failure {
case "chmod":
bridge.chmod = func(file *os.File, _ os.FileMode) error {
chmodFile = file
return errors.New("chmod unavailable")
}
case "directory sync":
calls := 0
bridge.syncDirectory = func(path string) error {
calls++
if calls == 1 {
return errors.New("directory sync unavailable")
}
return syncCredentialDirectory(path)
}
}
if err := bridge.Store(context.Background(), testCredentialReference,
"creatorhub/account-a/cookies", testCredentialValue); err == nil {
t.Fatal("credential store failure returned success")
}
if chmodFile != nil {
if _, err := chmodFile.Write([]byte("still-open")); err == nil {
t.Fatal("credential temporary file remained open after chmod failure")
}
}
entries, err := os.ReadDir(directory)
if err != nil || len(entries) != 0 {
t.Fatalf("credential store failure left files behind: entries=%v err=%v", entries, err)
}
})
}
}
func TestPersistentCredentialBridgeResolvesAndRejectsCorruption(t *testing.T) {
bridge, err := NewPersistentCredentialBridge(t.TempDir(), []byte("0123456789abcdef0123456789abcdef"))
if err != nil {
t.Fatal(err)
}
key := "creatorhub/account-a/cookies"
if err := bridge.Store(context.Background(), testCredentialReference, key, testCredentialValue); err != nil {
t.Fatal(err)
}
value, err := bridge.Resolve(context.Background(), testCredentialReference, key)
if err != nil || string(value) != testCredentialValue {
t.Fatalf("resolve credential: %q %v", value, err)
}
path := bridge.path(testCredentialReference.Provider, key)
if err := os.WriteFile(path, []byte("corrupted"), 0o600); err != nil {
t.Fatal(err)
}
if _, err := bridge.Resolve(context.Background(), testCredentialReference, key); err == nil {
t.Fatal("corrupted credential resolved")
}
if err := bridge.Delete(context.Background(), testCredentialReference, key); err != nil {
t.Fatal(err)
}
if err := bridge.Delete(context.Background(), testCredentialReference, key); err != nil {
t.Fatal(err)
}
}
func TestPersistentCredentialBridgeStoresEncryptedCredentialAcrossProcesses(t *testing.T) {
if action := os.Getenv("CREATORHUB_CREDENTIAL_HELPER_ACTION"); action != "" {
bridge, err := NewPersistentCredentialBridge(os.Getenv("CREATORHUB_CREDENTIAL_HELPER_DIR"), []byte("0123456789abcdef0123456789abcdef"))
if err != nil {
t.Fatal(err)
}
switch action {
case "store":
err = bridge.Store(context.Background(), testCredentialReference, "creatorhub/account-a/cookies", testCredentialValue)
case "delete":
err = bridge.Delete(context.Background(), testCredentialReference, "creatorhub/account-a/cookies")
}
if err != nil {
t.Fatal(err)
}
return
}
directory := t.TempDir()
run := func(action string) {
t.Helper()
process := exec.Command(os.Args[0], "-test.run=^TestPersistentCredentialBridgeStoresEncryptedCredentialAcrossProcesses$")
process.Env = append(os.Environ(), "CREATORHUB_CREDENTIAL_HELPER_ACTION="+action,
"CREATORHUB_CREDENTIAL_HELPER_DIR="+directory)
if output, err := process.CombinedOutput(); err != nil {
t.Fatalf("credential helper %s: %v %s", action, err, output)
}
}
run("store")
files, err := filepath.Glob(filepath.Join(directory, "*.credential"))
if err != nil || len(files) != 1 {
t.Fatalf("persistent credential file missing: files=%v err=%v", files, err)
}
payload, err := os.ReadFile(files[0])
if err != nil || strings.Contains(string(payload), "restart-secret") {
t.Fatalf("credential file is unreadable or contains plaintext: err=%v", err)
}
run("delete")
if _, err := os.Stat(files[0]); !os.IsNotExist(err) {
t.Fatalf("credential file remained after delete: %v", err)
}
}