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

(#3565 #2421) Avoid credential bleed from saved sources with the same hostname #3568

Open
wants to merge 3 commits into
base: hotfix/2.4.1
Choose a base branch
from

Conversation

vexx32
Copy link
Member

@vexx32 vexx32 commented Nov 19, 2024

Description Of Changes

  • Rework handling in ChocolateyNugetCredentialProvider to ensure we only match credentials from the full URL
  • Add a config property so we can actually tell what the literal arguments passed to --source on the command line are, and start making use of it here to aid with looking up source credentials.
    • In discussions with Gary, we have agreed the behaviour of looking up a source based purely on the URL here is bad In General, because choco should probably just use the literal command line arguments and not assume you want it to look things up in the config when you did not ask, so this will later be expanded upon and some of the searching functionality here can be removed at a later date.
  • Add unit tests for this credential provider

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

  • Bug fix (non-breaking change).
  • Feature / Enhancement (non-breaking change).
  • Breaking change (fix or feature that could cause existing functionality to change).
  • Documentation changes.
  • PowerShell code changes.

Change Checklist

  • Requires a change to the documentation.
  • Documentation has been updated.
  • Tests to cover my changes, have been added.
  • All new and existing tests passed?
  • PowerShell code changes: PowerShell v3 compatibility checked?

Related Issue

Fixes #3565
Fixes #2421 (different symptom, same issue, likely somewhat duplicate issue)

@gep13 gep13 changed the title Avoid credential bleed from saved sources with the same hostname (#3565 #2421) Avoid credential bleed from saved sources with the same hostname Nov 20, 2024
@gep13 gep13 changed the base branch from develop to hotfix/2.4.1 November 20, 2024 09:01
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.
@corbob
Copy link
Member

corbob commented Nov 20, 2024

Converting this PR to draft while I add and validate Pester tests.

Add Pester tests to ensure we don't inadvertently bleed configured
credentials into scenarios where they should not be used.
@corbob corbob marked this pull request as ready for review November 21, 2024 01:19
@corbob
Copy link
Member

corbob commented Nov 21, 2024

I've updated the Pester tests once I got them sorted in Test Kitchen. This is once again ready for review.

Copy link
Member

@AdmiringWorm AdmiringWorm left a 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.

Comment on lines +73 to +77
[OneTimeSetUp]
public async Task OneTimeSetup()
{
await With();
}
Copy link
Member

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).

Suggested change
[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).

Comment on lines +88 to +93
public override void Because()
{
Configuration.Sources = Configuration.ExplicitSources = TargetSourceUrl;
Configuration.SourceCommand.Username = "user";
Configuration.SourceCommand.Password = "totally_secure_password!!!";
}
Copy link
Member

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.

Suggested change
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!!!";
}

Comment on lines +95 to +99
[Fact]
public void Creates_a_valid_credential()
{
Result.Should().NotBeNull();
}
Copy link
Member

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).

Suggested change
[Fact]
public void Creates_a_valid_credential()
{
Result.Should().NotBeNull();
}
[Fact]
public void Should_Create_Credentials()
{
Result.Should().NotBeNull();
}

Comment on lines +116 to +120
public override void Because()
{
Configuration.Sources = TargetSourceUrl;
Configuration.ExplicitSources = TargetSourceName;
}
Copy link
Member

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.

Suggested change
public override void Because()
{
Configuration.Sources = TargetSourceUrl;
Configuration.ExplicitSources = TargetSourceName;
}
public override void Context()
{
base.Context();
Configuration.Sources = TargetSourceUrl;
Configuration.ExplicitSources = TargetSourceName;
}

Comment on lines +143 to +146
public override void Because()
{
Configuration.Sources = Configuration.ExplicitSources = TargetSourceUrl;
}
Copy link
Member

Choose a reason for hiding this comment

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

Again, should be Context.

Suggested change
public override void Because()
{
Configuration.Sources = Configuration.ExplicitSources = TargetSourceUrl;
}
public override void Context()
{
base.Context();
Configuration.Sources = Configuration.ExplicitSources = TargetSourceUrl;
}

Comment on lines +42 to +43
configuration.Sources = configuration.ExplicitSources = option.UnquoteSafe();
configuration.ListCommand.ExplicitSource = true;
Copy link
Member

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?

Comment on lines +97 to +98
.ToList();
if (namedExplicitSources?.Count > 0)
Copy link
Member

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.

Suggested change
.ToList();
if (namedExplicitSources?.Count > 0)
.ToList();
if (namedExplicitSources?.Count > 0)

Comment on lines +102 to +104
source = _config.MachineSources
.Where(s => namedExplicitSources.Contains(s.Name) && new Uri(s.Key.TrimEnd('/')) == trimmedTargetUri)
.FirstOrDefault();
Copy link
Member

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.

Comment on lines +112 to +113
// 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
Copy link
Member

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)
Copy link
Member

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants