Skip to content

Commit 4f91faa

Browse files
committed
http: treat empty cred proxy as system default creds
To enable using the default system credentials for authenticated proxies we must set the `WebProxy.UseDefaultCredentials` property to `true`. (For example, with Windows Integrated Authentication-based proxies - NTLM or Kerberos.) Currently we set this if no username and password information is specified at all in the proxy configuration. However, Git itself when presented with this configuration will prompt the user for a username/password (which isn't required). Specifying an empty string username/password in the proxy configuration will stop Git from prompting for them, and allow cURL to auto-negotiate proxy auth. However, GCM reads that same configuration as having a username/password based proxy with the empty string for both values. We change GCM to use the system default credentials when configuring a proxy if there is no userinfo OR if BOTH the username and password values are the empty string. Example proxy config value: http://:@proxy.example.com
1 parent edfb863 commit 4f91faa

File tree

2 files changed

+108
-4
lines changed

2 files changed

+108
-4
lines changed

src/shared/Microsoft.Git.CredentialManager.Tests/HttpClientFactoryTests.cs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,104 @@ public void HttpClientFactory_TryCreateProxy_ProxyWithCredentials_ReturnsTrueOut
118118
Assert.Equal(proxyPass, configuredCredentials.Password);
119119
}
120120

121+
[Fact]
122+
public void HttpClientFactory_TryCreateProxy_ProxyWithNonEmptyUserAndEmptyPass_ReturnsTrueOutProxyWithUrlConfiguredCredentials()
123+
{
124+
const string proxyScheme = "https";
125+
const string proxyUser = "john.doe";
126+
const string proxyHost = "proxy.example.com/git";
127+
const string repoPath = "/tmp/repos/foo";
128+
const string repoRemote = "https://remote.example.com/foo.git";
129+
130+
string proxyConfigString = $"{proxyScheme}://{proxyUser}:@{proxyHost}";
131+
string expectedProxyUrl = $"{proxyScheme}://{proxyHost}";
132+
var repoRemoteUri = new Uri(repoRemote);
133+
134+
var settings = new TestSettings
135+
{
136+
RemoteUri = repoRemoteUri,
137+
RepositoryPath = repoPath,
138+
ProxyConfiguration = new Uri(proxyConfigString)
139+
};
140+
var httpFactory = new HttpClientFactory(Mock.Of<ITrace>(), settings, Mock.Of<IStandardStreams>());
141+
142+
bool result = httpFactory.TryCreateProxy(out IWebProxy proxy);
143+
144+
Assert.True(result);
145+
Assert.NotNull(proxy);
146+
var configuredProxyUrl = proxy.GetProxy(repoRemoteUri);
147+
Assert.Equal(expectedProxyUrl, configuredProxyUrl.ToString());
148+
149+
Assert.NotNull(proxy.Credentials);
150+
Assert.IsType<NetworkCredential>(proxy.Credentials);
151+
var configuredCredentials = (NetworkCredential) proxy.Credentials;
152+
Assert.Equal(proxyUser, configuredCredentials.UserName);
153+
Assert.True(string.IsNullOrWhiteSpace(configuredCredentials.Password));
154+
}
155+
156+
[Fact]
157+
public void HttpClientFactory_TryCreateProxy_ProxyWithEmptyUserAndNonEmptyPass_ReturnsTrueOutProxyWithUrlConfiguredCredentials()
158+
{
159+
const string proxyScheme = "https";
160+
const string proxyPass = "letmein";
161+
const string proxyHost = "proxy.example.com/git";
162+
const string repoPath = "/tmp/repos/foo";
163+
const string repoRemote = "https://remote.example.com/foo.git";
164+
165+
string proxyConfigString = $"{proxyScheme}://:{proxyPass}@{proxyHost}";
166+
string expectedProxyUrl = $"{proxyScheme}://{proxyHost}";
167+
var repoRemoteUri = new Uri(repoRemote);
168+
169+
var settings = new TestSettings
170+
{
171+
RemoteUri = repoRemoteUri,
172+
RepositoryPath = repoPath,
173+
ProxyConfiguration = new Uri(proxyConfigString)
174+
};
175+
var httpFactory = new HttpClientFactory(Mock.Of<ITrace>(), settings, Mock.Of<IStandardStreams>());
176+
177+
bool result = httpFactory.TryCreateProxy(out IWebProxy proxy);
178+
179+
Assert.True(result);
180+
Assert.NotNull(proxy);
181+
var configuredProxyUrl = proxy.GetProxy(repoRemoteUri);
182+
Assert.Equal(expectedProxyUrl, configuredProxyUrl.ToString());
183+
184+
Assert.NotNull(proxy.Credentials);
185+
Assert.IsType<NetworkCredential>(proxy.Credentials);
186+
var configuredCredentials = (NetworkCredential) proxy.Credentials;
187+
Assert.True(string.IsNullOrWhiteSpace(configuredCredentials.UserName));
188+
Assert.Equal(proxyPass, configuredCredentials.Password);
189+
}
190+
191+
[Fact]
192+
public void HttpClientFactory_TryCreateProxy_ProxyEmptyUserAndEmptyPass_ReturnsTrueOutProxyWithUrlDefaultCredentials()
193+
{
194+
const string repoPath = "/tmp/repos/foo";
195+
const string repoRemote = "https://remote.example.com/foo.git";
196+
var repoRemoteUri = new Uri(repoRemote);
197+
198+
string proxyConfigString = "https://:@proxy.example.com/git";
199+
string expectedProxyUrl = "https://proxy.example.com/git";
200+
201+
var settings = new TestSettings
202+
{
203+
RemoteUri = repoRemoteUri,
204+
RepositoryPath = repoPath,
205+
ProxyConfiguration = new Uri(proxyConfigString)
206+
};
207+
var httpFactory = new HttpClientFactory(Mock.Of<ITrace>(), settings, Mock.Of<IStandardStreams>());
208+
209+
bool result = httpFactory.TryCreateProxy(out IWebProxy proxy);
210+
211+
Assert.True(result);
212+
Assert.NotNull(proxy);
213+
var configuredProxyUrl = proxy.GetProxy(repoRemoteUri);
214+
Assert.Equal(expectedProxyUrl, configuredProxyUrl.ToString());
215+
216+
AssertDefaultCredentials(proxy.Credentials);
217+
}
218+
121219
private static void AssertDefaultCredentials(ICredentials credentials)
122220
{
123221
var netCred = (NetworkCredential) credentials;

src/shared/Microsoft.Git.CredentialManager/HttpClientFactory.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,24 +120,30 @@ public bool TryCreateProxy(out IWebProxy proxy)
120120
// Dictionary of proxy info for tracing
121121
var dict = new Dictionary<string, string> {["uri"] = proxyUri.ToString()};
122122

123-
// Try to extract and configure proxy credentials from the configured URI
124-
if (proxyConfig.TryGetUserInfo(out string userName, out string password))
123+
// Try to extract and configure proxy credentials from the configured URI.
124+
// For an empty username AND password we should use the system default credentials
125+
// (for example for Windows Integrated Authentication-based proxies).
126+
if (proxyConfig.TryGetUserInfo(out string userName, out string password) &&
127+
!(string.IsNullOrEmpty(userName) && string.IsNullOrEmpty(password)))
125128
{
126129
proxy = new WebProxy(proxyUri)
127130
{
128131
Credentials = new NetworkCredential(userName, password)
129132
};
130133

131134
// Add user/pass info to the trace dictionary
132-
if (!string.IsNullOrWhiteSpace(userName)) dict["username"] = userName;
133-
if (!string.IsNullOrEmpty(password)) dict["password"] = password;
135+
dict["username"] = userName;
136+
dict["password"] = password;
134137
}
135138
else
136139
{
137140
proxy = new WebProxy(proxyUri)
138141
{
139142
UseDefaultCredentials = true
140143
};
144+
145+
// Trace the use of system default credentials
146+
dict["useDefaultCredentials"] = "true";
141147
}
142148

143149
// Tracer out proxy info dictionary

0 commit comments

Comments
 (0)