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

New release #530

Merged
merged 38 commits into from
Nov 15, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
40f5934
Run release-winget on Windows Server
ldennington Oct 6, 2021
624251e
Update inno installer metadata
ldennington Oct 8, 2021
53ea286
Merge pull request #483 from ldennington/update-metadata
ldennington Oct 12, 2021
77a5748
docs: update README to list bad Git versions
mjcheetham Oct 12, 2021
6c0fb9e
docs: add missing DPAPI option to summary
mjcheetham Oct 14, 2021
8f63406
Merge pull request #488 from mjcheetham/badgitver-docs
mjcheetham Oct 15, 2021
4472ea0
registry: remove autodetection progress/info message
mjcheetham Oct 15, 2021
d872294
Merge pull request #494 from mjcheetham/no-auto-noise
mjcheetham Oct 20, 2021
3183fdb
Add PAT as possible option to credential.gitHubAuthModes (#496)
Shegox Oct 20, 2021
1b0ad58
Fix CONTRIBUTING.md link to LICENSE
julescubtree Oct 28, 2021
8b96141
Remove Microsoft from assembly names
ldennington Oct 20, 2021
e7a8807
Remove Microsoft from/generally update namespaces
ldennington Oct 21, 2021
c912943
Rename projects and directories
ldennington Oct 22, 2021
c35166d
fix the signing! woop yolo
ldennington Nov 1, 2021
5aa3590
Merge pull request #500 from ldennington/remove-microsoft-from-namespace
ldennington Nov 1, 2021
dd5b999
Add UI testing instructions to CONTRIBUTING
ldennington Nov 1, 2021
7609cec
Clean up README
ldennington Nov 1, 2021
73cb423
msauth: update to MSAL.NET 4.37
mjcheetham Nov 1, 2021
8cd79a3
proxy: match NO_PROXY formats to libcurl behaviour
mjcheetham Oct 20, 2021
f22fd0b
Merge pull request #510 from ldennington/add-ui-testing-instructions
mjcheetham Nov 2, 2021
a391785
Merge pull request #501 from mjcheetham/noproxy-globs
mjcheetham Nov 2, 2021
81cdb21
settings: match curl lowercase proxy envar behaviour
mjcheetham Oct 22, 2021
3bfcf41
Merge pull request #508 from julescubtree/update-contributing-guide
mjcheetham Nov 2, 2021
90a8d6d
Merge pull request #503 from mjcheetham/curl-envars
mjcheetham Nov 2, 2021
5e01de1
docs: update readme to call out G4W bundling
mjcheetham Nov 2, 2021
eac38a4
Merge pull request #512 from mjcheetham/msal-4.37
mjcheetham Nov 2, 2021
901f9ea
Merge pull request #513 from microsoft/docs-g4w
mjcheetham Nov 2, 2021
8d2d9ed
deb: include all shared libraries in Debian package
mjcheetham Nov 2, 2021
a193f94
Remove release-apt-get workflow
ldennington Nov 2, 2021
fcb3e0a
Remove apt-get install instructions from README.md
ldennington Nov 2, 2021
7edf9cd
Merge pull request #516 from ldennington/remove-release-apt-get
ldennington Nov 2, 2021
ac34798
Merge pull request #514 from mjcheetham/fix-deb
mjcheetham Nov 3, 2021
564045e
autodetect: set credential.provider after autodetect
mjcheetham Nov 3, 2021
45133e8
docs: use new Git for Windows installer screenshot
mjcheetham Nov 3, 2021
75d008d
Merge pull request #517 from mjcheetham/discovery-setconfig
mjcheetham Nov 3, 2021
f559bdc
Remove winget workflow
ldennington Nov 8, 2021
1d668b1
Merge pull request #520 from ldennington/remove-winget
ldennington Nov 8, 2021
0b2cdef
docs: update version cmd in issue template
mjcheetham Nov 12, 2021
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
Prev Previous commit
Next Next commit
proxy: match NO_PROXY formats to libcurl behaviour
We aim to be compatible with the behaviour of Git as much as possible
when it comes to network settings. This enables users to setup Git proxy
settings and get the same setup "for free" with GCM.

Git uses libcurl to provide it's HTTP interactions. The NO_PROXY setting
is used by libcurl to disable proxy settings for specific hosts.

We previously attempted to plumb the value of NO_PROXY through to the
.NET WebProxy class' list of "bypassed addresses" (the set of hosts that
should not be proxied). However, the .NET class expects a set of
_regular expressions_ which is unlike libcurl!

