-
Notifications
You must be signed in to change notification settings - Fork 903
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
(#3565 #2421) Avoid credential bleed from saved sources with the same hostname #3568
base: hotfix/2.4.1
Are you sure you want to change the base?
Conversation
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. Additionally, we try to ensure that if a user has named a specific source, rather than themselves providing a URL at the command line, we prioritise finding that in the already-configured sources and use that source if the URL matches the current URL that NuGet requires a credential for.
These tests ensure that the use cases we expect to handle in the credential provider are appropriately handled according to our expectations, based on the user-provided input and the transformed input that is left in configuration.Sources once the credential provider typically gets queried.
Converting this PR to draft while I add and validate Pester tests. |
I've updated the Pester tests once I got them sorted in Test Kitchen. This is once again ready for review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general, this looks good. But there are a few things to fix up, or questions needed to be answered.
[OneTimeSetUp] | ||
public async Task OneTimeSetup() | ||
{ | ||
await With(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be a Because clause, not a separate OneTimeSetup
.
There are no guarantee in what order a OneTimeSetup
will be ran when multiple ones are specified (both Context and Because is in a single OneTimeSetup
).
[OneTimeSetUp] | |
public async Task OneTimeSetup() | |
{ | |
await With(); | |
} | |
public override void Because() | |
{ | |
With().Wait(); | |
} | |
At some point, if we want to test async code more often, then we should think about introducing an async TinySpec instead of doing this (You can look at the AyncTinySpec
class I created for Agent).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has been updated.
public override void Because() | ||
{ | ||
Configuration.Sources = Configuration.ExplicitSources = TargetSourceUrl; | ||
Configuration.SourceCommand.Username = "user"; | ||
Configuration.SourceCommand.Password = "totally_secure_password!!!"; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is part of the setup, not the actual run of a code. As such this should be in a Context
method.
public override void Because() | |
{ | |
Configuration.Sources = Configuration.ExplicitSources = TargetSourceUrl; | |
Configuration.SourceCommand.Username = "user"; | |
Configuration.SourceCommand.Password = "totally_secure_password!!!"; | |
} | |
public override void Context() | |
{ | |
base.Context(); | |
Configuration.Sources = Configuration.ExplicitSources = TargetSourceUrl; | |
Configuration.SourceCommand.Username = "user"; | |
Configuration.SourceCommand.Password = "totally_secure_password!!!"; | |
} | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has been updated.
[Fact] | ||
public void Creates_a_valid_credential() | ||
{ | ||
Result.Should().NotBeNull(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This does not test if the credentials are valid, just that they were created.
We also commonly have prefixed assertions with Should_
(this part is also true for all other methods created in this file, I am just not commenting on it right now). Additionally, each word should have the first letter in uppercase (it wasn't done when converting to FluentAssertion due to the amount of time it would take).
[Fact] | |
public void Creates_a_valid_credential() | |
{ | |
Result.Should().NotBeNull(); | |
} | |
[Fact] | |
public void Should_Create_Credentials() | |
{ | |
Result.Should().NotBeNull(); | |
} | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has been updated.
A note on the naming and casing: should it not be PascalCase as well?
public override void Because() | ||
{ | ||
Configuration.Sources = TargetSourceUrl; | ||
Configuration.ExplicitSources = TargetSourceName; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be part of the context (ie the setup), not the because method.
public override void Because() | |
{ | |
Configuration.Sources = TargetSourceUrl; | |
Configuration.ExplicitSources = TargetSourceName; | |
} | |
public override void Context() | |
{ | |
base.Context(); | |
Configuration.Sources = TargetSourceUrl; | |
Configuration.ExplicitSources = TargetSourceName; | |
} | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has been updated.
public override void Because() | ||
{ | ||
Configuration.Sources = Configuration.ExplicitSources = TargetSourceUrl; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, should be Context.
public override void Because() | |
{ | |
Configuration.Sources = Configuration.ExplicitSources = TargetSourceUrl; | |
} | |
public override void Context() | |
{ | |
base.Context(); | |
Configuration.Sources = Configuration.ExplicitSources = TargetSourceUrl; | |
} | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has been updated.
configuration.Sources = configuration.ExplicitSources = option.UnquoteSafe(); | ||
configuration.ListCommand.ExplicitSource = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we storing a Source
and an ExplicitSource
, at the same time that we are setting a boolean property called ExplicitSource
?
Also, what happens if the source that is passed is the named source of a configuration? Or one of the alternative sources?
Should that also be considered an explicit source?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the credential provider, the first thing it does it try to look up any explicitly set named sources, and then the remainder as URIs. I if it's an alternative source it'll be handled before getting to the credential provider and so will not matter if it's set as an explicit source.
.ToList(); | ||
if (namedExplicitSources?.Count > 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be a newline here.
.ToList(); | |
if (namedExplicitSources?.Count > 0) | |
.ToList(); | |
if (namedExplicitSources?.Count > 0) | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has been updated.
source = _config.MachineSources | ||
.Where(s => namedExplicitSources.Contains(s.Name) && new Uri(s.Key.TrimEnd('/')) == trimmedTargetUri) | ||
.FirstOrDefault(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we doing case sensitive checking here?
The Contains
method is case sensitive, and as the comment says it does a case sensitive match on the url (just not the hostname).
That do not make sense, and I would think it is a case insensitive match we would want.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason for doing a case sensitive match on everything but the hostname is that the hostname is handled by DNS and is case insensitive, but the rest of the URL is up to the responding server which may or may not be case insensitive. For example: https://docs.chocolatey.org/en-us/central-management/
is valid while https://docs.chocolatey.org/en-us/central-managemenT/
is not. But if you were to case-insensitive compare them they are identical.
// Note: This behaviour remains as removing it would be a breaking change, but we may want | ||
// to remove this in a future version, as specifying an explicit URL should potentially |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there has been a decision to remove this, or potentially removing it, we would need an issue to be created to keep track of this.
.Where(s => !string.IsNullOrWhiteSpace(s.Username) | ||
&& !string.IsNullOrWhiteSpace(s.EncryptedPassword) | ||
&& Uri.TryCreate(s.Key.TrimEnd('/'), UriKind.Absolute, out var trimmedSourceUri) | ||
&& trimmedSourceUri == trimmedTargetUri) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Coming back to the comment further up, if this is doing a case-sensitive match on the URL (except the hostname), then this type of comparison is probably not what we would want.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add Pester tests to ensure we don't inadvertently bleed configured credentials into scenarios where they should not be used.
The query string used by some commands seems to be ever so slightly different depending on where they are run. This removes the query string from the tests as these tests are not concerned with the entire message, just the initial part of the message.
Description Of Changes
--source
on the command line are, and start making use of it here to aid with looking up source credentials.Motivation and Context
See #3565 - the credentials are being reused in places that they shouldn't.
Testing
Unit tests! :3 Including a regression test specifically for #3565
Open VS and run the tests in the
chocolatey.tests
project and make sure they pass.Cory added: Ran
CredentialProvider
tag in Test Kitchen.Operating Systems Testing
Win10
Change Types Made
Change Checklist
Related Issue
Fixes #3565
Fixes #2421 (different symptom, same issue, likely somewhat duplicate issue)