Components auth step 2 (#10293)
* CR feedback left over from #10227 * Begin adding E2E test case * Add cookie auth and test login page * Make E2E auth component work client-side too * Restructure auth E2E tests around a router so there can easily be multiple such test components * Add E2E test case for AuthorizeView * Prepare for E2E test implementations * Fix ToBaseRelativePath handling of hashes ... otherwise E2E test will fail, because we're using the hash to control server-or-client execution * Decouple E2E execution mode from hosting mode * Actual E2E tests for cascading authentication state * Actual E2E tests for AuthorizeView (in "no authentication rule" mode) * Fix inconsistent namespace * CR: Manual ref assembly definitions for AuthorizeView/CascadingAuthenticationState
This commit is contained in:
parent
5502c2080e
commit
147880f796
|
|
@ -4,6 +4,8 @@ T:Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrame
|
|||
T:Microsoft.AspNetCore.Mvc.ApplicationModels.PageParameterModel
|
||||
T:Microsoft.AspNetCore.Mvc.ApplicationModels.PagePropertyModel
|
||||
# Manually implemented - https://github.com/aspnet/AspNetCore/issues/8825
|
||||
T:Microsoft.AspNetCore.Components.AuthorizeView
|
||||
T:Microsoft.AspNetCore.Components.CascadingAuthenticationState
|
||||
T:Microsoft.AspNetCore.Components.CascadingValue`1
|
||||
T:Microsoft.AspNetCore.Components.Forms.DataAnnotationsValidator
|
||||
T:Microsoft.AspNetCore.Components.Forms.EditForm
|
||||
|
|
|
|||
|
|
@ -29,6 +29,9 @@ namespace Microsoft.AspNetCore.Blazor.Services.Test
|
|||
[InlineData("scheme://host/path/", "scheme://host/path/", "")]
|
||||
[InlineData("scheme://host/path/", "scheme://host/path/more", "more")]
|
||||
[InlineData("scheme://host/path/", "scheme://host/path", "")]
|
||||
[InlineData("scheme://host/path/", "scheme://host/path#hash", "#hash")]
|
||||
[InlineData("scheme://host/path/", "scheme://host/path/#hash", "#hash")]
|
||||
[InlineData("scheme://host/path/", "scheme://host/path/more#hash", "more#hash")]
|
||||
public void ComputesCorrectValidBaseRelativePaths(string baseUri, string absoluteUri, string expectedResult)
|
||||
{
|
||||
var actualResult = _uriHelper.ToBaseRelativePath(baseUri, absoluteUri);
|
||||
|
|
|
|||
|
|
@ -49,6 +49,32 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
// Built-in components: https://github.com/aspnet/AspNetCore/issues/8825
|
||||
namespace Microsoft.AspNetCore.Components
|
||||
{
|
||||
public partial class AuthorizeView : Microsoft.AspNetCore.Components.ComponentBase
|
||||
{
|
||||
public AuthorizeView() { }
|
||||
[Microsoft.AspNetCore.Components.ParameterAttribute]
|
||||
public Microsoft.AspNetCore.Components.RenderFragment<Microsoft.AspNetCore.Components.AuthenticationState> Authorized { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; } }
|
||||
[Microsoft.AspNetCore.Components.ParameterAttribute]
|
||||
public Microsoft.AspNetCore.Components.RenderFragment Authorizing { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; } }
|
||||
[Microsoft.AspNetCore.Components.ParameterAttribute]
|
||||
public Microsoft.AspNetCore.Components.RenderFragment<Microsoft.AspNetCore.Components.AuthenticationState> ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; } }
|
||||
[Microsoft.AspNetCore.Components.ParameterAttribute]
|
||||
public Microsoft.AspNetCore.Components.RenderFragment NotAuthorized { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; } }
|
||||
protected override void BuildRenderTree(Microsoft.AspNetCore.Components.RenderTree.RenderTreeBuilder builder) { }
|
||||
[System.Diagnostics.DebuggerStepThroughAttribute]
|
||||
protected override System.Threading.Tasks.Task OnParametersSetAsync() { throw null; }
|
||||
}
|
||||
|
||||
public partial class CascadingAuthenticationState : Microsoft.AspNetCore.Components.ComponentBase, System.IDisposable
|
||||
{
|
||||
public CascadingAuthenticationState() { }
|
||||
[Microsoft.AspNetCore.Components.ParameterAttribute]
|
||||
public Microsoft.AspNetCore.Components.RenderFragment ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; } }
|
||||
protected override void BuildRenderTree(Microsoft.AspNetCore.Components.RenderTree.RenderTreeBuilder builder) { }
|
||||
protected override void OnInit() { }
|
||||
void System.IDisposable.Dispose() { }
|
||||
}
|
||||
|
||||
public partial class CascadingValue<T> : Microsoft.AspNetCore.Components.IComponent
|
||||
{
|
||||
public CascadingValue() { }
|
||||
|
|
|
|||
|
|
@ -16,21 +16,6 @@ namespace Microsoft.AspNetCore.Components
|
|||
public abstract System.Threading.Tasks.Task<Microsoft.AspNetCore.Components.AuthenticationState> GetAuthenticationStateAsync();
|
||||
protected void NotifyAuthenticationStateChanged(System.Threading.Tasks.Task<Microsoft.AspNetCore.Components.AuthenticationState> task) { }
|
||||
}
|
||||
public partial class AuthorizeView : Microsoft.AspNetCore.Components.ComponentBase
|
||||
{
|
||||
public AuthorizeView() { }
|
||||
[Microsoft.AspNetCore.Components.ParameterAttribute]
|
||||
public Microsoft.AspNetCore.Components.RenderFragment<Microsoft.AspNetCore.Components.AuthenticationState> Authorized { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
[Microsoft.AspNetCore.Components.ParameterAttribute]
|
||||
public Microsoft.AspNetCore.Components.RenderFragment Authorizing { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
[Microsoft.AspNetCore.Components.ParameterAttribute]
|
||||
public Microsoft.AspNetCore.Components.RenderFragment<Microsoft.AspNetCore.Components.AuthenticationState> ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
[Microsoft.AspNetCore.Components.ParameterAttribute]
|
||||
public Microsoft.AspNetCore.Components.RenderFragment NotAuthorized { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
protected override void BuildRenderTree(Microsoft.AspNetCore.Components.RenderTree.RenderTreeBuilder builder) { }
|
||||
[System.Diagnostics.DebuggerStepThroughAttribute]
|
||||
protected override System.Threading.Tasks.Task OnParametersSetAsync() { throw null; }
|
||||
}
|
||||
[Microsoft.AspNetCore.Components.BindElementAttribute("select", null, "value", "onchange")]
|
||||
[Microsoft.AspNetCore.Components.BindElementAttribute("textarea", null, "value", "onchange")]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("checkbox", null, "checked", "onchange")]
|
||||
|
|
@ -85,15 +70,6 @@ namespace Microsoft.AspNetCore.Components
|
|||
public static System.Action<Microsoft.AspNetCore.Components.UIEventArgs> SetValueHandler(System.Action<string> setter, string existingValue) { throw null; }
|
||||
public static System.Action<Microsoft.AspNetCore.Components.UIEventArgs> SetValueHandler<T>(System.Action<T> setter, T existingValue) { throw null; }
|
||||
}
|
||||
public partial class CascadingAuthenticationState : Microsoft.AspNetCore.Components.ComponentBase, System.IDisposable
|
||||
{
|
||||
public CascadingAuthenticationState() { }
|
||||
[Microsoft.AspNetCore.Components.ParameterAttribute]
|
||||
public Microsoft.AspNetCore.Components.RenderFragment ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
protected override void BuildRenderTree(Microsoft.AspNetCore.Components.RenderTree.RenderTreeBuilder builder) { }
|
||||
protected override void OnInit() { }
|
||||
void System.IDisposable.Dispose() { }
|
||||
}
|
||||
[System.AttributeUsageAttribute(System.AttributeTargets.Property, AllowMultiple=false, Inherited=false)]
|
||||
public sealed partial class CascadingParameterAttribute : System.Attribute
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,11 +11,6 @@ namespace Microsoft.AspNetCore.Components
|
|||
/// </summary>
|
||||
public class AuthenticationState
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a <see cref="ClaimsPrincipal"/> that describes the current user.
|
||||
/// </summary>
|
||||
public ClaimsPrincipal User { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs an instance of <see cref="AuthenticationState"/>.
|
||||
/// </summary>
|
||||
|
|
@ -24,5 +19,10 @@ namespace Microsoft.AspNetCore.Components
|
|||
{
|
||||
User = user ?? throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="ClaimsPrincipal"/> that describes the current user.
|
||||
/// </summary>
|
||||
public ClaimsPrincipal User { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,19 +12,16 @@ namespace Microsoft.AspNetCore.Components
|
|||
public abstract class AuthenticationStateProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets an <see cref="AuthenticationState"/> instance that describes
|
||||
/// the current user.
|
||||
/// Asynchronously gets an <see cref="AuthenticationState"/> that describes the current user.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="AuthenticationState"/> instance that describes the current user.</returns>
|
||||
/// <returns>A task that, when resolved, gives an <see cref="AuthenticationState"/> instance that describes the current user.</returns>
|
||||
public abstract Task<AuthenticationState> GetAuthenticationStateAsync();
|
||||
|
||||
/// <summary>
|
||||
/// An event that provides notification when the <see cref="AuthenticationState"/>
|
||||
/// has changed. For example, this event may be raised if a user logs in or out.
|
||||
/// </summary>
|
||||
#pragma warning disable 0067 // "Never used" (it's only raised by subclasses)
|
||||
public event AuthenticationStateChangedHandler AuthenticationStateChanged;
|
||||
#pragma warning restore 0067
|
||||
|
||||
/// <summary>
|
||||
/// Raises the <see cref="AuthenticationStateChanged"/> event.
|
||||
|
|
|
|||
|
|
@ -154,7 +154,10 @@ namespace Microsoft.AspNetCore.Components
|
|||
// baseUri ends with a slash), and from that we return "something"
|
||||
return locationAbsolute.Substring(baseUri.Length);
|
||||
}
|
||||
else if ($"{locationAbsolute}/".Equals(baseUri, StringComparison.Ordinal))
|
||||
|
||||
var hashIndex = locationAbsolute.IndexOf('#');
|
||||
var locationAbsoluteNoHash = hashIndex < 0 ? locationAbsolute : locationAbsolute.Substring(0, hashIndex);
|
||||
if ($"{locationAbsoluteNoHash}/".Equals(baseUri, StringComparison.Ordinal))
|
||||
{
|
||||
// Special case: for the base URI "/something/", if you're at
|
||||
// "/something" then treat it as if you were at "/something/" (i.e.,
|
||||
|
|
@ -162,7 +165,7 @@ namespace Microsoft.AspNetCore.Components
|
|||
// whether the server would return the same page whether or not the
|
||||
// slash is present, but ASP.NET Core at least does by default when
|
||||
// using PathBase.
|
||||
return string.Empty;
|
||||
return locationAbsolute.Substring(baseUri.Length - 1);
|
||||
}
|
||||
|
||||
var message = $"The URI '{locationAbsolute}' is not contained by the base URI '{baseUri}'.";
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Infrastructure
|
|||
public class BasicTestAppTestBase : ServerTestBase<ToggleExecutionModeServerFixture<Program>>
|
||||
{
|
||||
public string ServerPathBase
|
||||
=> "/subdir" + (_serverFixture.UsingAspNetHost ? "#server" : "");
|
||||
=> "/subdir" + (_serverFixture.ExecutionMode == ExecutionMode.Server ? "#server" : "");
|
||||
|
||||
public BasicTestAppTestBase(
|
||||
BrowserFixture browserFixture,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures
|
|||
: ServerFixture
|
||||
{
|
||||
public string PathBase { get; set; }
|
||||
public bool UsingAspNetHost { get; private set; }
|
||||
|
||||
public ExecutionMode ExecutionMode { get; set; } = ExecutionMode.Client;
|
||||
|
||||
private AspNetSiteServerFixture.BuildWebHost _buildWebHostMethod;
|
||||
private IDisposable _serverToDispose;
|
||||
|
|
@ -18,7 +19,6 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures
|
|||
{
|
||||
_buildWebHostMethod = buildWebHostMethod
|
||||
?? throw new ArgumentNullException(nameof(buildWebHostMethod));
|
||||
UsingAspNetHost = true;
|
||||
}
|
||||
|
||||
protected override string StartAndGetRootUri()
|
||||
|
|
@ -46,4 +46,6 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures
|
|||
_serverToDispose?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public enum ExecutionMode { Client, Server }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ using OpenQA.Selenium;
|
|||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.E2ETests.ServerExecutionTests
|
||||
namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||
{
|
||||
public class PrerenderingTest : ServerTestBase<AspNetSiteServerFixture>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
|||
public static ToggleExecutionModeServerFixture<T> WithServerExecution<T>(this ToggleExecutionModeServerFixture<T> serverFixture)
|
||||
{
|
||||
serverFixture.UseAspNetHost(TestServer.Program.BuildWebHost);
|
||||
serverFixture.ExecutionMode = ExecutionMode.Server;
|
||||
return serverFixture;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,4 +81,12 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
|||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class ServerAuthTest : AuthTest
|
||||
{
|
||||
public ServerAuthTest(BrowserFixture browserFixture, ToggleExecutionModeServerFixture<Program> serverFixture, ITestOutputHelper output)
|
||||
: base(browserFixture, serverFixture.WithServerExecution(), output)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,92 @@
|
|||
// 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 BasicTestApp;
|
||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
|
||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
||||
using Microsoft.AspNetCore.E2ETesting;
|
||||
using OpenQA.Selenium;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.E2ETest.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";
|
||||
|
||||
public AuthTest(
|
||||
BrowserFixture browserFixture,
|
||||
ToggleExecutionModeServerFixture<Program> serverFixture,
|
||||
ITestOutputHelper output)
|
||||
: base(browserFixture, serverFixture, output)
|
||||
{
|
||||
// Normally, the E2E tests use the Blazor dev server if they are testing
|
||||
// client-side execution. But for the auth tests, we always have to run
|
||||
// in "hosted on ASP.NET Core" mode, because we get the auth state from it.
|
||||
serverFixture.UseAspNetHost(TestServer.Program.BuildWebHost);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CascadingAuthenticationState_Unauthenticated()
|
||||
{
|
||||
SignInAs(null);
|
||||
|
||||
var appElement = MountAndNavigateToAuthTest(CascadingAuthenticationStateLink);
|
||||
|
||||
Browser.Equal("False", () => appElement.FindElement(By.Id("identity-authenticated")).Text);
|
||||
Browser.Equal(string.Empty, () => appElement.FindElement(By.Id("identity-name")).Text);
|
||||
Browser.Equal("(none)", () => appElement.FindElement(By.Id("test-claim")).Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CascadingAuthenticationState_Authenticated()
|
||||
{
|
||||
SignInAs("someone cool");
|
||||
|
||||
var appElement = MountAndNavigateToAuthTest(CascadingAuthenticationStateLink);
|
||||
|
||||
Browser.Equal("True", () => appElement.FindElement(By.Id("identity-authenticated")).Text);
|
||||
Browser.Equal("someone cool", () => appElement.FindElement(By.Id("identity-name")).Text);
|
||||
Browser.Equal("Test claim value", () => appElement.FindElement(By.Id("test-claim")).Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AuthorizeViewCases_NoAuthorizationRule_Unauthenticated()
|
||||
{
|
||||
SignInAs(null);
|
||||
MountAndNavigateToAuthTest(AuthorizeViewCases);
|
||||
WaitUntilExists(By.CssSelector("#no-authorization-rule .not-authorized"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AuthorizeViewCases_NoAuthorizationRule_Authenticated()
|
||||
{
|
||||
SignInAs("Some User");
|
||||
var appElement = MountAndNavigateToAuthTest(AuthorizeViewCases);
|
||||
Browser.Equal("Welcome, Some User!", () =>
|
||||
appElement.FindElement(By.CssSelector("#no-authorization-rule .authorized")).Text);
|
||||
}
|
||||
|
||||
IWebElement MountAndNavigateToAuthTest(string authLinkText)
|
||||
{
|
||||
Navigate(ServerPathBase);
|
||||
var appElement = MountTestComponent<BasicTestApp.AuthTest.AuthRouter>();
|
||||
WaitUntilExists(By.Id("auth-links"));
|
||||
appElement.FindElement(By.LinkText(authLinkText)).Click();
|
||||
return appElement;
|
||||
}
|
||||
|
||||
void SignInAs(string usernameOrNull)
|
||||
{
|
||||
const string authenticationPageUrl = "/Authentication";
|
||||
var baseRelativeUri = usernameOrNull == null
|
||||
? $"{authenticationPageUrl}?signout=true"
|
||||
: $"{authenticationPageUrl}?username={usernameOrNull}";
|
||||
Navigate(baseRelativeUri);
|
||||
WaitUntilExists(By.CssSelector("h1#authentication"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
protected override void InitializeAsyncCore()
|
||||
{
|
||||
// On WebAssembly, page reloads are expensive so skip if possible
|
||||
Navigate(ServerPathBase, noReload: !_serverFixture.UsingAspNetHost);
|
||||
Navigate(ServerPathBase, noReload: _serverFixture.ExecutionMode == ExecutionMode.Client);
|
||||
MountTestComponent<BindCasesComponent>();
|
||||
WaitUntilExists(By.Id("bind-cases"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
|
||||
protected override void InitializeAsyncCore()
|
||||
{
|
||||
Navigate(ServerPathBase, noReload: !_serverFixture.UsingAspNetHost);
|
||||
Navigate(ServerPathBase, noReload: _serverFixture.ExecutionMode == ExecutionMode.Client);
|
||||
MountTestComponent<BasicTestApp.CascadingValueTest.CascadingValueSupplier>();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
|
||||
protected override void InitializeAsyncCore()
|
||||
{
|
||||
Navigate(ServerPathBase, noReload: !_serverFixture.UsingAspNetHost);
|
||||
Navigate(ServerPathBase, noReload: _serverFixture.ExecutionMode == ExecutionMode.Client);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
|
||||
protected override void InitializeAsyncCore()
|
||||
{
|
||||
Navigate(ServerPathBase, noReload: !_serverFixture.UsingAspNetHost);
|
||||
Navigate(ServerPathBase, noReload: _serverFixture.ExecutionMode == ExecutionMode.Client);
|
||||
MountTestComponent<EventBubblingComponent>();
|
||||
WaitUntilExists(By.Id("event-bubbling"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
protected override void InitializeAsyncCore()
|
||||
{
|
||||
// On WebAssembly, page reloads are expensive so skip if possible
|
||||
Navigate(ServerPathBase, noReload: !_serverFixture.UsingAspNetHost);
|
||||
Navigate(ServerPathBase, noReload: _serverFixture.ExecutionMode == ExecutionMode.Client);
|
||||
MountTestComponent<BasicTestApp.EventCallbackTest.EventCallbackCases>();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
protected override void InitializeAsyncCore()
|
||||
{
|
||||
// On WebAssembly, page reloads are expensive so skip if possible
|
||||
Navigate(ServerPathBase, noReload: !_serverFixture.UsingAspNetHost);
|
||||
Navigate(ServerPathBase, noReload: _serverFixture.ExecutionMode == ExecutionMode.Client);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
|
||||
// Include the sync assertions only when running under WebAssembly
|
||||
var expectedValues = expectedAsyncValues;
|
||||
if (!_serverFixture.UsingAspNetHost)
|
||||
if (_serverFixture.ExecutionMode == ExecutionMode.Client)
|
||||
{
|
||||
foreach (var kvp in expectedSyncValues)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
protected override void InitializeAsyncCore()
|
||||
{
|
||||
// On WebAssembly, page reloads are expensive so skip if possible
|
||||
Navigate(ServerPathBase, noReload: !_serverFixture.UsingAspNetHost);
|
||||
Navigate(ServerPathBase, noReload: _serverFixture.ExecutionMode == ExecutionMode.Client);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
@page "/AuthHome"
|
||||
|
||||
Select an auth test below.
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@inject IUriHelper UriHelper
|
||||
|
||||
@*
|
||||
This router is independent of any other router that may exist within the same project.
|
||||
It exists so that (1) we can easily have multiple test cases that depend on the
|
||||
CascadingAuthenticationState, and (2) we can test the integration between the router
|
||||
and @page authorization rules.
|
||||
*@
|
||||
|
||||
<CascadingAuthenticationState>
|
||||
<Router AppAssembly=typeof(BasicTestApp.Program).Assembly />
|
||||
</CascadingAuthenticationState>
|
||||
|
||||
<hr />
|
||||
<Links />
|
||||
|
||||
@functions {
|
||||
protected override void OnInit()
|
||||
{
|
||||
// Start at AuthHome, not at any other component in the same app that happens to
|
||||
// register itself for the route ""
|
||||
var absoluteUriPath = new Uri(UriHelper.GetAbsoluteUri()).GetLeftPart(UriPartial.Path);
|
||||
var relativeUri = UriHelper.ToBaseRelativePath(UriHelper.GetBaseUri(), absoluteUriPath);
|
||||
if (relativeUri == string.Empty)
|
||||
{
|
||||
UriHelper.NavigateTo("AuthHome");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
@page "/AuthorizeViewCases"
|
||||
|
||||
<div id="no-authorization-rule">
|
||||
<h3>Scenario: No authorization rule</h3>
|
||||
|
||||
<AuthorizeView>
|
||||
<Authorizing>
|
||||
<p class="authorizing">Authorizing...</p>
|
||||
</Authorizing>
|
||||
<Authorized>
|
||||
<p class="authorized">Welcome, @context.User.Identity.Name!</p>
|
||||
</Authorized>
|
||||
<NotAuthorized>
|
||||
<p class="not-authorized">You're not logged in.</p>
|
||||
</NotAuthorized>
|
||||
</AuthorizeView>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
@page "/CascadingAuthenticationStateConsumer"
|
||||
@using System.Security.Claims
|
||||
|
||||
<h1>Cascading authentication state</h1>
|
||||
|
||||
@if (user == null)
|
||||
{
|
||||
<span id="auth-state-pending">Requesting authentication state...</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p>
|
||||
Authenticated:
|
||||
<strong id="identity-authenticated">@user.Identity.IsAuthenticated</strong>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Name:
|
||||
<strong id="identity-name">@user.Identity.Name</strong>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Test claim:
|
||||
@if (user.HasClaim(TestClaimPredicate) == true)
|
||||
{
|
||||
<strong id="test-claim">@user.Claims.Single(c => TestClaimPredicate(c)).Value</strong>
|
||||
}
|
||||
else
|
||||
{
|
||||
<strong id="test-claim">(none)</strong>
|
||||
}
|
||||
</p>
|
||||
}
|
||||
|
||||
@functions
|
||||
{
|
||||
static Predicate<Claim> TestClaimPredicate = c => c.Type == "test-claim";
|
||||
|
||||
ClaimsPrincipal user;
|
||||
|
||||
[CascadingParameter] Task<AuthenticationState> AuthenticationStateTask { get; set; }
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
user = (await AuthenticationStateTask).User;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
// 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.Collections.Generic;
|
||||
|
||||
namespace BasicTestApp.AuthTest
|
||||
{
|
||||
// DTO shared between server and client
|
||||
public class ClientSideAuthenticationStateData
|
||||
{
|
||||
public bool IsAuthenticated { get; set; }
|
||||
|
||||
public string UserName { get; set; }
|
||||
|
||||
public Dictionary<string, string> ExposedClaims { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
@using Microsoft.AspNetCore.Components.Routing
|
||||
<ul id="auth-links">
|
||||
<li><NavLink href="AuthHome" Match=NavLinkMatch.All>Home</NavLink></li>
|
||||
<li><NavLink href="CascadingAuthenticationStateConsumer">Cascading authentication state</NavLink></li>
|
||||
<li><NavLink href="AuthorizeViewCases">AuthorizeView cases</NavLink></li>
|
||||
</ul>
|
||||
|
||||
<p>To change the underlying authentication state, <a target="_blank" href="/Authentication">go here</a>.</p>
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
// 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 System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace BasicTestApp.AuthTest
|
||||
{
|
||||
// This is intended to be similar to the authentication stateprovider included by default
|
||||
// with the client-side Blazor "Hosted in ASP.NET Core" template
|
||||
public class ServerAuthenticationStateProvider : AuthenticationStateProvider
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
public ServerAuthenticationStateProvider(HttpClient httpClient)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
}
|
||||
|
||||
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
|
||||
{
|
||||
var uri = new Uri(_httpClient.BaseAddress, "/api/User");
|
||||
var data = await _httpClient.GetJsonAsync<ClientSideAuthenticationStateData>(uri.AbsoluteUri);
|
||||
ClaimsIdentity identity;
|
||||
if (data.IsAuthenticated)
|
||||
{
|
||||
var claims = new[] { new Claim(ClaimTypes.Name, data.UserName) }
|
||||
.Concat(data.ExposedClaims.Select(c => new Claim(c.Key, c.Value)));
|
||||
identity = new ClaimsIdentity(claims, "Server authentication");
|
||||
}
|
||||
else
|
||||
{
|
||||
identity = new ClaimsIdentity();
|
||||
}
|
||||
|
||||
return new AuthenticationState(new ClaimsPrincipal(identity));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -54,6 +54,8 @@
|
|||
<option value="BasicTestApp.KeyCasesComponent">Key cases</option>
|
||||
<option value="BasicTestApp.ReorderingFocusComponent">Reordering focus retention</option>
|
||||
<option value="BasicTestApp.RouterTest.UriHelperComponent">UriHelper Test</option>
|
||||
<option value="BasicTestApp.AuthTest.CascadingAuthenticationStateParent">Cascading authentication state</option>
|
||||
<option value="BasicTestApp.AuthTest.AuthRouter">Auth cases</option>
|
||||
</select>
|
||||
|
||||
@if (SelectedComponentType != null)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using BasicTestApp.AuthTest;
|
||||
using Microsoft.AspNetCore.Blazor.Http;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
|
|
@ -12,6 +14,7 @@ namespace BasicTestApp
|
|||
{
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<AuthenticationStateProvider, ServerAuthenticationStateProvider>();
|
||||
}
|
||||
|
||||
public void Configure(IComponentsApplicationBuilder app)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.AspNetCore" />
|
||||
<Reference Include="Microsoft.AspNetCore.Authentication.Cookies" />
|
||||
<Reference Include="Microsoft.AspNetCore.Blazor.Server" />
|
||||
<Reference Include="Microsoft.AspNetCore.Cors" />
|
||||
<Reference Include="Microsoft.AspNetCore.Mvc" />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
using System.Linq;
|
||||
using BasicTestApp.AuthTest;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Components.TestServer.Controllers
|
||||
{
|
||||
[Route("api/[controller]")]
|
||||
public class UserController : Controller
|
||||
{
|
||||
// GET api/user
|
||||
[HttpGet]
|
||||
public ClientSideAuthenticationStateData Get()
|
||||
{
|
||||
// Servers are not expected to expose everything from the server-side ClaimsPrincipal
|
||||
// to the client. It's up to the developer to choose what kind of authentication state
|
||||
// data is needed on the client so it can display suitable options in the UI.
|
||||
|
||||
return new ClientSideAuthenticationStateData
|
||||
{
|
||||
IsAuthenticated = User.Identity.IsAuthenticated,
|
||||
UserName = User.Identity.Name,
|
||||
ExposedClaims = User.Claims
|
||||
.Where(c => c.Type == "test-claim")
|
||||
.ToDictionary(c => c.Type, c => c.Value)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
@page
|
||||
@using Microsoft.AspNetCore.Authentication
|
||||
@using System.Security.Claims
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Authentication</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1 id="authentication">Authentication</h1>
|
||||
<p>
|
||||
This is a completely fake login mechanism for automated test purposes.
|
||||
It accepts any username, with no password.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Obviously you should not do this in real applications.</strong>
|
||||
See also: <a href="https://docs.microsoft.com/aspnet/core/security">documentation on configuring a real login system.</a>
|
||||
</p>
|
||||
|
||||
<fieldset>
|
||||
<h3>Sign in</h3>
|
||||
|
||||
@* Do not use method="get" for real login forms. This is just to simplify E2E tests. *@
|
||||
<form method="get">
|
||||
<p>
|
||||
User name:
|
||||
<input name="username" />
|
||||
</p>
|
||||
<p>
|
||||
<button type="submit">Submit</button>
|
||||
</p>
|
||||
</form>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<h3>Status</h3>
|
||||
<p>
|
||||
Authenticated: <strong>@User.Identity.IsAuthenticated</strong>
|
||||
Username: <strong>@User.Identity.Name</strong>
|
||||
</p>
|
||||
<a href="Authentication?signout=true">Sign out</a>
|
||||
</fieldset>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@functions {
|
||||
public async Task<IActionResult> OnGet()
|
||||
{
|
||||
if (Request.Query["signout"] == "true")
|
||||
{
|
||||
await HttpContext.SignOutAsync();
|
||||
return Redirect("Authentication");
|
||||
}
|
||||
|
||||
var username = Request.Query["username"];
|
||||
if (!string.IsNullOrEmpty(username))
|
||||
{
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
new Claim(ClaimTypes.Name, username),
|
||||
new Claim("test-claim", "Test claim value"),
|
||||
};
|
||||
|
||||
await HttpContext.SignInAsync(
|
||||
new ClaimsPrincipal(new ClaimsIdentity(claims, "FakeAuthenticationType")));
|
||||
|
||||
return Redirect("Authentication");
|
||||
}
|
||||
|
||||
return Page();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
using BasicTestApp;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Components.Server;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
|
|
@ -28,6 +29,7 @@ namespace TestServer
|
|||
options.AddPolicy("AllowAll", _ => { /* Controlled below */ });
|
||||
});
|
||||
services.AddServerSideBlazor();
|
||||
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
|
|
@ -50,6 +52,7 @@ namespace TestServer
|
|||
.AllowCredentials();
|
||||
});
|
||||
|
||||
app.UseAuthentication();
|
||||
|
||||
// Mount the server-side Blazor app on /subdir
|
||||
app.Map("/subdir", subdirApp =>
|
||||
|
|
@ -70,6 +73,7 @@ namespace TestServer
|
|||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapControllers();
|
||||
endpoints.MapRazorPages();
|
||||
});
|
||||
|
||||
// Separately, mount a prerendered server-side Blazor app on /prerendered
|
||||
|
|
|
|||
Loading…
Reference in New Issue