Skip to content

Commit

Permalink
(chocolatey#3565) Match full source URL for credential lookup
Browse files Browse the repository at this point in the history
Previously we looked up any available sources in the config by the
hostname, before falling back to trying an exact match if we had
collisions.

This still allowed credentials to be reused in situations where we don't
actually know if they're applicable; many repository servers will
support different credentials for individual repositories, so we cannot
and should not assume that credentials for one repository will actually
match another repository, nor that users want the credentials to be
shared for both.

It also led to the possibility of users storing one repository first,
and then later specifying a different repository on the same server, and
choco would try to use the stored credentials for the first repository
for the explicitly-entered URL which is nowhere in config.

Instead, we should only match the whole URL (which can be done with
Uri.Equals to ensure that we match hostnames case-insensitively, but
routes case-sensitively), and expect users to provide credentials if
they provide a URL that is not explicitly in the sources.
  • Loading branch information
vexx32 committed Nov 15, 2024
1 parent 5b5b54a commit 09b3f6d
Showing 1 changed file with 13 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -86,22 +86,22 @@ public Task<CredentialResponse> GetAsync(Uri uri, IWebProxy proxy, CredentialReq
}

// credentials were not explicit
// discover based on closest match in sources
// find matching source(s) in sources list
var candidateSources = _config.MachineSources.Where(
s =>
{
var sourceUrl = s.Key.TrimEnd('/');

try
{
var sourceUri = new Uri(sourceUrl);
return sourceUri.Host.IsEqualTo(uri.Host)
// Uri.Equals() compares hostnames case-insensitively and the remainder of the url case-sensitively
// while ignoring #fragments on the URLs, but does care about trailing slashes, which we do not here.
var sourceUri = new Uri(s.Key.TrimEnd('/'));
return sourceUri.Equals(new Uri(uri.AbsoluteUri.TrimEnd('/')))
&& !string.IsNullOrWhiteSpace(s.Username)
&& !string.IsNullOrWhiteSpace(s.EncryptedPassword);
}
catch (Exception)
{
this.Log().Error("Source '{0}' is not a valid Uri".FormatWith(sourceUrl));
this.Log().Error("Source '{0}' is not a valid Uri".FormatWith(s.Key));
}

return false;
Expand All @@ -113,29 +113,15 @@ public Task<CredentialResponse> GetAsync(Uri uri, IWebProxy proxy, CredentialReq
if (candidateSources.Count == 1)
{
// only one match, use it
source = candidateSources.FirstOrDefault();
source = candidateSources.First();
}
else if (candidateSources.Count > 1)
else if (candidateSources.Count > 1 && !isRetry)
{
// find the source that is the closest match
foreach (var candidateSource in candidateSources.OrEmpty())
{
var candidateRegEx = new Regex(Regex.Escape(candidateSource.Key.TrimEnd('/')), RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
if (candidateRegEx.IsMatch(uri.OriginalString.TrimEnd('/')))
{
this.Log().Debug("Source selected will be '{0}'".FormatWith(candidateSource.Key.TrimEnd('/')));
source = candidateSource;
break;
}
}

if (source == null && !isRetry)
{
// use the first source. If it fails, fall back to grabbing credentials from the user
var candidateSource = candidateSources.First();
this.Log().Debug("Evaluated {0} candidate sources but was unable to find a match, using {1}".FormatWith(candidateSources.Count, candidateSource.Key.TrimEnd('/')));
source = candidateSource;
}
// Use the credentials from the first found source, unless it's a retry (creds already tried and failed)
// use the first source. If it fails, fall back to grabbing credentials from the user.
var candidateSource = candidateSources.First();
this.Log().Debug("Evaluated {0} candidate sources but was unable to find a match, using {1}".FormatWith(candidateSources.Count, candidateSource.Key.TrimEnd('/')));
source = candidateSource;
}

if (source == null)
Expand All @@ -146,7 +132,6 @@ public Task<CredentialResponse> GetAsync(Uri uri, IWebProxy proxy, CredentialReq
{
this.Log().Debug("This is a retry attempt. Asking user for credentials for '{0}'".FormatWith(uri.OriginalString));
credential = GetUserCredentials(uri, proxy, credentialType);

}

return Task.FromResult(new CredentialResponse(credential));
Expand Down

0 comments on commit 09b3f6d

Please sign in to comment.