Compare commits
7 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e0e1f6de6 | ||
| 7c999d2aba | |||
| 846894c1a7 | |||
| 7c016e8c5e | |||
| 90dbed4d37 | |||
| 078aa17285 | |||
|
|
200674778b |
5 changed files with 252 additions and 8 deletions
10
CHANGELOG.md
10
CHANGELOG.md
|
|
@ -2,6 +2,14 @@
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [v1.13.0] — 2026-05-13
|
||||||
|
|
||||||
|
### Corrections
|
||||||
|
|
||||||
|
- **Secretstore — isolation des sessions Bitwarden entre MCPs** : `bw unlock` n'est plus appelé si le vault est déjà ouvert et qu'une session valide existe (env ou fichier partagé). Le login écrit uniquement dans `~/.config/mcp-framework/bw-session` — fichier commun à tous les MCPs — évitant de générer un nouveau token qui invaliderait les sessions en cours. Chaque MCP relit ce fichier avant chaque opération Bitwarden pour récupérer dynamiquement les sessions créées après son démarrage.
|
||||||
|
|
||||||
|
## [v1.12.0] — 2026-05-13
|
||||||
|
|
||||||
### Nouvelles fonctionnalités
|
### Nouvelles fonctionnalités
|
||||||
|
|
||||||
- **Bootstrap — `DefaultLoginHandler`** : handler de login Bitwarden prêt à l'emploi avec confirmation, évitant de réimplémenter le même code dans chaque MCP.
|
- **Bootstrap — `DefaultLoginHandler`** : handler de login Bitwarden prêt à l'emploi avec confirmation, évitant de réimplémenter le même code dans chaque MCP.
|
||||||
|
|
@ -208,6 +216,8 @@ Première release stable du framework.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
[v1.13.0]: http://forgejo:3000/AI/mcp-framework/releases/tag/v1.13.0
|
||||||
|
[v1.12.0]: http://forgejo:3000/AI/mcp-framework/releases/tag/v1.12.0
|
||||||
[v1.11.0]: https://forge.lclr.dev/AI/mcp-framework/releases/tag/v1.11.0
|
[v1.11.0]: https://forge.lclr.dev/AI/mcp-framework/releases/tag/v1.11.0
|
||||||
[v1.10.0]: https://forge.lclr.dev/AI/mcp-framework/releases/tag/v1.10.0
|
[v1.10.0]: https://forge.lclr.dev/AI/mcp-framework/releases/tag/v1.10.0
|
||||||
[v1.9.0]: https://forge.lclr.dev/AI/mcp-framework/releases/tag/v1.9.0
|
[v1.9.0]: https://forge.lclr.dev/AI/mcp-framework/releases/tag/v1.9.0
|
||||||
|
|
|
||||||
|
|
@ -384,6 +384,7 @@ func (s *bitwardenStore) scopedName(name string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *bitwardenStore) ensureReady() error {
|
func (s *bitwardenStore) ensureReady() error {
|
||||||
|
s.refreshSessionEnv()
|
||||||
return verifyBitwardenCLIReady(Options{
|
return verifyBitwardenCLIReady(Options{
|
||||||
BitwardenCommand: s.command,
|
BitwardenCommand: s.command,
|
||||||
BitwardenDebug: s.debug,
|
BitwardenDebug: s.debug,
|
||||||
|
|
@ -392,6 +393,14 @@ func (s *bitwardenStore) ensureReady() error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *bitwardenStore) refreshSessionEnv() {
|
||||||
|
session, err := LoadBitwardenSession(BitwardenSessionOptions{ServiceName: bitwardenSharedSessionName})
|
||||||
|
if err != nil || strings.TrimSpace(session) == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = os.Setenv(bitwardenSessionEnvName, strings.TrimSpace(session))
|
||||||
|
}
|
||||||
|
|
||||||
type bitwardenResolvedItem struct {
|
type bitwardenResolvedItem struct {
|
||||||
item bitwardenListItem
|
item bitwardenListItem
|
||||||
payload map[string]any
|
payload map[string]any
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,10 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const bitwardenSessionFileName = "bw-session"
|
const (
|
||||||
|
bitwardenSessionFileName = "bw-session"
|
||||||
|
bitwardenSharedSessionName = "mcp-framework"
|
||||||
|
)
|
||||||
|
|
||||||
var bitwardenUserConfigDir = os.UserConfigDir
|
var bitwardenUserConfigDir = os.UserConfigDir
|
||||||
|
|
||||||
|
|
@ -108,10 +111,14 @@ func EnsureBitwardenSessionEnv(options BitwardenSessionOptions) (bool, error) {
|
||||||
|
|
||||||
session, err := LoadBitwardenSession(options)
|
session, err := LoadBitwardenSession(options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, ErrNotFound) {
|
if !errors.Is(err, ErrNotFound) {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
// Service-specific file not found; try the shared file written by any MCP login.
|
||||||
|
session, err = LoadBitwardenSession(BitwardenSessionOptions{ServiceName: bitwardenSharedSessionName})
|
||||||
|
if err != nil {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
return false, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.Setenv(bitwardenSessionEnvName, session); err != nil {
|
if err := os.Setenv(bitwardenSessionEnvName, session); err != nil {
|
||||||
|
|
@ -156,7 +163,20 @@ func LoginBitwarden(options BitwardenLoginOptions) (string, error) {
|
||||||
if _, err := runBitwardenInteractiveCommand(command, debugEnabled, stdin, stdout, stderr, "login"); err != nil {
|
if _, err := runBitwardenInteractiveCommand(command, debugEnabled, stdin, stdout, stderr, "login"); err != nil {
|
||||||
return "", fmt.Errorf("login to bitwarden CLI: %w", err)
|
return "", fmt.Errorf("login to bitwarden CLI: %w", err)
|
||||||
}
|
}
|
||||||
case "locked", "unlocked":
|
case "unlocked":
|
||||||
|
// Vault is already unlocked. Reuse an existing session to avoid calling
|
||||||
|
// bw unlock again, which would generate a new token and invalidate the
|
||||||
|
// tokens held by other running MCP processes.
|
||||||
|
if existing := loadAnyBitwardenSession(); existing != "" {
|
||||||
|
if err := os.Setenv(bitwardenSessionEnvName, existing); err != nil {
|
||||||
|
return "", fmt.Errorf("set %s from existing session: %w", bitwardenSessionEnvName, err)
|
||||||
|
}
|
||||||
|
if _, err := SaveBitwardenSession(BitwardenSessionOptions{ServiceName: serviceName}, existing); err != nil {
|
||||||
|
return "", fmt.Errorf("persist bitwarden session: %w", err)
|
||||||
|
}
|
||||||
|
return existing, nil
|
||||||
|
}
|
||||||
|
case "locked":
|
||||||
default:
|
default:
|
||||||
return "", fmt.Errorf("%w: unsupported bitwarden status %q", ErrBWUnavailable, status)
|
return "", fmt.Errorf("%w: unsupported bitwarden status %q", ErrBWUnavailable, status)
|
||||||
}
|
}
|
||||||
|
|
@ -175,13 +195,25 @@ func LoginBitwarden(options BitwardenLoginOptions) (string, error) {
|
||||||
return "", fmt.Errorf("set %s after bitwarden unlock: %w", bitwardenSessionEnvName, err)
|
return "", fmt.Errorf("set %s after bitwarden unlock: %w", bitwardenSessionEnvName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := SaveBitwardenSession(BitwardenSessionOptions{ServiceName: serviceName}, session); err != nil {
|
if _, err := SaveBitwardenSession(BitwardenSessionOptions{ServiceName: bitwardenSharedSessionName}, session); err != nil {
|
||||||
return "", fmt.Errorf("persist bitwarden session: %w", err)
|
return "", fmt.Errorf("persist bitwarden session: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return session, nil
|
return session, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// loadAnyBitwardenSession looks for an existing valid session in: the process
|
||||||
|
// environment, then the shared file written by any MCP login.
|
||||||
|
func loadAnyBitwardenSession() string {
|
||||||
|
if session, ok := os.LookupEnv(bitwardenSessionEnvName); ok && strings.TrimSpace(session) != "" {
|
||||||
|
return strings.TrimSpace(session)
|
||||||
|
}
|
||||||
|
if session, err := LoadBitwardenSession(BitwardenSessionOptions{ServiceName: bitwardenSharedSessionName}); err == nil {
|
||||||
|
return session
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func resolveBitwardenSessionPath(options BitwardenSessionOptions) (string, error) {
|
func resolveBitwardenSessionPath(options BitwardenSessionOptions) (string, error) {
|
||||||
serviceName := strings.TrimSpace(options.ServiceName)
|
serviceName := strings.TrimSpace(options.ServiceName)
|
||||||
if serviceName == "" {
|
if serviceName == "" {
|
||||||
|
|
|
||||||
|
|
@ -60,12 +60,12 @@ func TestLoginBitwardenRunsInteractiveFlowAndPersistsSession(t *testing.T) {
|
||||||
t.Fatalf("interactive call #2 args = %v, want [unlock --raw]", calls[1])
|
t.Fatalf("interactive call #2 args = %v, want [unlock --raw]", calls[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
persisted, err := LoadBitwardenSession(BitwardenSessionOptions{ServiceName: "email-mcp"})
|
persisted, err := LoadBitwardenSession(BitwardenSessionOptions{ServiceName: bitwardenSharedSessionName})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("LoadBitwardenSession returned error: %v", err)
|
t.Fatalf("LoadBitwardenSession (shared) returned error: %v", err)
|
||||||
}
|
}
|
||||||
if persisted != "persisted-session" {
|
if persisted != "persisted-session" {
|
||||||
t.Fatalf("persisted session = %q, want persisted-session", persisted)
|
t.Fatalf("shared persisted session = %q, want persisted-session", persisted)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -207,6 +207,112 @@ func TestLoadBitwardenSessionReturnsNotFoundWhenFileMissing(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLoginBitwardenPersistsToSharedFileAfterUnlock(t *testing.T) {
|
||||||
|
t.Setenv("BW_SESSION", "")
|
||||||
|
configDir := t.TempDir()
|
||||||
|
withBitwardenUserConfigDir(t, func() (string, error) {
|
||||||
|
return configDir, nil
|
||||||
|
})
|
||||||
|
withBitwardenRunner(t, func(command string, stdin []byte, args ...string) ([]byte, error) {
|
||||||
|
if len(args) == 1 && args[0] == "status" {
|
||||||
|
return []byte(`{"status":"locked"}`), nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unexpected args: %v", args)
|
||||||
|
})
|
||||||
|
withBitwardenInteractiveRunner(t, func(command string, stdin io.Reader, stdout, stderr io.Writer, args ...string) ([]byte, error) {
|
||||||
|
if len(args) == 2 && args[0] == "unlock" && args[1] == "--raw" {
|
||||||
|
return []byte("shared-session\n"), nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unexpected interactive args: %v", args)
|
||||||
|
})
|
||||||
|
|
||||||
|
if _, err := LoginBitwarden(BitwardenLoginOptions{
|
||||||
|
ServiceName: "graylog-mcp",
|
||||||
|
BitwardenCommand: "bw",
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("LoginBitwarden returned error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
shared, err := LoadBitwardenSession(BitwardenSessionOptions{ServiceName: bitwardenSharedSessionName})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("LoadBitwardenSession (shared) returned error: %v", err)
|
||||||
|
}
|
||||||
|
if shared != "shared-session" {
|
||||||
|
t.Fatalf("shared session = %q, want shared-session", shared)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoginBitwardenSkipsUnlockWhenAlreadyUnlockedWithEnvSession(t *testing.T) {
|
||||||
|
t.Setenv("BW_SESSION", "existing-session")
|
||||||
|
configDir := t.TempDir()
|
||||||
|
withBitwardenUserConfigDir(t, func() (string, error) {
|
||||||
|
return configDir, nil
|
||||||
|
})
|
||||||
|
withBitwardenRunner(t, func(command string, stdin []byte, args ...string) ([]byte, error) {
|
||||||
|
if len(args) == 1 && args[0] == "status" {
|
||||||
|
return []byte(`{"status":"unlocked"}`), nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unexpected args: %v", args)
|
||||||
|
})
|
||||||
|
withBitwardenInteractiveRunner(t, func(command string, stdin io.Reader, stdout, stderr io.Writer, args ...string) ([]byte, error) {
|
||||||
|
return nil, fmt.Errorf("bw unlock must not be called when vault is already unlocked with a session: %v", args)
|
||||||
|
})
|
||||||
|
|
||||||
|
session, err := LoginBitwarden(BitwardenLoginOptions{
|
||||||
|
ServiceName: "email-mcp",
|
||||||
|
BitwardenCommand: "bw",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("LoginBitwarden returned error: %v", err)
|
||||||
|
}
|
||||||
|
if session != "existing-session" {
|
||||||
|
t.Fatalf("session = %q, want existing-session", session)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoginBitwardenSkipsUnlockWhenAlreadyUnlockedWithSharedSession(t *testing.T) {
|
||||||
|
t.Setenv("BW_SESSION", "")
|
||||||
|
configDir := t.TempDir()
|
||||||
|
withBitwardenUserConfigDir(t, func() (string, error) {
|
||||||
|
return configDir, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
// graylog-mcp logged in earlier and wrote to the shared file
|
||||||
|
if _, err := SaveBitwardenSession(BitwardenSessionOptions{ServiceName: bitwardenSharedSessionName}, "graylog-session"); err != nil {
|
||||||
|
t.Fatalf("SaveBitwardenSession returned error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
withBitwardenRunner(t, func(command string, stdin []byte, args ...string) ([]byte, error) {
|
||||||
|
if len(args) == 1 && args[0] == "status" {
|
||||||
|
return []byte(`{"status":"unlocked"}`), nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unexpected args: %v", args)
|
||||||
|
})
|
||||||
|
withBitwardenInteractiveRunner(t, func(command string, stdin io.Reader, stdout, stderr io.Writer, args ...string) ([]byte, error) {
|
||||||
|
return nil, fmt.Errorf("bw unlock must not be called when shared session exists: %v", args)
|
||||||
|
})
|
||||||
|
|
||||||
|
session, err := LoginBitwarden(BitwardenLoginOptions{
|
||||||
|
ServiceName: "email-mcp",
|
||||||
|
BitwardenCommand: "bw",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("LoginBitwarden returned error: %v", err)
|
||||||
|
}
|
||||||
|
if session != "graylog-session" {
|
||||||
|
t.Fatalf("session = %q, want graylog-session (from shared file)", session)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The shared session must also be persisted to the service-specific file
|
||||||
|
persisted, err := LoadBitwardenSession(BitwardenSessionOptions{ServiceName: "email-mcp"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("LoadBitwardenSession returned error: %v", err)
|
||||||
|
}
|
||||||
|
if persisted != "graylog-session" {
|
||||||
|
t.Fatalf("email-mcp persisted session = %q, want graylog-session", persisted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func withBitwardenInteractiveRunner(
|
func withBitwardenInteractiveRunner(
|
||||||
t *testing.T,
|
t *testing.T,
|
||||||
runner func(command string, stdin io.Reader, stdout, stderr io.Writer, args ...string) ([]byte, error),
|
runner func(command string, stdin io.Reader, stdout, stderr io.Writer, args ...string) ([]byte, error),
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,9 @@ func TestBitwardenStoreMissReturnsUnavailableWhenCommandIsMissing(t *testing.T)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBitwardenStoreMissFailsWhenSessionIsMissing(t *testing.T) {
|
func TestBitwardenStoreMissFailsWhenSessionIsMissing(t *testing.T) {
|
||||||
|
withBitwardenUserConfigDir(t, func() (string, error) {
|
||||||
|
return t.TempDir(), nil
|
||||||
|
})
|
||||||
fakeCLI := newFakeBitwardenCLI("bw")
|
fakeCLI := newFakeBitwardenCLI("bw")
|
||||||
withBitwardenRunner(t, fakeCLI.run)
|
withBitwardenRunner(t, fakeCLI.run)
|
||||||
|
|
||||||
|
|
@ -572,6 +575,9 @@ func TestBitwardenStoreDeleteSecretInvalidatesCache(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBitwardenFindItemTreatsEmptyListOutputAsNotFound(t *testing.T) {
|
func TestBitwardenFindItemTreatsEmptyListOutputAsNotFound(t *testing.T) {
|
||||||
|
withBitwardenUserConfigDir(t, func() (string, error) {
|
||||||
|
return t.TempDir(), nil
|
||||||
|
})
|
||||||
store := &bitwardenStore{command: "bw", serviceName: "email-mcp"}
|
store := &bitwardenStore{command: "bw", serviceName: "email-mcp"}
|
||||||
withBitwardenRunner(t, func(command string, stdin []byte, args ...string) ([]byte, error) {
|
withBitwardenRunner(t, func(command string, stdin []byte, args ...string) ([]byte, error) {
|
||||||
if len(args) == 4 && args[0] == "list" && args[1] == "items" && args[2] == "--search" {
|
if len(args) == 4 && args[0] == "list" && args[1] == "items" && args[2] == "--search" {
|
||||||
|
|
@ -728,6 +734,84 @@ func stripANSIControlSequences(value string) string {
|
||||||
return strings.ReplaceAll(noANSI, "\r", "")
|
return strings.ReplaceAll(noANSI, "\r", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBitwardenStorePicksUpSessionRotatedByAnotherProcess(t *testing.T) {
|
||||||
|
t.Setenv("BW_SESSION", "old-session")
|
||||||
|
configDir := t.TempDir()
|
||||||
|
withBitwardenUserConfigDir(t, func() (string, error) {
|
||||||
|
return configDir, nil
|
||||||
|
})
|
||||||
|
withBitwardenUserCacheDir(t, func() (string, error) {
|
||||||
|
return t.TempDir(), nil
|
||||||
|
})
|
||||||
|
fakeCLI := newFakeBitwardenCLI("bw")
|
||||||
|
fakeCLI.itemsByID["item-1"] = fakeBitwardenItem{
|
||||||
|
ID: "item-1",
|
||||||
|
Name: "email-mcp/api-token",
|
||||||
|
Secret: "secret-v1",
|
||||||
|
MarkerService: "email-mcp",
|
||||||
|
MarkerSecretName: "api-token",
|
||||||
|
}
|
||||||
|
withBitwardenRunner(t, fakeCLI.run)
|
||||||
|
|
||||||
|
store, err := Open(Options{
|
||||||
|
ServiceName: "email-mcp",
|
||||||
|
BackendPolicy: BackendBitwardenCLI,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Open returned error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := SaveBitwardenSession(BitwardenSessionOptions{ServiceName: bitwardenSharedSessionName}, "new-session"); err != nil {
|
||||||
|
t.Fatalf("SaveBitwardenSession returned error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := store.GetSecret("api-token"); err != nil {
|
||||||
|
t.Fatalf("GetSecret returned error: %v", err)
|
||||||
|
}
|
||||||
|
if got := os.Getenv("BW_SESSION"); got != "new-session" {
|
||||||
|
t.Fatalf("BW_SESSION = %q, want new-session after session rotation", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBitwardenStorePicksUpSessionFromFileWhenEnvIsEmpty(t *testing.T) {
|
||||||
|
t.Setenv("BW_SESSION", "")
|
||||||
|
configDir := t.TempDir()
|
||||||
|
withBitwardenUserConfigDir(t, func() (string, error) {
|
||||||
|
return configDir, nil
|
||||||
|
})
|
||||||
|
withBitwardenUserCacheDir(t, func() (string, error) {
|
||||||
|
return t.TempDir(), nil
|
||||||
|
})
|
||||||
|
fakeCLI := newFakeBitwardenCLI("bw")
|
||||||
|
fakeCLI.itemsByID["item-1"] = fakeBitwardenItem{
|
||||||
|
ID: "item-1",
|
||||||
|
Name: "email-mcp/api-token",
|
||||||
|
Secret: "secret-v1",
|
||||||
|
MarkerService: "email-mcp",
|
||||||
|
MarkerSecretName: "api-token",
|
||||||
|
}
|
||||||
|
withBitwardenRunner(t, fakeCLI.run)
|
||||||
|
|
||||||
|
store, err := Open(Options{
|
||||||
|
ServiceName: "email-mcp",
|
||||||
|
BackendPolicy: BackendBitwardenCLI,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Open returned error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := SaveBitwardenSession(BitwardenSessionOptions{ServiceName: bitwardenSharedSessionName}, "file-session"); err != nil {
|
||||||
|
t.Fatalf("SaveBitwardenSession returned error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := store.GetSecret("api-token"); err != nil {
|
||||||
|
t.Fatalf("GetSecret returned error: %v", err)
|
||||||
|
}
|
||||||
|
if got := os.Getenv("BW_SESSION"); got != "file-session" {
|
||||||
|
t.Fatalf("BW_SESSION = %q, want file-session loaded from file after login by another process", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestOpenFromManifestSupportsBitwardenCLIBackendPolicy(t *testing.T) {
|
func TestOpenFromManifestSupportsBitwardenCLIBackendPolicy(t *testing.T) {
|
||||||
withBitwardenSession(t)
|
withBitwardenSession(t)
|
||||||
fakeCLI := newFakeBitwardenCLI("bw")
|
fakeCLI := newFakeBitwardenCLI("bw")
|
||||||
|
|
@ -768,6 +852,9 @@ func withBitwardenSession(t *testing.T) {
|
||||||
withBitwardenUserCacheDir(t, func() (string, error) {
|
withBitwardenUserCacheDir(t, func() (string, error) {
|
||||||
return t.TempDir(), nil
|
return t.TempDir(), nil
|
||||||
})
|
})
|
||||||
|
withBitwardenUserConfigDir(t, func() (string, error) {
|
||||||
|
return t.TempDir(), nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func withBitwardenRunner(
|
func withBitwardenRunner(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue