Skip to content

Commit 1ea10f6

Browse files
Create WindowsPrincipal when cloning WindowsIdentity for SignalR (#19337)
1 parent 03a9e8e commit 1ea10f6

File tree

2 files changed

+77
-3
lines changed

2 files changed

+77
-3
lines changed

src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.cs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -572,12 +572,31 @@ private async Task<bool> EnsureConnectionStateAsync(HttpConnectionContext connec
572572

573573
private static void CloneUser(HttpContext newContext, HttpContext oldContext)
574574
{
575-
if (oldContext.User.Identity is WindowsIdentity)
575+
// If the identity is a WindowsIdentity we need to clone the User.
576+
// This is because the WindowsIdentity uses SafeHandle's which are disposed at the end of the request
577+
// and accessing the identity can happen outside of the request scope.
578+
if (oldContext.User.Identity is WindowsIdentity windowsIdentity)
576579
{
577-
newContext.User = new ClaimsPrincipal();
580+
var skipFirstIdentity = false;
581+
if (oldContext.User is WindowsPrincipal)
582+
{
583+
// We want to explicitly create a WindowsPrincipal instead of a ClaimsPrincipal
584+
// so methods that WindowsPrincipal overrides like 'IsInRole', work as expected.
585+
newContext.User = new WindowsPrincipal((WindowsIdentity)(windowsIdentity.Clone()));
586+
skipFirstIdentity = true;
587+
}
588+
else
589+
{
590+
newContext.User = new ClaimsPrincipal();
591+
}
578592

579593
foreach (var identity in oldContext.User.Identities)
580594
{
595+
if (skipFirstIdentity)
596+
{
597+
skipFirstIdentity = false;
598+
continue;
599+
}
581600
newContext.User.AddIdentity(identity.Clone());
582601
}
583602
}

src/SignalR/common/Http.Connections/test/HttpConnectionDispatcherTests.cs

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1639,7 +1639,7 @@ public async Task TransferModeSet(HttpTransportType transportType, TransferForma
16391639

16401640
[ConditionalFact]
16411641
[OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)]
1642-
public async Task LongPollingKeepsWindowsIdentityBetweenRequests()
1642+
public async Task LongPollingKeepsWindowsPrincipalAndIdentityBetweenRequests()
16431643
{
16441644
using (StartVerifiableLog())
16451645
{
@@ -1668,6 +1668,7 @@ public async Task LongPollingKeepsWindowsIdentityBetweenRequests()
16681668

16691669
var windowsIdentity = WindowsIdentity.GetAnonymous();
16701670
context.User = new WindowsPrincipal(windowsIdentity);
1671+
context.User.AddIdentity(new ClaimsIdentity());
16711672

16721673
// would get stuck if EndPoint was running
16731674
await dispatcher.ExecuteAsync(context, options, app).OrTimeout();
@@ -1681,6 +1682,60 @@ public async Task LongPollingKeepsWindowsIdentityBetweenRequests()
16811682

16821683
// This is the important check
16831684
Assert.Same(currentUser, connection.User);
1685+
Assert.IsType<WindowsPrincipal>(currentUser);
1686+
Assert.Equal(2, connection.User.Identities.Count());
1687+
1688+
Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
1689+
}
1690+
}
1691+
1692+
[ConditionalFact]
1693+
[OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)]
1694+
public async Task LongPollingKeepsWindowsIdentityWithoutWindowsPrincipalBetweenRequests()
1695+
{
1696+
using (StartVerifiableLog())
1697+
{
1698+
var manager = CreateConnectionManager(LoggerFactory);
1699+
var connection = manager.CreateConnection();
1700+
connection.TransportType = HttpTransportType.LongPolling;
1701+
var dispatcher = new HttpConnectionDispatcher(manager, LoggerFactory);
1702+
var context = new DefaultHttpContext();
1703+
var services = new ServiceCollection();
1704+
services.AddOptions();
1705+
services.AddSingleton<TestConnectionHandler>();
1706+
services.AddLogging();
1707+
var sp = services.BuildServiceProvider();
1708+
context.Request.Path = "/foo";
1709+
context.Request.Method = "GET";
1710+
context.RequestServices = sp;
1711+
var values = new Dictionary<string, StringValues>();
1712+
values["id"] = connection.ConnectionToken;
1713+
values["negotiateVersion"] = "1";
1714+
var qs = new QueryCollection(values);
1715+
context.Request.Query = qs;
1716+
var builder = new ConnectionBuilder(sp);
1717+
builder.UseConnectionHandler<TestConnectionHandler>();
1718+
var app = builder.Build();
1719+
var options = new HttpConnectionDispatcherOptions();
1720+
1721+
var windowsIdentity = WindowsIdentity.GetAnonymous();
1722+
context.User = new ClaimsPrincipal(windowsIdentity);
1723+
context.User.AddIdentity(new ClaimsIdentity());
1724+
1725+
// would get stuck if EndPoint was running
1726+
await dispatcher.ExecuteAsync(context, options, app).OrTimeout();
1727+
1728+
Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
1729+
var currentUser = connection.User;
1730+
1731+
var connectionHandlerTask = dispatcher.ExecuteAsync(context, options, app);
1732+
await connection.Transport.Output.WriteAsync(Encoding.UTF8.GetBytes("Unblock")).AsTask().OrTimeout();
1733+
await connectionHandlerTask.OrTimeout();
1734+
1735+
// This is the important check
1736+
Assert.Same(currentUser, connection.User);
1737+
Assert.IsNotType<WindowsPrincipal>(currentUser);
1738+
Assert.Equal(2, connection.User.Identities.Count());
16841739

16851740
Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
16861741
}

0 commit comments

Comments
 (0)