As a result, libcurl permitted values for NO_PROXY were throwing errors
inside of GCM since they are not valid regexs.

In this commit we perform a transformation of the NO_PROXY list and
construct a set of regular expressions that match addresses in the same
way as libcurl does.

The transformation is as follows:

1. strip any leading periods '.' or wildcards '*.'
2. escape the remaining input to match literally (e.g.: '.' becomes '\.')
3. prepend a group that matches either a period '.' or a URI scheme
   delimiter '://' - this prevents partial domain matching
4. append a end-of-string symbol '$' to ensure we only match to the
   specified TLD and port

See the libcurl documentation on NO_PROXY behaviour:
https://curl.se/libcurl/c/CURLOPT_NOPROXY.html
  • Loading branch information
mjcheetham committed Nov 2, 2021
commit 8cd79a3a702be0a2441878818f094d3554c80aca
24 changes: 22 additions & 2 deletions docs/netconfig.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,28 @@ addresses. GCM Core supports the cURL environment variable `NO_PROXY` for this
scenariom, as does Git itself.

The `NO_PROXY` environment variable should contain a comma (`,`) or space (` `)
separated list of regular expressions to match hosts that should not be proxied
(should connect directly).
separated list of host names that should not be proxied (should connect
directly).

GCM Core attempts to match [libcurl's behaviour](https://curl.se/libcurl/c/CURLOPT_NOPROXY.html),
which is briefly summarized here:

- a value of `*` disables proxying for all hosts;
- other wildcard use is **not** supported;
- each name in the list is matched as a domain which contains the hostname,
or the hostname itself
- a leading period/dot `.` matches against the provided hostname

For example, setting `NO_PROXY` to `example.com` results in the following:

Hostname|Matches?
-|-
`example.com`|:white_check_mark:
`example.com:80`|:white_check_mark:
`www.example.com`|:white_check_mark:
`notanexample.com`|:x:
`www.notanexample.com`|:x:
`example.com.othertld`|:x:

**Example:**

Expand Down
33 changes: 31 additions & 2 deletions src/shared/Core.Tests/HttpClientFactoryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,13 @@ public void HttpClientFactory_TryCreateProxy_ProxyWithBypass_ReturnsTrueOutProxy
const string repoPath = "/tmp/repos/foo";
const string repoRemote = "https://remote.example.com/foo.git";

var bypassList = new List<string> {"https://contoso.com", ".*fabrikam\\.com"};
var noProxyRaw = "contoso.com,fabrikam.com";
var repoRemoteUri = new Uri(repoRemote);
var proxyConfig = new ProxyConfiguration(
new Uri(proxyUrl),
userName: null,
password: null,
bypassHosts: bypassList);
noProxyRaw: noProxyRaw);

var settings = new TestSettings
{
Expand All @@ -117,6 +117,35 @@ public void HttpClientFactory_TryCreateProxy_ProxyWithBypass_ReturnsTrueOutProxy
Assert.False(proxy.IsBypassed(repoRemoteUri));
}

