Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: #1802 confirm cached credential before rejecting #1803

Merged
merged 3 commits into from
Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions GVFS/GVFS.Common/Git/GitAuthentication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,27 @@ public void RejectCredentials(ITracer tracer, string credentialString)
{
lock (this.gitAuthLock)
{
var cachedCredentialAtStartOfReject = this.cachedCredentialString;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The norm in this repo is to not use var unless the right-hand-side is a constructor explicitly mentioning the type that the variable will become.

Suggested change
var cachedCredentialAtStartOfReject = this.cachedCredentialString;
string cachedCredentialAtStartOfReject = this.cachedCredentialString;

// Don't stomp a different credential
if (credentialString == this.cachedCredentialString && this.cachedCredentialString != null)
if (credentialString == cachedCredentialAtStartOfReject && cachedCredentialAtStartOfReject != null)
{
// We can't assume that the credential store's cached credential is the same as the one we have.
// Reload the credential from the store to ensure we're rejecting the correct one.
var attemptsBeforeCheckingExistingCredential = this.numberOfAttempts;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The norm in this repo is to not use var unless the right-hand-side is a constructor explicitly mentioning the type that the variable will become.

Suggested change
var attemptsBeforeCheckingExistingCredential = this.numberOfAttempts;
int attemptsBeforeCheckingExistingCredential = this.numberOfAttempts;

if (this.TryCallGitCredential(tracer, out string getCredentialError))
{
if (this.cachedCredentialString != cachedCredentialAtStartOfReject)
{
// If the store already had a different credential, we don't want to reject it without trying it.
this.isCachedCredentialStringApproved = false;
return;
}
}
else
{
tracer.RelatedWarning(getCredentialError);
}

// If we can we should pass the actual username/password values we used (and found to be invalid)
// to `git-credential reject` so the credential helpers can attempt to check if they're erasing
// the expected credentials, if they so choose to.
Expand Down Expand Up @@ -121,7 +139,12 @@ public void RejectCredentials(ITracer tracer, string credentialString)

this.cachedCredentialString = null;
this.isCachedCredentialStringApproved = false;
this.UpdateBackoff();

// Backoff may have already been incremented by a failure in TryCallGitCredential
if (attemptsBeforeCheckingExistingCredential == this.numberOfAttempts)
{
this.UpdateBackoff();
}
}
}
}
Expand Down
28 changes: 28 additions & 0 deletions GVFS/GVFS.UnitTests/Git/GitAuthenticationTests.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Linq;
using GVFS.Common.Git;
using GVFS.Tests;
Expand Down Expand Up @@ -245,6 +246,33 @@ public void DontStoreDifferentCredentialFromCachedValue()
gitProcess.StoredCredentials.Single().Key.ShouldEqual("mock://repoUrl");
}

[TestCase]
public void RejectionShouldNotBeSentIfUnderlyingTokenHasChanged()
{
MockTracer tracer = new MockTracer();
MockGitProcess gitProcess = this.GetGitProcess();

GitAuthentication dut = new GitAuthentication(gitProcess, "mock://repoUrl");
dut.TryInitializeAndRequireAuth(tracer, out _);

// Get and store an initial value that will be cached
string authString;
dut.TryGetCredentials(tracer, out authString, out _).ShouldBeTrue();
dut.ApproveCredentials(tracer, authString);

// Change the underlying token
gitProcess.SetExpectedCommandResult(
$"{AzureDevOpsUseHttpPathString} credential fill",
() => new GitProcess.Result("username=username\r\npassword=password" + Guid.NewGuid() + "\r\n", string.Empty, GitProcess.Result.SuccessCode));

// Try and reject it. We should get a new token, but without forwarding the rejection to the
// underlying credential store
dut.RejectCredentials(tracer, authString);
dut.TryGetCredentials(tracer, out var newAuthString, out _).ShouldBeTrue();
newAuthString.ShouldNotEqual(authString);
gitProcess.CredentialRejections.ShouldBeEmpty();
}

private MockGitProcess GetGitProcess()
{
MockGitProcess gitProcess = new MockGitProcess();
Expand Down
5 changes: 3 additions & 2 deletions GVFS/GVFS.UnitTests/Mock/Git/MockGitProcess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace GVFS.UnitTests.Mock.Git
Expand Down Expand Up @@ -91,7 +92,7 @@ protected override Result InvokeGitImpl(
return new Result(string.Empty, string.Empty, Result.GenericFailureCode);
}

Predicate<CommandInfo> commandMatchFunction =
Func<CommandInfo, bool> commandMatchFunction =
(CommandInfo commandInfo) =>
{
if (commandInfo.MatchPrefix)
Expand All @@ -104,7 +105,7 @@ protected override Result InvokeGitImpl(
}
};

CommandInfo matchedCommand = this.expectedCommandInfos.Find(commandMatchFunction);
CommandInfo matchedCommand = this.expectedCommandInfos.Last(commandMatchFunction);
matchedCommand.ShouldNotBeNull("Unexpected command: " + command);

return matchedCommand.Result();
Expand Down
Loading