Update user on reconnect. Fixes #12051 (#12421)

This commit is contained in:
Steve Sanderson 2019-07-24 10:23:38 -07:00 committed by GitHub
parent 9b6f10d20c
commit 8b7fcf1f76
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 154 additions and 47 deletions

View File

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Security.Claims;
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Components.Server.Circuits
@ -11,6 +12,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
HttpContext httpContext,
CircuitClientProxy client,
string uriAbsolute,
string baseUriAbsolute);
string baseUriAbsolute,
ClaimsPrincipal user);
}
}

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
@ -109,6 +110,16 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
});
}
public void SetCircuitUser(ClaimsPrincipal user)
{
var authenticationStateProvider = Services.GetService<AuthenticationStateProvider>() as IHostEnvironmentAuthenticationStateProvider;
if (authenticationStateProvider != null)
{
var authenticationState = new AuthenticationState(user);
authenticationStateProvider.SetAuthenticationState(Task.FromResult(authenticationState));
}
}
internal void InitializeCircuitAfterPrerender(UnhandledExceptionEventHandler unhandledException)
{
if (!_initialized)

View File

@ -132,7 +132,8 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
context,
client: new CircuitClientProxy(), // This creates an "offline" client.
GetFullUri(context.Request),
GetFullBaseUri(context.Request));
GetFullBaseUri(context.Request),
context.User);
result.UnhandledException += CircuitHost_UnhandledException;
context.Response.OnCompleted(() =>

View File

@ -4,17 +4,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.Web.Rendering;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.AspNetCore.Components.Routing;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.JSInterop;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Components.Server.Circuits
{
@ -40,7 +39,8 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
HttpContext httpContext,
CircuitClientProxy client,
string uriAbsolute,
string baseUriAbsolute)
string baseUriAbsolute,
ClaimsPrincipal user)
{
var components = ResolveComponentMetadata(httpContext, client);
@ -51,13 +51,6 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
jsRuntime.Initialize(client);
componentContext.Initialize(client);
var authenticationStateProvider = scope.ServiceProvider.GetService<AuthenticationStateProvider>() as IHostEnvironmentAuthenticationStateProvider;
if (authenticationStateProvider != null)
{
var authenticationState = new AuthenticationState(httpContext.User); // TODO: Get this from the hub connection context instead
authenticationStateProvider.SetAuthenticationState(Task.FromResult(authenticationState));
}
var uriHelper = (RemoteUriHelper)scope.ServiceProvider.GetRequiredService<IUriHelper>();
var navigationInterception = (RemoteNavigationInterception)scope.ServiceProvider.GetRequiredService<INavigationInterception>();
if (client.Connected)
@ -102,6 +95,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
// Initialize per - circuit data that services need
(circuitHost.Services.GetRequiredService<ICircuitAccessor>() as DefaultCircuitAccessor).Circuit = circuitHost.Circuit;
circuitHost.SetCircuitUser(user);
return circuitHost;
}

View File

@ -97,7 +97,8 @@ namespace Microsoft.AspNetCore.Components.Server
Context.GetHttpContext(),
circuitClient,
uriAbsolute,
baseUriAbsolute);
baseUriAbsolute,
Context.User);
circuitHost.UnhandledException += CircuitHost_UnhandledException;
@ -125,6 +126,7 @@ namespace Microsoft.AspNetCore.Components.Server
CircuitHost = circuitHost;
circuitHost.InitializeCircuitAfterPrerender(CircuitHost_UnhandledException);
circuitHost.SetCircuitUser(Context.User);
circuitHost.SendPendingBatches();
return true;
}

View File

