Skip to content

Commit f0a34a4

Browse files
Clone Windows Identity in LongPolling connections (#2985)
1 parent bf1aa1d commit f0a34a4

File tree

4 files changed

+44
-7
lines changed

4 files changed

+44
-7
lines changed

build/dependencies.props

+1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
<SystemReactiveLinqPackageVersion>3.1.1</SystemReactiveLinqPackageVersion>
7070
<SystemReflectionEmitPackageVersion>4.3.0</SystemReflectionEmitPackageVersion>
7171
<SystemRuntimeCompilerServicesUnsafePackageVersion>4.5.1</SystemRuntimeCompilerServicesUnsafePackageVersion>
72+
<SystemSecurityPrincipalWindowsPackageVersion>4.5.0</SystemSecurityPrincipalWindowsPackageVersion>
7273
<SystemThreadingChannelsPackageVersion>4.5.0</SystemThreadingChannelsPackageVersion>
7374
<SystemThreadingTasksExtensionsPackageVersion>4.5.1</SystemThreadingTasksExtensionsPackageVersion>
7475
<XunitAssertPackageVersion>2.3.1</XunitAssertPackageVersion>

src/Microsoft.AspNetCore.Http.Connections/Internal/HttpConnectionContext.cs

+9
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Collections.Generic;
77
using System.IO.Pipelines;
88
using System.Security.Claims;
9+
using System.Security.Principal;
910
using System.Threading;
1011
using System.Threading.Tasks;
1112
using Microsoft.AspNetCore.Connections;
@@ -208,6 +209,14 @@ public async Task DisposeAsync(bool closeGracefully = false)
208209
Cancellation?.Dispose();
209210

210211
Cancellation = null;
212+
213+
if (User != null && User.Identity is WindowsIdentity)
214+
{
215+
foreach (var identity in User.Identities)
216+
{
217+
(identity as IDisposable)?.Dispose();
218+
}
219+
}
211220
}
212221

213222
await disposeTask;

src/Microsoft.AspNetCore.Http.Connections/Internal/HttpConnectionDispatcher.cs

+33-7
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
using System.Diagnostics;
88
using System.IO;
99
using System.IO.Pipelines;
10+
using System.Security.Claims;
11+
using System.Security.Principal;
1012
using System.Threading;
1113
using System.Threading.Tasks;
1214
using Microsoft.AspNetCore.Connections;
13-
using Microsoft.AspNetCore.Connections.Features;
14-
using Microsoft.AspNetCore.Http.Connections.Features;
1515
using Microsoft.AspNetCore.Http.Connections.Internal.Transports;
1616
using Microsoft.AspNetCore.Http.Features;
1717
using Microsoft.AspNetCore.Internal;
@@ -608,9 +608,6 @@ private async Task<bool> EnsureConnectionStateAsync(HttpConnectionContext connec
608608
return false;
609609
}
610610

611-
// Setup the connection state from the http context
612-
connection.User = context.User;
613-
614611
// Configure transport-specific features.
615612
if (transportType == HttpTransportType.LongPolling)
616613
{
@@ -630,21 +627,49 @@ private async Task<bool> EnsureConnectionStateAsync(HttpConnectionContext connec
630627
{
631628
// Set the request trace identifier to the current http request handling the poll
632629
existing.TraceIdentifier = context.TraceIdentifier;
633-
existing.User = context.User;
630+
631+
// Don't copy the identity if it's a windows identity
632+
// We specifically clone the identity on first poll if it's a windows identity
633+
// If we swapped the new User here we'd have to dispose the old identities which could race with the application
634+
// trying to access the identity.
635+
if (context.User.Identity is WindowsIdentity)
636+
{
637+
existing.User = context.User;
638+
}
634639
}
635640
}
636641
else
637642
{
638643
connection.HttpContext = context;
639644
}
640645

646+
// Setup the connection state from the http context
647+
connection.User = connection.HttpContext.User;
648+
641649
// Set the Connection ID on the logging scope so that logs from now on will have the
642650
// Connection ID metadata set.
643651
logScope.ConnectionId = connection.ConnectionId;
644652

645653
return true;
646654
}
647655

656+
private static void CloneUser(HttpContext newContext, HttpContext oldContext)
657+
{
658+
if (oldContext.User.Identity is WindowsIdentity)
659+
{
660+
newContext.User = new ClaimsPrincipal();
661+
662+
foreach (var identity in oldContext.User.Identities)
663+
{
664+
newContext.User.AddIdentity(identity.Clone());
665+
}
666+
}
667+
else
668+
{
669+
newContext.User = oldContext.User;
670+
}
671+
}
672+
648673
private static HttpContext CloneHttpContext(HttpContext context)
649674
{
650675
// The reason we're copying the base features instead of the HttpContext properties is
@@ -692,7 +717,8 @@ private static HttpContext CloneHttpContext(HttpContext context)
692717

693718
var newHttpContext = new DefaultHttpContext(features);
694719
newHttpContext.TraceIdentifier = context.TraceIdentifier;
695-
newHttpContext.User = context.User;
720+
721+
CloneUser(newHttpContext, context);
696722

697723
// Making request services function property could be tricky and expensive as it would require
698724
// DI scope per connection. It would also mean that services resolved in middleware leading up to here

src/Microsoft.AspNetCore.Http.Connections/Microsoft.AspNetCore.Http.Connections.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
<PackageReference Include="Microsoft.Extensions.WebEncoders.Sources" Version="$(MicrosoftExtensionsWebEncodersSourcesPackageVersion)" PrivateAssets="All" />
3030
<PackageReference Include="Microsoft.Extensions.ValueStopwatch.Sources" Version="$(MicrosoftExtensionsValueStopwatchSourcesPackageVersion)" PrivateAssets="All" />
3131
<PackageReference Include="Newtonsoft.Json" Version="$(NewtonsoftJsonPackageVersion)" />
32+
<PackageReference Include="System.Security.Principal.Windows" Version="$(SystemSecurityPrincipalWindowsPackageVersion)" />
3233
</ItemGroup>
3334

3435
</Project>

0 commit comments

Comments
 (0)