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

fixes NuGet/Home #11210 CredentialService incorrectly sets the value of isRetry #4636

Closed
wants to merge 2 commits into from

Conversation

vlada-shubina
Copy link

@vlada-shubina vlada-shubina commented May 19, 2022

Bug

fixes NuGet/Home#11210
Related to: dotnet/templating#4278

Description

Fixed setting of isRetry value in CredentialService, isRetry should be set only when previous request fails.

This affects the UX of .NET SDK as users need to auth multiple times when doing operations with private NuGet feed.
isRetry should not be set on subsequent call unless the credentials are invalid.

PR Checklist

@vlada-shubina vlada-shubina requested a review from a team as a code owner May 19, 2022 11:00
@ghost ghost added the Community PRs created by someone not in the NuGet team label May 19, 2022
@@ -90,7 +90,7 @@ public CredentialService(AsyncLazy<IEnumerable<ICredentialProvider>> providers,
cancellationToken.ThrowIfCancellationRequested();

var retryKey = RetryCacheKey(uri, type, provider);
var isRetry = _retryCache.ContainsKey(retryKey);
var isRetry = (type == CredentialRequestType.Forbidden) && _retryCache.ContainsKey(retryKey);
Copy link
Member

Choose a reason for hiding this comment

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

Could you help me understand why Forbidden and not Unauthorized?

Forbidden suggests the username&password or PAT is valid, so no amounts of retries are going to succeed until permissions are fixed server side. The edge case is when the customer has two or more accounts, and they entered the wrong one.

Unauthorized suggests the username& password or PAT is invalid, so valid credentials need to be supplied in the retry.

Honestly, my intuition is that both attempts are a kind of retry, but only unauthorized responses make sense to retry. Why shouldn't incorrect credentials being supplied be treated as a retry?

My understanding of the problem, from reading the link issues, is that NuGet doesn't attempt to pass the credentials on the first HTTP request (same as how web browsers (usually?) don't, but NuGet incorrectly detects the first attempt at sending credentials as a retry. My gut feeling is that we need a more sophisticated fix, rather than treating all unauthorized responses as not-retries.

Copy link
Member

Choose a reason for hiding this comment

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

My understanding of the problem, from reading the link issues, is that NuGet doesn't attempt to pass the credentials on the first HTTP request (same as how web browsers (usually?) don't, but NuGet incorrectly detects the first attempt at sending credentials as a retry.

The credential service should only come in play with a challenge, so I don't imagine we have an unneeded isRetry.

Copy link
Member

@nkolev92 nkolev92 left a comment

Choose a reason for hiding this comment

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

I'm having a tough time figuring out the effects of this change here.

In particular I was reading:

dotnet/templating#4278 (comment)

Which suggests NuGet tries to talk to the provider after it's obtained successful credentials, which seems to contradict what

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
does.

If NuGet retries for successful credentials then this wouldn't fix it correct?

Can you maybe explain step by step what happens here currently and what should be happen with a focus on what the customer sees.

}

[Fact]
public async Task GetCredentialsAsync_SecondCallHasRetryTrue_WhenTypeIsForbidden()
Copy link
Member

Choose a reason for hiding this comment

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

@@ -90,7 +90,7 @@ public CredentialService(AsyncLazy<IEnumerable<ICredentialProvider>> providers,
cancellationToken.ThrowIfCancellationRequested();

var retryKey = RetryCacheKey(uri, type, provider);
var isRetry = _retryCache.ContainsKey(retryKey);
var isRetry = (type == CredentialRequestType.Forbidden) && _retryCache.ContainsKey(retryKey);
Copy link
Member

Choose a reason for hiding this comment

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

My understanding of the problem, from reading the link issues, is that NuGet doesn't attempt to pass the credentials on the first HTTP request (same as how web browsers (usually?) don't, but NuGet incorrectly detects the first attempt at sending credentials as a retry.

The credential service should only come in play with a challenge, so I don't imagine we have an unneeded isRetry.

@vlada-shubina
Copy link
Author

I'm having a tough time figuring out the effects of this change here.

In particular I was reading:

dotnet/templating#4278 (comment)

Which suggests NuGet tries to talk to the provider after it's obtained successful credentials, which seems to contradict what

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)

