On retrieving a connection from a pool, HttpConnectionPoolManager adds the current user identity to the HttpConnectionKey for direct and proxy connections when the default credentials is used on Windows platform. Since on Unix there is not the concept of a user identity on the thread, the identity component in the key is always set to string.Empty.
Fixes dotnet/corefx#39621
<ItemGroup Condition=" '$(TargetsUnix)' == 'true'">
<Compile Include="System\Net\Http\SocketsHttpHandler\HttpEnvironmentProxy.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\HttpEnvironmentProxy.Unix.cs" />
+ <Compile Include="System\Net\Http\SocketsHttpHandler\CurrentUserIdentityProvider.Unix.cs" />
<Compile Include="$(CommonPath)System\Net\ContextFlagsAdapterPal.Unix.cs">
<Link>Common\System\Net\ContextFlagsAdapterPal.Unix.cs</Link>
</Compile>
<Compile Include="System\Net\Http\SocketsHttpHandler\HttpEnvironmentProxy.Windows.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\HttpNoProxy.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\HttpWindowsProxy.cs" />
+ <Compile Include="System\Net\Http\SocketsHttpHandler\CurrentUserIdentityProvider.Windows.cs" />
<Compile Include="$(CommonPath)Interop\Windows\SChannel\Interop.SecPkgContext_ApplicationProtocol.cs">
<Link>Common\Interop\Windows\SChannel\Interop.SecPkgContext_ApplicationProtocol.cs</Link>
</Compile>
<Reference Include="System.Runtime" />
<Reference Include="System.Runtime.Extensions" />
<Reference Include="System.Runtime.InteropServices" />
+ <Reference Include="System.Security.Claims" Condition="'$(TargetsWindows)' == 'true'" />
<Reference Include="System.Security.Cryptography.Algorithms" />
<Reference Include="System.Security.Cryptography.Csp" />
<Reference Include="System.Security.Cryptography.Encoding" />
<Reference Include="System.Security.Cryptography.Primitives" />
<Reference Include="System.Security.Cryptography.X509Certificates" />
+ <Reference Include="System.Security.Principal" Condition="'$(TargetsWindows)' == 'true'" />
<Reference Include="System.Security.Principal.Windows" />
<Reference Include="System.Threading" />
<Reference Include="System.Threading.Tasks" />
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Net.Http
+{
+ internal static class CurrentUserIdentityProvider
+ {
+ public static string GetIdentity()
+ {
+ return string.Empty;
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Security.Principal;
+
+namespace System.Net.Http
+{
+ internal static class CurrentUserIdentityProvider
+ {
+ public static string GetIdentity()
+ {
+ using WindowsIdentity identity = WindowsIdentity.GetCurrent();
+ return identity.Name;
+ }
+ }
+}
return hostHeader;
}
- private static HttpConnectionKey GetConnectionKey(HttpRequestMessage request, Uri proxyUri, bool isProxyConnect)
+ private HttpConnectionKey GetConnectionKey(HttpRequestMessage request, Uri proxyUri, bool isProxyConnect)
{
Uri uri = request.RequestUri;
if (isProxyConnect)
{
Debug.Assert(uri == proxyUri);
- return new HttpConnectionKey(HttpConnectionKind.ProxyConnect, uri.IdnHost, uri.Port, null, proxyUri);
+ return new HttpConnectionKey(HttpConnectionKind.ProxyConnect, uri.IdnHost, uri.Port, null, proxyUri, GetIdentityIfDefaultCredentialsUsed(_settings._defaultCredentialsUsedForProxy));
}
string sslHostName = null;
}
}
+ string identity = GetIdentityIfDefaultCredentialsUsed(proxyUri != null ? _settings._defaultCredentialsUsedForProxy : _settings._defaultCredentialsUsedForServer);
+
if (proxyUri != null)
{
Debug.Assert(HttpUtilities.IsSupportedNonSecureScheme(proxyUri.Scheme));
if (HttpUtilities.IsNonSecureWebSocketScheme(uri.Scheme))
{
// Non-secure websocket connection through proxy to the destination.
- return new HttpConnectionKey(HttpConnectionKind.ProxyTunnel, uri.IdnHost, uri.Port, null, proxyUri);
+ return new HttpConnectionKey(HttpConnectionKind.ProxyTunnel, uri.IdnHost, uri.Port, null, proxyUri, identity);
}
else
{
// Standard HTTP proxy usage for non-secure requests
// The destination host and port are ignored here, since these connections
// will be shared across any requests that use the proxy.
- return new HttpConnectionKey(HttpConnectionKind.Proxy, null, 0, null, proxyUri);
+ return new HttpConnectionKey(HttpConnectionKind.Proxy, null, 0, null, proxyUri, identity);
}
}
else
{
// Tunnel SSL connection through proxy to the destination.
- return new HttpConnectionKey(HttpConnectionKind.SslProxyTunnel, uri.IdnHost, uri.Port, sslHostName, proxyUri);
+ return new HttpConnectionKey(HttpConnectionKind.SslProxyTunnel, uri.IdnHost, uri.Port, sslHostName, proxyUri, identity);
}
}
else if (sslHostName != null)
{
- return new HttpConnectionKey(HttpConnectionKind.Https, uri.IdnHost, uri.Port, sslHostName, null);
+ return new HttpConnectionKey(HttpConnectionKind.Https, uri.IdnHost, uri.Port, sslHostName, null, identity);
}
else
{
- return new HttpConnectionKey(HttpConnectionKind.Http, uri.IdnHost, uri.Port, null, null);
+ return new HttpConnectionKey(HttpConnectionKind.Http, uri.IdnHost, uri.Port, null, null, identity);
}
}
if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
}
+ private static string GetIdentityIfDefaultCredentialsUsed(bool defaultCredentialsUsed)
+ {
+ return defaultCredentialsUsed ? CurrentUserIdentityProvider.GetIdentity() : string.Empty;
+ }
+
internal readonly struct HttpConnectionKey : IEquatable<HttpConnectionKey>
{
public readonly HttpConnectionKind Kind;
public readonly int Port;
public readonly string SslHostName; // null if not SSL
public readonly Uri ProxyUri;
+ public readonly string Identity;
- public HttpConnectionKey(HttpConnectionKind kind, string host, int port, string sslHostName, Uri proxyUri)
+ public HttpConnectionKey(HttpConnectionKind kind, string host, int port, string sslHostName, Uri proxyUri, string identity)
{
Kind = kind;
Host = host;
Port = port;
SslHostName = sslHostName;
ProxyUri = proxyUri;
+ Identity = identity;
}
// In the common case, SslHostName (when present) is equal to Host. If so, don't include in hash.
public override int GetHashCode() =>
(SslHostName == Host ?
- HashCode.Combine(Kind, Host, Port, ProxyUri) :
- HashCode.Combine(Kind, Host, Port, SslHostName, ProxyUri));
+ HashCode.Combine(Kind, Host, Port, ProxyUri, Identity) :
+ HashCode.Combine(Kind, Host, Port, SslHostName, ProxyUri, Identity));
public override bool Equals(object obj) =>
obj != null &&
Host == other.Host &&
Port == other.Port &&
ProxyUri == other.ProxyUri &&
- SslHostName == other.SslHostName;
+ SslHostName == other.SslHostName &&
+ Identity == other.Identity;
}
}
}
internal bool _useProxy = HttpHandlerDefaults.DefaultUseProxy;
internal IWebProxy _proxy;
internal ICredentials _defaultProxyCredentials;
+ internal bool _defaultCredentialsUsedForProxy;
+ internal bool _defaultCredentialsUsedForServer;
internal bool _preAuthenticate = HttpHandlerDefaults.DefaultPreAuthenticate;
internal ICredentials _credentials;
bool allowHttp2 = AllowHttp2;
_maxHttpVersion = allowHttp2 ? HttpVersion.Version20 : HttpVersion.Version11;
_allowUnencryptedHttp2 = allowHttp2 && AllowUnencryptedHttp2;
+ _defaultCredentialsUsedForProxy = _proxy != null && (_proxy.Credentials == CredentialCache.DefaultCredentials || _defaultProxyCredentials == CredentialCache.DefaultCredentials);
+ _defaultCredentialsUsedForServer = _credentials == CredentialCache.DefaultCredentials;
}
/// <summary>Creates a copy of the settings but with some values normalized to suit the implementation.</summary>
_connectTimeout = _connectTimeout,
_credentials = _credentials,
_defaultProxyCredentials = _defaultProxyCredentials,
+ _defaultCredentialsUsedForProxy = _defaultCredentialsUsedForProxy,
+ _defaultCredentialsUsedForServer = _defaultCredentialsUsedForServer,
_expect100ContinueTimeout = _expect100ContinueTimeout,
_maxAutomaticRedirections = _maxAutomaticRedirections,
_maxConnectionsPerServer = _maxConnectionsPerServer,
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Reflection;
+using Xunit;
+
+namespace System.Net.Http.Functional.Tests
+{
+ public class HttpConnectionKeyTest
+ {
+ public static IEnumerable<object[]> KeyComponents()
+ {
+ yield return new object[] { "Https", "localhost", 80, "localhost-ssl", new Uri("http://localhost"), "domain1/userA", false};
+ yield return new object[] { "Http", "localhost1", 80, "localhost-ssl", new Uri("http://localhost"), "domain1/userA", false };
+ yield return new object[] { "Http", "localhost", 81, "localhost-ssl", new Uri("http://localhost"), "domain1/userA", false };
+ yield return new object[] { "Http", "localhost", 80, "localhost-ssl1", new Uri("http://localhost"), "domain1/userA", false };
+ yield return new object[] { "Http", "localhost", 80, "localhost-ssl", new Uri("http://localhost1"), "domain1/userA", false };
+ yield return new object[] { "Http", "localhost", 80, "localhost-ssl", new Uri("http://localhost"), "domain1/userB", false };
+ yield return new object[] { "Http", "localhost", 80, "localhost-ssl", new Uri("http://localhost"), "domain1/userA", true };
+ }
+
+ [Theory, MemberData(nameof(KeyComponents))]
+ public void Equals_DifferentParameters_ReturnsTrueIfAllEqual(string kindString, string host, int port, string sslHostName, Uri proxyUri, string identity, bool expected)
+ {
+ Assembly assembly = typeof(HttpClientHandler).Assembly;
+ Type connectionKindType = assembly.GetTypes().Where(t => t.Name == "HttpConnectionKind").First();
+ Type poolManagerType = assembly.GetTypes().Where(t => t.Name == "HttpConnectionPoolManager").First();
+ Type keyType = poolManagerType.GetNestedType("HttpConnectionKey", BindingFlags.NonPublic);
+ dynamic referenceKey = Activator.CreateInstance(keyType, Enum.Parse(connectionKindType, "Http"), "localhost", 80, "localhost-ssl", new Uri("http://localhost"), "domain1/userA");
+ dynamic actualKey = Activator.CreateInstance(keyType, Enum.Parse(connectionKindType, kindString), host, port, sslHostName, proxyUri, identity);
+ Assert.Equal(expected, referenceKey.Equals(actualKey));
+ }
+ }
+}
<Compile Include="HttpClientTest.cs" />
<Compile Include="HttpClientEKUTest.cs" />
<Compile Include="HttpClient.SelectedSitesTest.cs" />
+ <Compile Include="HttpConnectionKeyTest.cs" />
<Compile Include="HttpContentTest.cs" />
<Compile Include="HttpMessageInvokerTest.cs" />
<Compile Include="HttpMethodTest.cs" />