[Fact]
public void HttpClientFactory_TryCreateProxy_ProxyWithWildcardBypass_ReturnsFalse()
{
const string proxyUrl = "https://proxy.example.com/git";
const string repoPath = "/tmp/repos/foo";
const string repoRemote = "https://remote.example.com/foo.git";

var noProxyRaw = "*";
var repoRemoteUri = new Uri(repoRemote);
var proxyConfig = new ProxyConfiguration(
new Uri(proxyUrl),
userName: null,
password: null,
noProxyRaw: noProxyRaw);

var settings = new TestSettings
{
RemoteUri = repoRemoteUri,
RepositoryPath = repoPath,
ProxyConfiguration = proxyConfig
};
var httpFactory = new HttpClientFactory(Mock.Of<IFileSystem>(), Mock.Of<ITrace>(), settings, Mock.Of<IStandardStreams>());

bool result = httpFactory.TryCreateProxy(out IWebProxy proxy);

Assert.False(result);
Assert.Null(proxy);
}

[Fact]
public void HttpClientFactory_TryCreateProxy_ProxyWithCredentials_ReturnsTrueOutProxyWithUrlConfiguredCredentials()
{
Expand Down
130 changes: 73 additions & 57 deletions src/shared/Core.Tests/SettingsTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using GitCredentialManager.Tests.Objects;
using Xunit;

Expand Down Expand Up @@ -427,6 +428,57 @@ public void Settings_IsWindowsIntegratedAuthenticationEnabled_ConfigNonBooleanyV
Assert.True(settings.IsWindowsIntegratedAuthenticationEnabled);
}

[Theory]
[InlineData("", new string[0])]
[InlineData(" ", new string[0])]
[InlineData(",", new string[0])]
[InlineData("example.com", new[] { @"(\.|\:\/\/)example\.com$" })]
[InlineData("example.com:8080", new[] { @"(\.|\:\/\/)example\.com:8080$" })]
[InlineData("example.com,", new[] { @"(\.|\:\/\/)example\.com$" })]
[InlineData(",example.com", new[] { @"(\.|\:\/\/)example\.com$" })]
[InlineData(",example.com,", new[] { @"(\.|\:\/\/)example\.com$" })]
[InlineData(".example.com", new[] { @"(\.|\:\/\/)example\.com$" })]
[InlineData("..example.com", new[] { @"(\.|\:\/\/)example\.com$" })]
[InlineData("*.example.com", new[] { @"(\.|\:\/\/)example\.com$" })]
[InlineData("my.example.com", new[] { @"(\.|\:\/\/)my\.example\.com$" })]
[InlineData("example.com,contoso.com,fabrikam.com", new[]
{
@"(\.|\:\/\/)example\.com$",
@"(\.|\:\/\/)contoso\.com$",
@"(\.|\:\/\/)fabrikam\.com$"
})]
public void Settings_ProxyConfiguration_ConvertToBypassRegexArray(string input, string[] expected)
{
string[] actual = ProxyConfiguration.ConvertToBypassRegexArray(input).ToArray();
Assert.Equal(expected, actual);
}

[Theory]
[InlineData("example.com", "http://example.com", true)]
[InlineData("example.com", "https://example.com", true)]
[InlineData("example.com", "https://www.example.com", true)]
[InlineData("example.com", "http://www.example.com:80", true)]
[InlineData("example.com", "https://www.example.com:443", true)]
[InlineData("example.com", "https://www.example.com:8080", false)]
[InlineData("example.com", "http://notanexample.com", false)]
[InlineData("example.com", "https://notanexample.com", false)]
[InlineData("example.com", "https://www.notanexample.com", false)]
[InlineData("example.com", "https://example.com.otherltd", false)]
[InlineData("example.com:8080", "http://example.com", false)]
[InlineData("my.example.com", "http://example.com", false)]
public void Settings_ProxyConfiguration_ConvertToBypassRegexArray_WebProxyBypass(string noProxy, string address, bool expected)
{
var bypassList = ProxyConfiguration.ConvertToBypassRegexArray(noProxy).ToArray();
var webProxy = new WebProxy("https://localhost:8080/proxy")
{
BypassList = bypassList
};

bool actual = webProxy.IsBypassed(new Uri(address));

Assert.Equal(expected, actual);
}