does.
If NuGet retries for successful credentials then this wouldn't fix it correct?

Can you maybe explain step by step what happens here currently and what should be happen with a focus on what the customer sees.

I attempted to do fix explained in NuGet/Home#11210
I'm still trying to debug this change in dotnet new, will provide more results tomorrow. Logs in #4278 is for different case.

My understanding of what is happening:

  • dotnet new using multiple resources from NuGet.Protocol at same uri
  • at accessing the first resource, on SendAsync default credentials is used, it fails for private feed; isRetry is not set` due to no cache, the user completes authentication and it gets cached; consequent call succeeds
  • at accessing the second resource, on SendAsync default credentials is used, it fails; since cache is already available, isRetry is set`, cache is removed, and the user need to complete authentication again.

As the result, the user need to authenticate twice on installing / updating template packages with dotnet new. This is very unfortunate behavior.
The fix may work if first call to the second resource completes with code other than Unauthorized, but I doubt so at this point.

Proper fix might be:

  • in HttpSourceAuthenticationHandler and possible other Handlers implement the following logic
    • 1st try - with default credentials (as now)
    • 2nd try - with cached credentials from CredentialService (if any)
    • 3rd try - with cleaning the cache and re-authentication (basically current isRetry behavior)

This will avoid the need of re-authentication. But this will need changes in API, I cannot see any way how 2nd try is possible with existing definition of GetCredentialsAsync.

Another way is to share Credentials between resource, but again not sure if it's possible and reasonable.

@zivkan
Copy link
Member

zivkan commented May 19, 2022

A problem that the dotnet workload team experienced is that they were creating multiple instances of NuGet.Protocol.SourceRepository, one for each file they were downloading. NuGet caches the credential (for all resources) within that instance, but by creating a different instance per file, it caused the credential to be re-obtained each time (although in that scenario, isRetry should be false on the first message to the credential provider).

Is it possible that dotnet new is creating multiple instances of SourceRepository (probably though Repository.Factory.CreateCoreV3(url)) for the same URL, rather than re-using/sharing a single instance?

@vlada-shubina
Copy link
Author

A problem that the dotnet workload team experienced is that they were creating multiple instances of NuGet.Protocol.SourceRepository, one for each file they were downloading. NuGet caches the credential (for all resources) within that instance, but by creating a different instance per file, it caused the credential to be re-obtained each time (in which case the change in this PR wouldn't help).

Is it possible that dotnet new is creating multiple instances of SourceRepository (probably though Repository.Factory.CreateCoreV3(url)) for the same URL, rather than re-using/sharing a single instance?

Yes, it appears it does, thank you for pointing it out - dotnet new creates it twice - once for downloading available package metadata, and second time for actual download of requested package. I will change it tomorrow to see if it helps to avoid authenticating twice, it should not be difficult to cache created source repositories. If so, this issue will no longer be that important for dotnet new.

@nkolev92
Copy link
Member

We recently fixed a similar issue with list package: NuGet/Home#11169.

@vlada-shubina
Copy link
Author

A problem that the dotnet workload team experienced is that they were creating multiple instances of NuGet.Protocol.SourceRepository, one for each file they were downloading. NuGet caches the credential (for all resources) within that instance, but by creating a different instance per file, it caused the credential to be re-obtained each time (although in that scenario, isRetry should be false on the first message to the credential provider).

Is it possible that dotnet new is creating multiple instances of SourceRepository (probably though Repository.Factory.CreateCoreV3(url)) for the same URL, rather than re-using/sharing a single instance?

So this worked perfectly, so dotnet/templating issue will be fixed in dotnet/templating#4764.
I'm closing this PR.

I don't have enough context to see if NuGet/Home#11210 should be closed too. It's clear that fix suggested in that issue won't work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Community PRs created by someone not in the NuGet team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Bug]: VS Credential Provider Incorrectly Setting Value of isRetry
3 participants