diff --git a/Microsoft.Alm.Authentication/Src/BasicAuthentication.cs b/Microsoft.Alm.Authentication/Src/BasicAuthentication.cs index e1267a013..081fecea0 100644 --- a/Microsoft.Alm.Authentication/Src/BasicAuthentication.cs +++ b/Microsoft.Alm.Authentication/Src/BasicAuthentication.cs @@ -154,7 +154,32 @@ public override async Task DeleteCredentials(TargetUri targetUri) if (targetUri is null) throw new ArgumentNullException(nameof(targetUri)); - return await _credentialStore.DeleteCredentials(targetUri); + // Delete the credentials for the explicit target uri first. + + var initResult = await _credentialStore.DeleteCredentials(targetUri); + + // If we deleted per user then we should try and delete the host level credentials too if + // they match the username. + var hostTargetUri = new TargetUri(targetUri.ToString(false, true, true)); + var hostCredentials = await GetCredentials(hostTargetUri); + + if (hostCredentials is null) + { + Trace.WriteLine($"No entry found for {hostTargetUri}, nothing more to delete"); + return initResult; + } + + var hostUsername = hostCredentials.Username; + var encodedUsername = Uri.EscapeDataString(targetUri.UserInfo); + if (encodedUsername != hostUsername) + { + Trace.WriteLine($"{hostTargetUri} entry has username {hostUsername} != {encodedUsername}, not deleting"); + return initResult; + } + + Trace.WriteLine($"Also deleting generic entry for {hostTargetUri} with username {hostUsername}"); + return await _credentialStore.DeleteCredentials(hostTargetUri); + } public override async Task GetCredentials(TargetUri targetUri) diff --git a/Microsoft.Alm.Authentication/Test/BasicAuthTests.cs b/Microsoft.Alm.Authentication/Test/BasicAuthTests.cs index ea20adb29..2c7ab66f9 100644 --- a/Microsoft.Alm.Authentication/Test/BasicAuthTests.cs +++ b/Microsoft.Alm.Authentication/Test/BasicAuthTests.cs @@ -24,6 +24,23 @@ public async Task BasicAuthDeleteCredentialsTest() Assert.Null(await basicAuth.CredentialStore.ReadCredentials(targetUri)); } + [Fact] + public async Task BasicAuthUserUriDeleteCredentialsTest() + { + TargetUri targetUserUri = new TargetUri("http://username@localhost"); + TargetUri targetGenericUri = new TargetUri("http://localhost"); + BasicAuthentication basicAuth = GetBasicAuthentication(RuntimeContext.Default, "basic-delete-user"); + + await basicAuth.CredentialStore.WriteCredentials(targetUserUri, new Credential("username", "password")); + await basicAuth.CredentialStore.WriteCredentials(targetGenericUri, new Credential("username", "password")); + + /* User-included format is what comes out of "erase" action, so that's what we want to test */ + await basicAuth.DeleteCredentials(targetUserUri); + + Assert.Null(await basicAuth.CredentialStore.ReadCredentials(targetUserUri)); + Assert.Null(await basicAuth.CredentialStore.ReadCredentials(targetGenericUri)); + } + [Fact] public async Task BasicAuthGetCredentialsTest() { @@ -41,6 +58,26 @@ public async Task BasicAuthGetCredentialsTest() Assert.NotNull(credentials = await basicAuth.GetCredentials(targetUri)); } + [Fact] + public async Task BasicAuthUserUriGetCredentialsTest() + { + TargetUri targetUserUri = new TargetUri("http://username@localhost"); + TargetUri targetGenericUri = new TargetUri("http://localhost"); + + BasicAuthentication basicAuth = GetBasicAuthentication(RuntimeContext.Default, "basic-get-user"); + + Credential credentials = null; + + Assert.Null(credentials = await basicAuth.GetCredentials(targetGenericUri)); + Assert.Null(credentials = await basicAuth.GetCredentials(targetUserUri)); + + credentials = new Credential("username", "password"); + + await basicAuth.CredentialStore.WriteCredentials(targetGenericUri, credentials); + + Assert.NotNull(credentials = await basicAuth.GetCredentials(targetUserUri)); + } + [Fact] public async Task BasicAuthSetCredentialsTest() {