[Fact]
public void Settings_ProxyConfiguration_Unset_ReturnsNull()
{
Expand Down Expand Up @@ -458,11 +510,11 @@ public void Settings_ProxyConfiguration_GcmHttpConfig_ReturnsValue()
const string expectedPassword = "letmein123";
var expectedAddress = new Uri("http://proxy.example.com");
var settingValue = new Uri("http://john.doe:letmein123@proxy.example.com");
var bypassList = new List<string> {"contoso.com", "fabrikam.com"};
var expectedNoProxy = "contoso.com,fabrikam.com";

var envars = new TestEnvironment
{
Variables = {[Constants.EnvironmentVariables.CurlNoProxy] = string.Join(',', bypassList)}
Variables = {[Constants.EnvironmentVariables.CurlNoProxy] = expectedNoProxy}
};
var git = new TestGit();
git.Configuration.Global[$"{section}.{property}"] = new[] {settingValue.ToString()};
Expand All @@ -478,7 +530,7 @@ public void Settings_ProxyConfiguration_GcmHttpConfig_ReturnsValue()
Assert.Equal(expectedAddress, actualConfig.Address);
Assert.Equal(expectedUserName, actualConfig.UserName);
Assert.Equal(expectedPassword, actualConfig.Password);
Assert.Equal(bypassList, actualConfig.BypassHosts);
Assert.Equal(expectedNoProxy, actualConfig.NoProxyRaw);
Assert.True(actualConfig.IsDeprecatedSource);
}

Expand All @@ -494,11 +546,11 @@ public void Settings_ProxyConfiguration_GcmHttpsConfig_ReturnsValue()
const string expectedPassword = "letmein123";
var expectedAddress = new Uri("http://proxy.example.com");
var settingValue = new Uri("http://john.doe:letmein123@proxy.example.com");
var bypassList = new List<string> {"contoso.com", "fabrikam.com"};
var expectedNoProxy = "contoso.com,fabrikam.com";

var envars = new TestEnvironment
{
Variables = {[Constants.EnvironmentVariables.CurlNoProxy] = string.Join(',', bypassList)}
Variables = {[Constants.EnvironmentVariables.CurlNoProxy] = expectedNoProxy}
};
var git = new TestGit();
git.Configuration.Global[$"{section}.{property}"] = new[] {settingValue.ToString()};
Expand All @@ -514,7 +566,7 @@ public void Settings_ProxyConfiguration_GcmHttpsConfig_ReturnsValue()
Assert.Equal(expectedAddress, actualConfig.Address);
Assert.Equal(expectedUserName, actualConfig.UserName);
Assert.Equal(expectedPassword, actualConfig.Password);
Assert.Equal(bypassList, actualConfig.BypassHosts);
Assert.Equal(expectedNoProxy, actualConfig.NoProxyRaw);
Assert.True(actualConfig.IsDeprecatedSource);
}

Expand All @@ -530,11 +582,11 @@ public void Settings_ProxyConfiguration_GitHttpConfig_ReturnsValue()
const string expectedPassword = "letmein123";
var expectedAddress = new Uri("http://proxy.example.com");
var settingValue = new Uri("http://john.doe:letmein123@proxy.example.com");
var bypassList = new List<string> {"contoso.com", "fabrikam.com"};
var expectedNoProxy = "contoso.com,fabrikam.com";

var envars = new TestEnvironment
{
Variables = {[Constants.EnvironmentVariables.CurlNoProxy] = string.Join(',', bypassList)}
Variables = {[Constants.EnvironmentVariables.CurlNoProxy] = expectedNoProxy}
};
var git = new TestGit();
git.Configuration.Global[$"{section}.{property}"] = new[] {settingValue.ToString()};
Expand All @@ -550,7 +602,7 @@ public void Settings_ProxyConfiguration_GitHttpConfig_ReturnsValue()
Assert.Equal(expectedAddress, actualConfig.Address);
Assert.Equal(expectedUserName, actualConfig.UserName);
Assert.Equal(expectedPassword, actualConfig.Password);
Assert.Equal(bypassList, actualConfig.BypassHosts);
Assert.Equal(expectedNoProxy, actualConfig.NoProxyRaw);
Assert.False(actualConfig.IsDeprecatedSource);
}

