From 29dd825ba57d9ed70ef090375507ad9d9b57eab4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 15:13:51 +0000 Subject: [PATCH 1/8] Initial plan From 32654f49a3643080c2d3a94e1c1c824c25c23670 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 15:24:34 +0000 Subject: [PATCH 2/8] Fix pass credential store to support .gpg-id in subdirectories (issue #2263) Co-authored-by: marekzmyslowski <1062877+marekzmyslowski@users.noreply.github.com> --- .../Posix/GnuPassCredentialStoreTests.cs | 56 +++++++++++++++++++ src/shared/Core/CredentialStore.cs | 12 ---- .../Interop/Posix/GpgPassCredentialStore.cs | 34 +++++++---- 3 files changed, 80 insertions(+), 22 deletions(-) diff --git a/src/shared/Core.Tests/Interop/Posix/GnuPassCredentialStoreTests.cs b/src/shared/Core.Tests/Interop/Posix/GnuPassCredentialStoreTests.cs index 7ff80f03d..0f27d8981 100644 --- a/src/shared/Core.Tests/Interop/Posix/GnuPassCredentialStoreTests.cs +++ b/src/shared/Core.Tests/Interop/Posix/GnuPassCredentialStoreTests.cs @@ -86,6 +86,40 @@ public void GnuPassCredentialStore_Remove_NotFound_ReturnsFalse() Assert.False(result); } + [PosixFact] + public void GnuPassCredentialStore_ReadWriteDelete_GpgIdInSubdirectory() + { + var fs = new TestFileSystem(); + var gpg = new TestGpg(fs); + string storeRoot = InitializePasswordStoreWithGpgIdInSubdirectory(fs, gpg, TestNamespace); + + var collection = new GpgPassCredentialStore(fs, gpg, storeRoot, TestNamespace); + + // Create a service that is guaranteed to be unique + string uniqueGuid = Guid.NewGuid().ToString("N"); + string service = $"https://example.com/{uniqueGuid}"; + const string userName = "john.doe"; + const string password = "letmein123"; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Fake credential")] + + try + { + // Write + collection.AddOrUpdate(service, userName, password); + + // Read + ICredential outCredential = collection.Get(service, userName); + + Assert.NotNull(outCredential); + Assert.Equal(userName, outCredential.Account); + Assert.Equal(password, outCredential.Password); + } + finally + { + // Ensure we clean up after ourselves even in case of 'get' failures + collection.Remove(service, userName); + } + } + private static string InitializePasswordStore(TestFileSystem fs, TestGpg gpg) { string homePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); @@ -102,5 +136,27 @@ private static string InitializePasswordStore(TestFileSystem fs, TestGpg gpg) return storePath; } + + private static string InitializePasswordStoreWithGpgIdInSubdirectory(TestFileSystem fs, TestGpg gpg, string subdirectory) + { + string homePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + string storePath = Path.Combine(homePath, ".password-store"); + string userId = "gcm-test@example.com"; + + // Place .gpg-id only in the namespace subdirectory (not the store root), + // simulating a pass store where the root has no .gpg-id but submodules do. + string subDirPath = Path.Combine(storePath, subdirectory); + string gpgIdPath = Path.Combine(subDirPath, ".gpg-id"); + + // Ensure we have a GPG key for use with testing + gpg.GenerateKeys(userId); + + // Init the password store with .gpg-id only in the subdirectory + fs.Directories.Add(storePath); + fs.Directories.Add(subDirPath); + fs.Files[gpgIdPath] = Encoding.UTF8.GetBytes(userId); + + return storePath; + } } } diff --git a/src/shared/Core/CredentialStore.cs b/src/shared/Core/CredentialStore.cs index 11dc83818..95d26df32 100644 --- a/src/shared/Core/CredentialStore.cs +++ b/src/shared/Core/CredentialStore.cs @@ -291,18 +291,6 @@ private void ValidateGpgPass(out string storeRoot, out string execPath) storeRoot = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".password-store"); } - // Check we have a GPG ID to sign credential files with - string gpgIdFile = Path.Combine(storeRoot, ".gpg-id"); - if (!_context.FileSystem.FileExists(gpgIdFile)) - { - var format = - "Password store has not been initialized at '{0}'; run `pass init ` to initialize the store."; - var message = string.Format(format, storeRoot); - _context.Trace2.WriteError(message); - throw new Exception(message + Environment.NewLine + - $"See {Constants.HelpUrls.GcmCredentialStores} for more information." - ); - } } private void ValidateCredentialCache(out string options) diff --git a/src/shared/Core/Interop/Posix/GpgPassCredentialStore.cs b/src/shared/Core/Interop/Posix/GpgPassCredentialStore.cs index 6ed56c693..3181f32a1 100644 --- a/src/shared/Core/Interop/Posix/GpgPassCredentialStore.cs +++ b/src/shared/Core/Interop/Posix/GpgPassCredentialStore.cs @@ -21,19 +21,33 @@ public GpgPassCredentialStore(IFileSystem fileSystem, IGpg gpg, string storeRoot protected override string CredentialFileExtension => ".gpg"; - private string GetGpgId() + private string GetGpgId(string credentialPath) { - string gpgIdPath = Path.Combine(StoreRoot, ".gpg-id"); - if (!FileSystem.FileExists(gpgIdPath)) + // Walk up from the credential's directory to the store root, looking for a .gpg-id file. + // This mimics the behavior of pass, which uses the nearest .gpg-id in the directory hierarchy. + string dir = Path.GetDirectoryName(credentialPath); + while (dir != null) { - throw new Exception($"Cannot find GPG ID in '{gpgIdPath}'; password store has not been initialized"); - } + string gpgIdPath = Path.Combine(dir, ".gpg-id"); + if (FileSystem.FileExists(gpgIdPath)) + { + using (var stream = FileSystem.OpenFileStream(gpgIdPath, FileMode.Open, FileAccess.Read, FileShare.Read)) + using (var reader = new StreamReader(stream)) + { + return reader.ReadLine(); + } + } - using (var stream = FileSystem.OpenFileStream(gpgIdPath, FileMode.Open, FileAccess.Read, FileShare.Read)) - using (var reader = new StreamReader(stream)) - { - return reader.ReadLine(); + // Stop after checking the store root + if (FileSystem.IsSamePath(dir, StoreRoot)) + { + break; + } + + dir = Path.GetDirectoryName(dir); } + + throw new Exception($"Cannot find GPG ID in password store at '{StoreRoot}'; run `pass init ` to initialize the store."); } protected override bool TryDeserializeCredential(string path, out FileCredential credential) @@ -68,7 +82,7 @@ protected override bool TryDeserializeCredential(string path, out FileCredential protected override void SerializeCredential(FileCredential credential) { - string gpgId = GetGpgId(); + string gpgId = GetGpgId(credential.FullPath); var sb = new StringBuilder(credential.Password); sb.AppendFormat("{1}service={0}{1}", credential.Service, Environment.NewLine); From c5128fab3b7db2269e234e4cf070089d48c13692 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 15:43:06 +0000 Subject: [PATCH 3/8] Remove credentialPath param from GetGpgId, use StoreRoot with EnumerateFiles Co-authored-by: marekzmyslowski <1062877+marekzmyslowski@users.noreply.github.com> --- .../Interop/Posix/GpgPassCredentialStore.cs | 30 ++++++------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/src/shared/Core/Interop/Posix/GpgPassCredentialStore.cs b/src/shared/Core/Interop/Posix/GpgPassCredentialStore.cs index 3181f32a1..e560ff920 100644 --- a/src/shared/Core/Interop/Posix/GpgPassCredentialStore.cs +++ b/src/shared/Core/Interop/Posix/GpgPassCredentialStore.cs @@ -21,30 +21,18 @@ public GpgPassCredentialStore(IFileSystem fileSystem, IGpg gpg, string storeRoot protected override string CredentialFileExtension => ".gpg"; - private string GetGpgId(string credentialPath) + private string GetGpgId() { - // Walk up from the credential's directory to the store root, looking for a .gpg-id file. - // This mimics the behavior of pass, which uses the nearest .gpg-id in the directory hierarchy. - string dir = Path.GetDirectoryName(credentialPath); - while (dir != null) + // Search for a .gpg-id file anywhere under the store root. + // This handles configurations where .gpg-id is in a subdirectory + // (e.g., a git submodule) rather than the store root itself. + foreach (string gpgIdPath in FileSystem.EnumerateFiles(StoreRoot, ".gpg-id")) { - string gpgIdPath = Path.Combine(dir, ".gpg-id"); - if (FileSystem.FileExists(gpgIdPath)) + using (var stream = FileSystem.OpenFileStream(gpgIdPath, FileMode.Open, FileAccess.Read, FileShare.Read)) + using (var reader = new StreamReader(stream)) { - using (var stream = FileSystem.OpenFileStream(gpgIdPath, FileMode.Open, FileAccess.Read, FileShare.Read)) - using (var reader = new StreamReader(stream)) - { - return reader.ReadLine(); - } + return reader.ReadLine(); } - - // Stop after checking the store root - if (FileSystem.IsSamePath(dir, StoreRoot)) - { - break; - } - - dir = Path.GetDirectoryName(dir); } throw new Exception($"Cannot find GPG ID in password store at '{StoreRoot}'; run `pass init ` to initialize the store."); @@ -82,7 +70,7 @@ protected override bool TryDeserializeCredential(string path, out FileCredential protected override void SerializeCredential(FileCredential credential) { - string gpgId = GetGpgId(credential.FullPath); + string gpgId = GetGpgId(); var sb = new StringBuilder(credential.Password); sb.AppendFormat("{1}service={0}{1}", credential.Service, Environment.NewLine); From b879c6371ec6e22120c7ecf30a2144c04bf9058a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 15:44:52 +0000 Subject: [PATCH 4/8] Use StoreRoot instead of credentialPath parameter in GetGpgId Co-authored-by: marekzmyslowski <1062877+marekzmyslowski@users.noreply.github.com> --- .../Interop/Posix/GpgPassCredentialStore.cs | 28 +++++-------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/src/shared/Core/Interop/Posix/GpgPassCredentialStore.cs b/src/shared/Core/Interop/Posix/GpgPassCredentialStore.cs index 3181f32a1..9780b4179 100644 --- a/src/shared/Core/Interop/Posix/GpgPassCredentialStore.cs +++ b/src/shared/Core/Interop/Posix/GpgPassCredentialStore.cs @@ -21,30 +21,16 @@ public GpgPassCredentialStore(IFileSystem fileSystem, IGpg gpg, string storeRoot protected override string CredentialFileExtension => ".gpg"; - private string GetGpgId(string credentialPath) + private string GetGpgId() { - // Walk up from the credential's directory to the store root, looking for a .gpg-id file. - // This mimics the behavior of pass, which uses the nearest .gpg-id in the directory hierarchy. - string dir = Path.GetDirectoryName(credentialPath); - while (dir != null) + string gpgIdPath = Path.Combine(StoreRoot, ".gpg-id"); + if (FileSystem.FileExists(gpgIdPath)) { - string gpgIdPath = Path.Combine(dir, ".gpg-id"); - if (FileSystem.FileExists(gpgIdPath)) + using (var stream = FileSystem.OpenFileStream(gpgIdPath, FileMode.Open, FileAccess.Read, FileShare.Read)) + using (var reader = new StreamReader(stream)) { - using (var stream = FileSystem.OpenFileStream(gpgIdPath, FileMode.Open, FileAccess.Read, FileShare.Read)) - using (var reader = new StreamReader(stream)) - { - return reader.ReadLine(); - } + return reader.ReadLine(); } - - // Stop after checking the store root - if (FileSystem.IsSamePath(dir, StoreRoot)) - { - break; - } - - dir = Path.GetDirectoryName(dir); } throw new Exception($"Cannot find GPG ID in password store at '{StoreRoot}'; run `pass init ` to initialize the store."); @@ -82,7 +68,7 @@ protected override bool TryDeserializeCredential(string path, out FileCredential protected override void SerializeCredential(FileCredential credential) { - string gpgId = GetGpgId(credential.FullPath); + string gpgId = GetGpgId(); var sb = new StringBuilder(credential.Password); sb.AppendFormat("{1}service={0}{1}", credential.Service, Environment.NewLine); From 6d53ee6beb8089496612ccd5cbfa9606f261eeb8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 15:45:48 +0000 Subject: [PATCH 5/8] Use StoreRoot with EnumerateFiles instead of credentialPath parameter in GetGpgId Co-authored-by: marekzmyslowski <1062877+marekzmyslowski@users.noreply.github.com> --- src/shared/Core/Interop/Posix/GpgPassCredentialStore.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/shared/Core/Interop/Posix/GpgPassCredentialStore.cs b/src/shared/Core/Interop/Posix/GpgPassCredentialStore.cs index 9780b4179..e560ff920 100644 --- a/src/shared/Core/Interop/Posix/GpgPassCredentialStore.cs +++ b/src/shared/Core/Interop/Posix/GpgPassCredentialStore.cs @@ -23,8 +23,10 @@ public GpgPassCredentialStore(IFileSystem fileSystem, IGpg gpg, string storeRoot private string GetGpgId() { - string gpgIdPath = Path.Combine(StoreRoot, ".gpg-id"); - if (FileSystem.FileExists(gpgIdPath)) + // Search for a .gpg-id file anywhere under the store root. + // This handles configurations where .gpg-id is in a subdirectory + // (e.g., a git submodule) rather than the store root itself. + foreach (string gpgIdPath in FileSystem.EnumerateFiles(StoreRoot, ".gpg-id")) { using (var stream = FileSystem.OpenFileStream(gpgIdPath, FileMode.Open, FileAccess.Read, FileShare.Read)) using (var reader = new StreamReader(stream)) From 29f16b367c81c6fe06987754ad8f5fee1603aa51 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 10:05:05 +0000 Subject: [PATCH 6/8] Fix GetGpgId: walk up from credential path to StoreRoot per GNU Pass behaviour Co-authored-by: marekzmyslowski <1062877+marekzmyslowski@users.noreply.github.com> --- .../Posix/GnuPassCredentialStoreTests.cs | 50 +++++++++++++++++++ .../Interop/Posix/GpgPassCredentialStore.cs | 30 +++++++---- 2 files changed, 71 insertions(+), 9 deletions(-) diff --git a/src/shared/Core.Tests/Interop/Posix/GnuPassCredentialStoreTests.cs b/src/shared/Core.Tests/Interop/Posix/GnuPassCredentialStoreTests.cs index 0f27d8981..4bcafb5e7 100644 --- a/src/shared/Core.Tests/Interop/Posix/GnuPassCredentialStoreTests.cs +++ b/src/shared/Core.Tests/Interop/Posix/GnuPassCredentialStoreTests.cs @@ -120,6 +120,56 @@ public void GnuPassCredentialStore_ReadWriteDelete_GpgIdInSubdirectory() } } + [PosixFact] + public void GnuPassCredentialStore_WriteCredential_MultipleGpgIds_UsesNearestGpgId() + { + // Verify that when two subdirectories each have their own .gpg-id, encrypting a credential + // under one subdirectory uses that subdirectory's GPG identity, not the other one. + var fs = new TestFileSystem(); + var gpg = new TestGpg(fs); + + string homePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + string storePath = Path.Combine(homePath, ".password-store"); + + const string personalUserId = "personal@example.com"; + const string workUserId = "work@example.com"; + + // Only register the personal key; if the wrong (work) key is picked, EncryptFile will throw. + gpg.GenerateKeys(personalUserId); + + string personalSubDir = Path.Combine(storePath, "personal"); + string workSubDir = Path.Combine(storePath, "work"); + + fs.Directories.Add(storePath); + fs.Directories.Add(personalSubDir); + fs.Directories.Add(workSubDir); + fs.Files[Path.Combine(personalSubDir, ".gpg-id")] = Encoding.UTF8.GetBytes(personalUserId); + fs.Files[Path.Combine(workSubDir, ".gpg-id")] = Encoding.UTF8.GetBytes(workUserId); + + // Use "personal" namespace so credentials are stored under storePath/personal/... + var collection = new GpgPassCredentialStore(fs, gpg, storePath, "personal"); + + string service = $"https://example.com/{Guid.NewGuid():N}"; + const string userName = "john.doe"; + const string password = "letmein123"; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Fake credential")] + + try + { + // Write - should pick personal/.gpg-id (personalUserId), not work/.gpg-id (workUserId) + collection.AddOrUpdate(service, userName, password); + + ICredential outCredential = collection.Get(service, userName); + + Assert.NotNull(outCredential); + Assert.Equal(userName, outCredential.Account); + Assert.Equal(password, outCredential.Password); + } + finally + { + collection.Remove(service, userName); + } + } + private static string InitializePasswordStore(TestFileSystem fs, TestGpg gpg) { string homePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); diff --git a/src/shared/Core/Interop/Posix/GpgPassCredentialStore.cs b/src/shared/Core/Interop/Posix/GpgPassCredentialStore.cs index e560ff920..debc9c815 100644 --- a/src/shared/Core/Interop/Posix/GpgPassCredentialStore.cs +++ b/src/shared/Core/Interop/Posix/GpgPassCredentialStore.cs @@ -21,18 +21,30 @@ public GpgPassCredentialStore(IFileSystem fileSystem, IGpg gpg, string storeRoot protected override string CredentialFileExtension => ".gpg"; - private string GetGpgId() + private string GetGpgId(string credentialFullPath) { - // Search for a .gpg-id file anywhere under the store root. - // This handles configurations where .gpg-id is in a subdirectory - // (e.g., a git submodule) rather than the store root itself. - foreach (string gpgIdPath in FileSystem.EnumerateFiles(StoreRoot, ".gpg-id")) + // Walk up from the credential's directory to the store root, looking for a .gpg-id file. + // This mimics the behaviour of GNU Pass, which uses the nearest .gpg-id in the directory hierarchy. + string dir = Path.GetDirectoryName(credentialFullPath); + while (dir != null) { - using (var stream = FileSystem.OpenFileStream(gpgIdPath, FileMode.Open, FileAccess.Read, FileShare.Read)) - using (var reader = new StreamReader(stream)) + string gpgIdPath = Path.Combine(dir, ".gpg-id"); + if (FileSystem.FileExists(gpgIdPath)) { - return reader.ReadLine(); + using (var stream = FileSystem.OpenFileStream(gpgIdPath, FileMode.Open, FileAccess.Read, FileShare.Read)) + using (var reader = new StreamReader(stream)) + { + return reader.ReadLine(); + } } + + // Stop after checking the store root + if (FileSystem.IsSamePath(dir, StoreRoot)) + { + break; + } + + dir = Path.GetDirectoryName(dir); } throw new Exception($"Cannot find GPG ID in password store at '{StoreRoot}'; run `pass init ` to initialize the store."); @@ -70,7 +82,7 @@ protected override bool TryDeserializeCredential(string path, out FileCredential protected override void SerializeCredential(FileCredential credential) { - string gpgId = GetGpgId(); + string gpgId = GetGpgId(credential.FullPath); var sb = new StringBuilder(credential.Password); sb.AppendFormat("{1}service={0}{1}", credential.Service, Environment.NewLine); From 50bae928b52b65d0db98f2601337bc568cbe573e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 11:18:46 +0000 Subject: [PATCH 7/8] Remove fake credentials from test file: use Guid.NewGuid() for test passwords Co-authored-by: marekzmyslowski <1062877+marekzmyslowski@users.noreply.github.com> --- .../Core.Tests/Interop/Posix/GnuPassCredentialStoreTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shared/Core.Tests/Interop/Posix/GnuPassCredentialStoreTests.cs b/src/shared/Core.Tests/Interop/Posix/GnuPassCredentialStoreTests.cs index 4bcafb5e7..220ba1a9f 100644 --- a/src/shared/Core.Tests/Interop/Posix/GnuPassCredentialStoreTests.cs +++ b/src/shared/Core.Tests/Interop/Posix/GnuPassCredentialStoreTests.cs @@ -99,7 +99,7 @@ public void GnuPassCredentialStore_ReadWriteDelete_GpgIdInSubdirectory() string uniqueGuid = Guid.NewGuid().ToString("N"); string service = $"https://example.com/{uniqueGuid}"; const string userName = "john.doe"; - const string password = "letmein123"; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Fake credential")] + string password = Guid.NewGuid().ToString("N"); try { @@ -151,7 +151,7 @@ public void GnuPassCredentialStore_WriteCredential_MultipleGpgIds_UsesNearestGpg string service = $"https://example.com/{Guid.NewGuid():N}"; const string userName = "john.doe"; - const string password = "letmein123"; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Fake credential")] + string password = Guid.NewGuid().ToString("N"); try { From b6415774ced93c4f28aa9eb477faf3cbebb1a24f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Mar 2026 14:31:14 +0000 Subject: [PATCH 8/8] Inline InitializePasswordStoreWithGpgIdInSubdirectory into its only test Co-authored-by: mjcheetham <5658207+mjcheetham@users.noreply.github.com> --- .../Posix/GnuPassCredentialStoreTests.cs | 44 +++++++------------ 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/src/shared/Core.Tests/Interop/Posix/GnuPassCredentialStoreTests.cs b/src/shared/Core.Tests/Interop/Posix/GnuPassCredentialStoreTests.cs index 220ba1a9f..eb87e8c57 100644 --- a/src/shared/Core.Tests/Interop/Posix/GnuPassCredentialStoreTests.cs +++ b/src/shared/Core.Tests/Interop/Posix/GnuPassCredentialStoreTests.cs @@ -91,13 +91,25 @@ public void GnuPassCredentialStore_ReadWriteDelete_GpgIdInSubdirectory() { var fs = new TestFileSystem(); var gpg = new TestGpg(fs); - string storeRoot = InitializePasswordStoreWithGpgIdInSubdirectory(fs, gpg, TestNamespace); - var collection = new GpgPassCredentialStore(fs, gpg, storeRoot, TestNamespace); + string homePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + string storePath = Path.Combine(homePath, ".password-store"); + const string userId = "gcm-test@example.com"; - // Create a service that is guaranteed to be unique - string uniqueGuid = Guid.NewGuid().ToString("N"); - string service = $"https://example.com/{uniqueGuid}"; + // Place .gpg-id only in the namespace subdirectory (not the store root), + // simulating a pass store where the root has no .gpg-id but submodules do. + string subDirPath = Path.Combine(storePath, TestNamespace); + string gpgIdPath = Path.Combine(subDirPath, ".gpg-id"); + + gpg.GenerateKeys(userId); + + fs.Directories.Add(storePath); + fs.Directories.Add(subDirPath); + fs.Files[gpgIdPath] = Encoding.UTF8.GetBytes(userId); + + var collection = new GpgPassCredentialStore(fs, gpg, storePath, TestNamespace); + + string service = $"https://example.com/{Guid.NewGuid():N}"; const string userName = "john.doe"; string password = Guid.NewGuid().ToString("N"); @@ -186,27 +198,5 @@ private static string InitializePasswordStore(TestFileSystem fs, TestGpg gpg) return storePath; } - - private static string InitializePasswordStoreWithGpgIdInSubdirectory(TestFileSystem fs, TestGpg gpg, string subdirectory) - { - string homePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); - string storePath = Path.Combine(homePath, ".password-store"); - string userId = "gcm-test@example.com"; - - // Place .gpg-id only in the namespace subdirectory (not the store root), - // simulating a pass store where the root has no .gpg-id but submodules do. - string subDirPath = Path.Combine(storePath, subdirectory); - string gpgIdPath = Path.Combine(subDirPath, ".gpg-id"); - - // Ensure we have a GPG key for use with testing - gpg.GenerateKeys(userId); - - // Init the password store with .gpg-id only in the subdirectory - fs.Directories.Add(storePath); - fs.Directories.Add(subDirPath); - fs.Files[gpgIdPath] = Encoding.UTF8.GetBytes(userId); - - return storePath; - } } }