@ -3,6 +3,7 @@
using System;
using System.IO;
using System.Security.Claims;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
@ -190,7 +191,7 @@ namespace Microsoft.AspNetCore.Components.Server.Tests.Circuits
_circuitIdFactory = circuitIdFactory ?? (() => Guid.NewGuid().ToString());
}
public override CircuitHost CreateCircuitHost(HttpContext httpContext, CircuitClientProxy client, string uriAbsolute, string baseUriAbsolute)
public override CircuitHost CreateCircuitHost(HttpContext httpContext, CircuitClientProxy client, string uriAbsolute, string baseUriAbsolute, ClaimsPrincipal user)
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddScoped<IUriHelper>(_ =>
@ -209,7 +210,7 @@ namespace Microsoft.AspNetCore.Components.Server.Tests.Circuits
public Mock<IServiceScope> MockServiceScope { get; }
= new Mock<IServiceScope>();
public override CircuitHost CreateCircuitHost(HttpContext httpContext, CircuitClientProxy client, string uriAbsolute, string baseUriAbsolute)
public override CircuitHost CreateCircuitHost(HttpContext httpContext, CircuitClientProxy client, string uriAbsolute, string baseUriAbsolute, ClaimsPrincipal user)
{
return TestCircuitHost.Create(Guid.NewGuid().ToString(), MockServiceScope.Object);
}

View File

@ -2,12 +2,12 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using BasicTestApp;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
using Microsoft.AspNetCore.E2ETesting;
using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using System;
using System.Linq;
using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Components.E2ETest.Infrastructure
@ -49,5 +49,32 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Infrastructure
.Until(driver => (result = driver.FindElement(findBy)) != null);
return result;
}
protected void SignInAs(string usernameOrNull, string rolesOrNull, bool useSeparateTab = false)
{
const string authenticationPageUrl = "/Authentication";
var baseRelativeUri = usernameOrNull == null
? $"{authenticationPageUrl}?signout=true"
: $"{authenticationPageUrl}?username={usernameOrNull}&roles={rolesOrNull}";
if (useSeparateTab)
{
// Some tests need to change the authentication state without discarding the
// original page, but this adds several seconds of delay
var javascript = (IJavaScriptExecutor)Browser;
var originalWindow = Browser.CurrentWindowHandle;
javascript.ExecuteScript("window.open()");
Browser.SwitchTo().Window(Browser.WindowHandles.Last());
Navigate(baseRelativeUri);
WaitUntilExists(By.CssSelector("h1#authentication"));
javascript.ExecuteScript("window.close()");
Browser.SwitchTo().Window(originalWindow);
}
else
{
Navigate(baseRelativeUri);
WaitUntilExists(By.CssSelector("h1#authentication"));
}
}
}
}

View File

@ -5,6 +5,7 @@ using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using BasicTestApp;
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
using Microsoft.AspNetCore.E2ETesting;
@ -14,16 +15,15 @@ using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
{
public class PrerenderingTest : ServerTestBase<AspNetSiteServerFixture>
[Collection("auth")] // Because auth uses cookies, this can't run in parallel with other auth tests
public class PrerenderingTest : BasicTestAppTestBase
{
public PrerenderingTest(
BrowserFixture browserFixture,
AspNetSiteServerFixture serverFixture,
ToggleExecutionModeServerFixture<Program> serverFixture,
ITestOutputHelper output)
: base(browserFixture, serverFixture, output)
: base(browserFixture, serverFixture.WithServerExecution(), output)
{
_serverFixture.Environment = AspNetEnvironment.Development;
_serverFixture.BuildWebHostMethod = TestServer.Program.BuildWebHost;
}
[Fact]
@ -97,6 +97,24 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
Assert.Equal(expectedUri, response.Headers.Location);
}
[Theory]
[InlineData(null, null)]
[InlineData(null, "Bert")]
[InlineData("Bert", null)]
[InlineData("Bert", "Treb")]
public void CanAccessAuthenticationStateDuringStaticPrerendering(string initialUsername, string interactiveUsername)
{
// See that the authentication state is usable during the initial prerendering
SignInAs(initialUsername, null);
Navigate("/prerendered/prerendered-transition");
Browser.Equal($"Hello, {initialUsername ?? "anonymous"}!", () => Browser.FindElement(By.TagName("h1")).Text);
// See that during connection, we update to whatever the latest authentication state now is
SignInAs(interactiveUsername, null, useSeparateTab: true);
BeginInteractivity();
Browser.Equal($"Hello, {interactiveUsername ?? "anonymous"}!", () => Browser.FindElement(By.TagName("h1")).Text);
}
private void BeginInteractivity()
{
Browser.FindElement(By.Id("load-boot-script")).Click();

View File

@ -0,0 +1,65 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using BasicTestApp;
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
using Microsoft.AspNetCore.Components.E2ETest.Tests;
using Microsoft.AspNetCore.E2ETesting;
using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
{
public class ServerAuthTest : AuthTest
{
public ServerAuthTest(BrowserFixture browserFixture, ToggleExecutionModeServerFixture<Program> serverFixture, ITestOutputHelper output)
: base(browserFixture, serverFixture.WithServerExecution(), output)
{
}
[Theory]
[InlineData(null, null)]
[InlineData(null, "Someone")]
[InlineData("Someone", null)]
[InlineData("Someone", "Someone")]
public void UpdatesAuthenticationStateWhenReconnecting(
string usernameBefore, string usernameAfter)
{
// Establish state before disconnection
SignInAs(usernameBefore, usernameBefore == null ? null : "TestRole");
var appElement = MountAndNavigateToAuthTest(AuthorizeViewCases);
AssertState(usernameBefore);
// Change authentication state and force reconnection
SignInAs(usernameAfter, usernameAfter == null ? null : "TestRole", useSeparateTab: true);
PerformReconnection();
AssertState(usernameAfter);
void AssertState(string username)
{
if (username == null)
{
Browser.Equal("You're not authorized, anonymous", () =>
appElement.FindElement(By.CssSelector("#authorize-role .not-authorized")).Text);
}
else
{
Browser.Equal($"Welcome, {username}!", () =>
appElement.FindElement(By.CssSelector("#authorize-role .authorized")).Text);
}
}
}
private void PerformReconnection()
{
((IJavaScriptExecutor)Browser).ExecuteScript("Blazor._internal.forceCloseConnection()");
// Wait until the reconnection dialog has been shown but is now hidden
new WebDriverWait(Browser, TimeSpan.FromSeconds(10))
.Until(driver => driver.FindElement(By.Id("components-reconnect-modal"))?.GetCssValue("display") == "none");
}
}
}

View File

@ -83,12 +83,4 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
{
}
}
public class ServerAuthTest : AuthTest
{
public ServerAuthTest(BrowserFixture browserFixture, ToggleExecutionModeServerFixture<Program> serverFixture, ITestOutputHelper output)
: base(browserFixture, serverFixture.WithServerExecution(), output)
{
}
}
}