Expand Down Expand Up @@ -579,42 +631,6 @@ public void Settings_ProxyConfiguration_GitHttpConfig_EmptyScopedUriUnscoped_Ret
Assert.Null(actualConfig);
}

[Fact]
public void Settings_ProxyConfiguration_NoProxyMixedSplitChar_ReturnsValue()
{
const string remoteUrl = "http://example.com/foo.git";
const string section = Constants.GitConfiguration.Http.SectionName;
const string property = Constants.GitConfiguration.Http.Proxy;
var remoteUri = new Uri(remoteUrl);

const string expectedUserName = "john.doe";
const string expectedPassword = "letmein123";
var expectedAddress = new Uri("http://proxy.example.com");
var settingValue = new Uri("http://john.doe:letmein123@proxy.example.com");
var bypassList = new List<string> {"contoso.com", "fabrikam.com", "example.com"};

var envars = new TestEnvironment
{
Variables = {[Constants.EnvironmentVariables.CurlNoProxy] = "contoso.com, fabrikam.com example.com,"}
};
var git = new TestGit();
git.Configuration.Global[$"{section}.{property}"] = new[] {settingValue.ToString()};

var settings = new Settings(envars, git)
{
RemoteUri = remoteUri
};

ProxyConfiguration actualConfig = settings.GetProxyConfiguration();

Assert.NotNull(actualConfig);
Assert.Equal(expectedAddress, actualConfig.Address);
Assert.Equal(expectedUserName, actualConfig.UserName);
Assert.Equal(expectedPassword, actualConfig.Password);
Assert.Equal(bypassList, actualConfig.BypassHosts);
Assert.False(actualConfig.IsDeprecatedSource);
}

[Fact]
public void Settings_ProxyConfiguration_CurlHttpEnvar_ReturnsValue()
{
Expand All @@ -625,14 +641,14 @@ public void Settings_ProxyConfiguration_CurlHttpEnvar_ReturnsValue()
const string expectedPassword = "letmein123";
var expectedAddress = new Uri("http://proxy.example.com");
var settingValue = new Uri("http://john.doe:letmein123@proxy.example.com");
var bypassList = new List<string> {"contoso.com", "fabrikam.com"};
var expectedNoProxy = "contoso.com,fabrikam.com";

var envars = new TestEnvironment
{
Variables =
{
[Constants.EnvironmentVariables.CurlHttpProxy] = settingValue.ToString(),
[Constants.EnvironmentVariables.CurlNoProxy] = string.Join(',', bypassList)
[Constants.EnvironmentVariables.CurlNoProxy] = expectedNoProxy
}
};
var git = new TestGit();
Expand All @@ -648,7 +664,7 @@ public void Settings_ProxyConfiguration_CurlHttpEnvar_ReturnsValue()
Assert.Equal(expectedAddress, actualConfig.Address);
Assert.Equal(expectedUserName, actualConfig.UserName);
Assert.Equal(expectedPassword, actualConfig.Password);
Assert.Equal(bypassList, actualConfig.BypassHosts);
Assert.Equal(expectedNoProxy, actualConfig.NoProxyRaw);
Assert.False(actualConfig.IsDeprecatedSource);
}

Expand All @@ -662,14 +678,14 @@ public void Settings_ProxyConfiguration_CurlHttpsEnvar_ReturnsValue()
const string expectedPassword = "letmein123";
var expectedAddress = new Uri("http://proxy.example.com");
var settingValue = new Uri("http://john.doe:letmein123@proxy.example.com");
var bypassList = new List<string> {"contoso.com", "fabrikam.com"};
var expectedNoProxy = "contoso.com,fabrikam.com";

