Skip to content

Commit ee4483d

Browse files
authored
Merge pull request #25 from mjcheetham/erase-userpass-match
Only erase credentials that match the username/password if specified
2 parents 3b4a1b9 + 939242d commit ee4483d

File tree

2 files changed

+122
-3
lines changed

2 files changed

+122
-3
lines changed

common/src/Microsoft.Git.CredentialManager/Commands/EraseCommand.cs

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT license.
3+
using System;
34
using System.Threading.Tasks;
5+
using Microsoft.Git.CredentialManager.SecureStorage;
46

57
namespace Microsoft.Git.CredentialManager.Commands
68
{
@@ -16,13 +18,46 @@ public EraseCommand(IHostProviderRegistry hostProviderRegistry)
1618

1719
protected override Task ExecuteInternalAsync(ICommandContext context, InputArguments input, IHostProvider provider, string credentialKey)
1820
{
19-
context.Trace.WriteLine("Erasing credential...");
21+
// Try to locate an existing credential with the computed key
22+
context.Trace.WriteLine("Looking for existing credential in store...");
23+
ICredential credential = context.CredentialStore.Get(credentialKey);
24+
if (credential == null)
25+
{
26+
context.Trace.WriteLine("No stored credential was found.");
27+
return Task.CompletedTask;
28+
}
29+
else
30+
{
31+
context.Trace.WriteLine("Existing credential found.");
32+
}
33+
34+
// If we've been given a specific username and/or password we should only proceed
35+
// to erase the stored credential if they match exactly
36+
if (!string.IsNullOrWhiteSpace(input.UserName) && !StringComparer.Ordinal.Equals(input.UserName, credential.UserName))
37+
{
38+
context.Trace.WriteLine("Stored username does not match specified username - not erasing credential.");
39+
context.Trace.WriteLine($"\tInput username={input.UserName}");
40+
context.Trace.WriteLine($"\tStored username={credential.UserName}");
41+
return Task.CompletedTask;
42+
}
43+
44+
if (!string.IsNullOrWhiteSpace(input.Password) && !StringComparer.Ordinal.Equals(input.Password, credential.Password))
45+
{
46+
context.Trace.WriteLine("Stored password does not match specified password - not erasing credential.");
47+
context.Trace.WriteLineSecrets("\tInput password={0}", new object[] {input.Password});
48+
context.Trace.WriteLineSecrets("\tStored password={0}", new object[] {credential.Password});
49+
return Task.CompletedTask;
50+
}
2051

21-
// Delete the credential in the store.
52+
context.Trace.WriteLine("Erasing stored credential...");
2253
if (context.CredentialStore.Remove(credentialKey))
2354
{
2455
context.Trace.WriteLine("Credential was successfully erased.");
2556
}
57+
else
58+
{
59+
context.Trace.WriteLine("Credential erase failed.");
60+
}
2661

2762
return Task.CompletedTask;
2863
}

common/tests/Microsoft.Git.CredentialManager.Tests/Commands/EraseCommandTests.cs

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public void EraseCommand_CanExecuteAsync(string argString, bool expected)
3030
}
3131

3232
[Fact]
33-
public async Task EraseCommand_ExecuteAsync_CredentialExists_ErasesCredential()
33+
public async Task EraseCommand_ExecuteAsync_NoInputUserPass_CredentialExists_ErasesCredential()
3434
{
3535
const string testCredentialKey = "test-cred-key";
3636

@@ -57,6 +57,90 @@ public async Task EraseCommand_ExecuteAsync_CredentialExists_ErasesCredential()
5757
Assert.True(context.CredentialStore.ContainsKey("git:credential2"));
5858
}
5959

60+
[Fact]
61+
public async Task EraseCommand_ExecuteAsync_InputUserPass_CredentialExists_UserNotMatch_DoesNothing()
62+
{
63+
const string testUserName = "john.doe";
64+
const string testPassword = "letmein123";
65+
const string testCredentialKey = "test-cred-key";
66+
string stdIn = $"username={testUserName}\npassword={testPassword}\n\n";
67+
68+
var provider = new TestHostProvider {CredentialKey = testCredentialKey};
69+
var providerRegistry = new TestHostProviderRegistry {Provider = provider};
70+
var context = new TestCommandContext
71+
{
72+
StdIn = stdIn,
73+
CredentialStore =
74+
{
75+
[$"git:{testCredentialKey}"] = new GitCredential("different-username", testPassword),
76+
}
77+
};
78+
79+
string[] cmdArgs = {"erase"};
80+
var command = new EraseCommand(providerRegistry);
81+
82+
await command.ExecuteAsync(context, cmdArgs);
83+
84+
Assert.Equal(1, context.CredentialStore.Count);
85+
Assert.True(context.CredentialStore.ContainsKey($"git:{testCredentialKey}"));
86+
}
87+
88+
[Fact]
89+
public async Task EraseCommand_ExecuteAsync_InputUserPass_CredentialExists_PassNotMatch_DoesNothing()
90+
{
91+
const string testUserName = "john.doe";
92+
const string testPassword = "letmein123";
93+
const string testCredentialKey = "test-cred-key";
94+
string stdIn = $"username={testUserName}\npassword={testPassword}\n\n";
95+
96+
var provider = new TestHostProvider {CredentialKey = testCredentialKey};
97+
var providerRegistry = new TestHostProviderRegistry {Provider = provider};
98+
var context = new TestCommandContext
99+
{
100+
StdIn = stdIn,
101+
CredentialStore =
102+
{
103+
[$"git:{testCredentialKey}"] = new GitCredential(testUserName, "different-password"),
104+
}
105+
};
106+
107+
string[] cmdArgs = {"erase"};
108+
var command = new EraseCommand(providerRegistry);
109+
110+
await command.ExecuteAsync(context, cmdArgs);
111+
112+
Assert.Equal(1, context.CredentialStore.Count);
113+
Assert.True(context.CredentialStore.ContainsKey($"git:{testCredentialKey}"));
114+
}
115+
116+
[Fact]
117+
public async Task EraseCommand_ExecuteAsync_InputUserPass_CredentialExists_UserPassMatch_ErasesCredential()
118+
{
119+
const string testUserName = "john.doe";
120+
const string testPassword = "letmein123";
121+
const string testCredentialKey = "test-cred-key";
122+
string stdIn = $"username={testUserName}\npassword={testPassword}\n\n";
123+
124+
var provider = new TestHostProvider {CredentialKey = testCredentialKey};
125+
var providerRegistry = new TestHostProviderRegistry {Provider = provider};
126+
var context = new TestCommandContext
127+
{
128+
StdIn = stdIn,
129+
CredentialStore =
130+
{
131+
[$"git:{testCredentialKey}"] = new GitCredential(testUserName, testPassword),
132+
}
133+
};
134+
135+
string[] cmdArgs = {"erase"};
136+
var command = new EraseCommand(providerRegistry);
137+
138+
await command.ExecuteAsync(context, cmdArgs);
139+
140+
Assert.Equal(0, context.CredentialStore.Count);
141+
Assert.False(context.CredentialStore.ContainsKey($"git:{testCredentialKey}"));
142+
}
143+
60144
[Fact]
61145
public async Task EraseCommand_ExecuteAsync_NoCredential_DoesNothing()
62146
{

0 commit comments

Comments
 (0)