Skip to content

Commit

Permalink
Current user identity in added to HttpConnectionKey (dotnet#303)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
alnikola authored Dec 2, 2019
1 parent 6afe96c commit 3cec4f5
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 11 deletions.
4 changes: 4 additions & 0 deletions src/libraries/System.Net.Http/src/System.Net.Http.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@
<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>
Expand Down Expand Up @@ -330,6 +331,7 @@
<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>
Expand Down Expand Up @@ -631,11 +633,13 @@
<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" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// 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;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// 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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -162,14 +162,14 @@ private static string ParseHostNameFromHeader(string hostHeader)
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;
Expand All @@ -187,6 +187,8 @@ private static HttpConnectionKey GetConnectionKey(HttpRequestMessage request, Ur
}
}

string identity = GetIdentityIfDefaultCredentialsUsed(proxyUri != null ? _settings._defaultCredentialsUsedForProxy : _settings._defaultCredentialsUsedForServer);

if (proxyUri != null)
{
Debug.Assert(HttpUtilities.IsSupportedNonSecureScheme(proxyUri.Scheme));
Expand All @@ -195,29 +197,29 @@ private static HttpConnectionKey GetConnectionKey(HttpRequestMessage request, Ur
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);
}
}

Expand Down Expand Up @@ -408,28 +410,35 @@ private void RemoveStalePools()
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 string Host;
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 &&
Expand All @@ -441,7 +450,8 @@ public bool Equals(HttpConnectionKey other) =>
Host == other.Host &&
Port == other.Port &&
ProxyUri == other.ProxyUri &&
SslHostName == other.SslHostName;
SslHostName == other.SslHostName &&
Identity == other.Identity;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ internal sealed class HttpConnectionSettings
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;
Expand Down Expand Up @@ -53,6 +55,8 @@ public HttpConnectionSettings()
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>
Expand All @@ -72,6 +76,8 @@ public HttpConnectionSettings CloneAndNormalize()
_connectTimeout = _connectTimeout,
_credentials = _credentials,
_defaultProxyCredentials = _defaultProxyCredentials,
_defaultCredentialsUsedForProxy = _defaultCredentialsUsedForProxy,
_defaultCredentialsUsedForServer = _defaultCredentialsUsedForServer,
_expect100ContinueTimeout = _expect100ContinueTimeout,
_maxAutomaticRedirections = _maxAutomaticRedirections,
_maxConnectionsPerServer = _maxConnectionsPerServer,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// 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));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@
<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" />
Expand Down

0 comments on commit 3cec4f5

Please sign in to comment.