var envars = new TestEnvironment
{
Variables =
{
[Constants.EnvironmentVariables.CurlHttpsProxy] = settingValue.ToString(),
[Constants.EnvironmentVariables.CurlNoProxy] = string.Join(',', bypassList)
[Constants.EnvironmentVariables.CurlNoProxy] = expectedNoProxy
}
};
var git = new TestGit();
Expand All @@ -685,7 +701,7 @@ public void Settings_ProxyConfiguration_CurlHttpsEnvar_ReturnsValue()
Assert.Equal(expectedAddress, actualConfig.Address);
Assert.Equal(expectedUserName, actualConfig.UserName);
Assert.Equal(expectedPassword, actualConfig.Password);
Assert.Equal(bypassList, actualConfig.BypassHosts);
Assert.Equal(expectedNoProxy, actualConfig.NoProxyRaw);
Assert.False(actualConfig.IsDeprecatedSource);
}

Expand All @@ -699,14 +715,14 @@ public void Settings_TryGetProxy_CurlAllEnvar_ReturnsValue()
const string expectedPassword = "letmein123";
var expectedAddress = new Uri("http://proxy.example.com");
var settingValue = new Uri("http://john.doe:letmein123@proxy.example.com");
var bypassList = new List<string> {"contoso.com", "fabrikam.com"};
var expectedNoProxy = "contoso.com,fabrikam.com";

var envars = new TestEnvironment
{
Variables =
{
[Constants.EnvironmentVariables.CurlAllProxy] = settingValue.ToString(),
[Constants.EnvironmentVariables.CurlNoProxy] = string.Join(',', bypassList)
[Constants.EnvironmentVariables.CurlNoProxy] = expectedNoProxy
}
};
var git = new TestGit();
Expand All @@ -722,7 +738,7 @@ public void Settings_TryGetProxy_CurlAllEnvar_ReturnsValue()
Assert.Equal(expectedAddress, actualConfig.Address);
Assert.Equal(expectedUserName, actualConfig.UserName);
Assert.Equal(expectedPassword, actualConfig.Password);
Assert.Equal(bypassList, actualConfig.BypassHosts);
Assert.Equal(expectedNoProxy, actualConfig.NoProxyRaw);
Assert.False(actualConfig.IsDeprecatedSource);
}

Expand All @@ -736,14 +752,14 @@ public void Settings_ProxyConfiguration_LegacyGcmEnvar_ReturnsValue()
const string expectedPassword = "letmein123";
var expectedAddress = new Uri("http://proxy.example.com");
var settingValue = new Uri("http://john.doe:letmein123@proxy.example.com");
var bypassList = new List<string> {"https://contoso.com", ".*fabrikam\\.com"};
var expectedNoProxy = "contoso.com,fabrikam.com";

var envars = new TestEnvironment
{
Variables =
{
[Constants.EnvironmentVariables.GcmHttpProxy] = settingValue.ToString(),
[Constants.EnvironmentVariables.CurlNoProxy] = string.Join(',', bypassList)
[Constants.EnvironmentVariables.CurlNoProxy] = expectedNoProxy
}
};
var git = new TestGit();
Expand All @@ -759,7 +775,7 @@ public void Settings_ProxyConfiguration_LegacyGcmEnvar_ReturnsValue()
Assert.Equal(expectedAddress, actualConfig.Address);
Assert.Equal(expectedUserName, actualConfig.UserName);
Assert.Equal(expectedPassword, actualConfig.Password);
Assert.Equal(bypassList, actualConfig.BypassHosts);
Assert.Equal(expectedNoProxy, actualConfig.NoProxyRaw);
Assert.True(actualConfig.IsDeprecatedSource);
}

Expand Down
Loading