Skip to content

Create WindowsPrincipal when cloning WindowsIdentity for SignalR #19337

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -572,12 +572,31 @@ private async Task<bool> EnsureConnectionStateAsync(HttpConnectionContext connec

private static void CloneUser(HttpContext newContext, HttpContext oldContext)
{
if (oldContext.User.Identity is WindowsIdentity)
// If the identity is a WindowsIdentity we need to clone the User.
// This is because the WindowsIdentity uses SafeHandle's which are disposed at the end of the request
// and accessing the identity can happen outside of the request scope.
if (oldContext.User.Identity is WindowsIdentity windowsIdentity)
{
newContext.User = new ClaimsPrincipal();
var skipFirstIdentity = false;
if (oldContext.User is WindowsPrincipal)
{
// We want to explicitly create a WindowsPrincipal instead of a ClaimsPrincipal
// so methods that WindowsPrincipal overrides like 'IsInRole', work as expected.
newContext.User = new WindowsPrincipal((WindowsIdentity)(windowsIdentity.Clone()));
skipFirstIdentity = true;
}
else
{
newContext.User = new ClaimsPrincipal();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, to get rid of skipFirstIdentity

Suggested change
newContext.User = new ClaimsPrincipal();
newContext.User = new ClaimsPrincipal(windowsIdentity.Clone());

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Think I have to keep it as is, because I still need to skip somehow when copying the rest of the identities.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't you just always skip? You could do something like oldContext.User.Identities.Skip(1), right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That allocates I believe

}

foreach (var identity in oldContext.User.Identities)
{
if (skipFirstIdentity)
{
skipFirstIdentity = false;
continue;
}
newContext.User.AddIdentity(identity.Clone());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1639,7 +1639,7 @@ public async Task TransferModeSet(HttpTransportType transportType, TransferForma

[ConditionalFact]
[OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)]
public async Task LongPollingKeepsWindowsIdentityBetweenRequests()
public async Task LongPollingKeepsWindowsPrincipalAndIdentityBetweenRequests()
{
using (StartVerifiableLog())
{
Expand Down Expand Up @@ -1668,6 +1668,7 @@ public async Task LongPollingKeepsWindowsIdentityBetweenRequests()

var windowsIdentity = WindowsIdentity.GetAnonymous();
context.User = new WindowsPrincipal(windowsIdentity);
context.User.AddIdentity(new ClaimsIdentity());

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

// This is the important check
Assert.Same(currentUser, connection.User);
Assert.IsType<WindowsPrincipal>(currentUser);
Assert.Equal(2, connection.User.Identities.Count());

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

[ConditionalFact]
[OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)]
public async Task LongPollingKeepsWindowsIdentityWithoutWindowsPrincipalBetweenRequests()
{
using (StartVerifiableLog())
{
var manager = CreateConnectionManager(LoggerFactory);
var connection = manager.CreateConnection();
connection.TransportType = HttpTransportType.LongPolling;
var dispatcher = new HttpConnectionDispatcher(manager, LoggerFactory);
var context = new DefaultHttpContext();
var services = new ServiceCollection();
services.AddOptions();
services.AddSingleton<TestConnectionHandler>();
services.AddLogging();
var sp = services.BuildServiceProvider();
context.Request.Path = "/foo";
context.Request.Method = "GET";
context.RequestServices = sp;
var values = new Dictionary<string, StringValues>();
values["id"] = connection.ConnectionToken;
values["negotiateVersion"] = "1";
var qs = new QueryCollection(values);
context.Request.Query = qs;
var builder = new ConnectionBuilder(sp);
builder.UseConnectionHandler<TestConnectionHandler>();
var app = builder.Build();
var options = new HttpConnectionDispatcherOptions();

var windowsIdentity = WindowsIdentity.GetAnonymous();
context.User = new ClaimsPrincipal(windowsIdentity);
context.User.AddIdentity(new ClaimsIdentity());

// would get stuck if EndPoint was running
await dispatcher.ExecuteAsync(context, options, app).OrTimeout();

Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
var currentUser = connection.User;

var connectionHandlerTask = dispatcher.ExecuteAsync(context, options, app);
await connection.Transport.Output.WriteAsync(Encoding.UTF8.GetBytes("Unblock")).AsTask().OrTimeout();
await connectionHandlerTask.OrTimeout();

// This is the important check
Assert.Same(currentUser, connection.User);
Assert.IsNotType<WindowsPrincipal>(currentUser);
Assert.Equal(2, connection.User.Identities.Count());

Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
}
Expand Down