View File

@ -11,15 +11,16 @@ using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Components.E2ETest.Tests
{
[Collection("auth")] // Because auth uses cookies, this can't run in parallel with other auth tests
public class AuthTest : BasicTestAppTestBase
{
// These strings correspond to the links in BasicTestApp\AuthTest\Links.razor
const string CascadingAuthenticationStateLink = "Cascading authentication state";
const string AuthorizeViewCases = "AuthorizeView cases";
const string PageAllowingAnonymous = "Page allowing anonymous";
const string PageRequiringAuthorization = "Page requiring any authentication";
const string PageRequiringPolicy = "Page requiring policy";
const string PageRequiringRole = "Page requiring role";
protected const string CascadingAuthenticationStateLink = "Cascading authentication state";
protected const string AuthorizeViewCases = "AuthorizeView cases";
protected const string PageAllowingAnonymous = "Page allowing anonymous";
protected const string PageRequiringAuthorization = "Page requiring any authentication";
protected const string PageRequiringPolicy = "Page requiring policy";
protected const string PageRequiringRole = "Page requiring role";
public AuthTest(
BrowserFixture browserFixture,
@ -184,7 +185,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
appElement.FindElement(By.CssSelector("#auth-failure")).Text);
}
IWebElement MountAndNavigateToAuthTest(string authLinkText)
protected IWebElement MountAndNavigateToAuthTest(string authLinkText)
{
Navigate(ServerPathBase);
var appElement = MountTestComponent<BasicTestApp.AuthTest.AuthRouter>();
@ -192,15 +193,5 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
appElement.FindElement(By.LinkText(authLinkText)).Click();
return appElement;
}
void SignInAs(string usernameOrNull, string rolesOrNull)
{
const string authenticationPageUrl = "/Authentication";
var baseRelativeUri = usernameOrNull == null
? $"{authenticationPageUrl}?signout=true"
: $"{authenticationPageUrl}?username={usernameOrNull}&roles={rolesOrNull}";
Navigate(baseRelativeUri);
WaitUntilExists(By.CssSelector("h1#authentication"));
}
}
}

View File

@ -3,7 +3,10 @@
@inject IComponentContext ComponentContext
<CascadingAuthenticationState>
<h1>Hello</h1>
<AuthorizeView>
<Authorized><h1>Hello, @context.User.Identity.Name!</h1></Authorized>
<NotAuthorized><h1>Hello, anonymous!</h1></NotAuthorized>
</AuthorizeView>
<p>
Current state: