131 lines
4.4 KiB
Go
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)
|
|
}
|
|
}
|