Components auth: basic services and components (#10227)
This commit is contained in:
parent
9f4aa98ee2
commit
1dbc203e19
|
|
@ -3,6 +3,34 @@
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components
|
namespace Microsoft.AspNetCore.Components
|
||||||
{
|
{
|
||||||
|
public partial class AuthenticationState
|
||||||
|
{
|
||||||
|
public AuthenticationState(System.Security.Claims.ClaimsPrincipal user) { }
|
||||||
|
public System.Security.Claims.ClaimsPrincipal User { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||||
|
}
|
||||||
|
public delegate void AuthenticationStateChangedHandler(System.Threading.Tasks.Task<Microsoft.AspNetCore.Components.AuthenticationState> task);
|
||||||
|
public abstract partial class AuthenticationStateProvider
|
||||||
|
{
|
||||||
|
protected AuthenticationStateProvider() { }
|
||||||
|
public event Microsoft.AspNetCore.Components.AuthenticationStateChangedHandler AuthenticationStateChanged { add { } remove { } }
|
||||||
|
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("select", null, "value", "onchange")]
|
||||||
[Microsoft.AspNetCore.Components.BindElementAttribute("textarea", null, "value", "onchange")]
|
[Microsoft.AspNetCore.Components.BindElementAttribute("textarea", null, "value", "onchange")]
|
||||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("checkbox", null, "checked", "onchange")]
|
[Microsoft.AspNetCore.Components.BindInputElementAttribute("checkbox", null, "checked", "onchange")]
|
||||||
|
|
@ -57,6 +85,15 @@ 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(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 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)]
|
[System.AttributeUsageAttribute(System.AttributeTargets.Property, AllowMultiple=false, Inherited=false)]
|
||||||
public sealed partial class CascadingParameterAttribute : System.Attribute
|
public sealed partial class CascadingParameterAttribute : System.Attribute
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
// 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.Security.Claims;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides information about the currently authenticated user, if any.
|
||||||
|
/// </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>
|
||||||
|
/// <param name="user">A <see cref="ClaimsPrincipal"/> representing the user.</param>
|
||||||
|
public AuthenticationState(ClaimsPrincipal user)
|
||||||
|
{
|
||||||
|
User = user ?? throw new ArgumentNullException(nameof(user));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
// 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.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides information about the authentication state of the current user.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class AuthenticationStateProvider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an <see cref="AuthenticationState"/> instance that describes
|
||||||
|
/// the current user.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>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.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="task">A <see cref="Task"/> that supplies the updated <see cref="AuthenticationState"/>.</param>
|
||||||
|
protected void NotifyAuthenticationStateChanged(Task<AuthenticationState> task)
|
||||||
|
{
|
||||||
|
if (task == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(task));
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthenticationStateChanged?.Invoke(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A handler for the <see cref="AuthenticationStateProvider.AuthenticationStateChanged"/> event.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="task">A <see cref="Task"/> that supplies the updated <see cref="AuthenticationState"/>.</param>
|
||||||
|
public delegate void AuthenticationStateChangedHandler(Task<AuthenticationState> task);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
@namespace Microsoft.AspNetCore.Components
|
||||||
|
|
||||||
|
@if (currentAuthenticationState == null)
|
||||||
|
{
|
||||||
|
@Authorizing
|
||||||
|
}
|
||||||
|
else if (IsAuthorized())
|
||||||
|
{
|
||||||
|
@((Authorized ?? ChildContent)?.Invoke(currentAuthenticationState))
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@NotAuthorized
|
||||||
|
}
|
||||||
|
|
||||||
|
@functions {
|
||||||
|
private AuthenticationState currentAuthenticationState;
|
||||||
|
|
||||||
|
[CascadingParameter] private Task<AuthenticationState> AuthenticationState { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The content that will be displayed if the user is authorized.
|
||||||
|
/// </summary>
|
||||||
|
[Parameter] public RenderFragment<AuthenticationState> ChildContent { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The content that will be displayed if the user is not authorized.
|
||||||
|
/// </summary>
|
||||||
|
[Parameter] public RenderFragment NotAuthorized { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The content that will be displayed if the user is authorized.
|
||||||
|
/// If you specify a value for this parameter, do not also specify a value for <see cref="ChildContent"/>.
|
||||||
|
/// </summary>
|
||||||
|
[Parameter] public RenderFragment<AuthenticationState> Authorized { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The content that will be displayed while asynchronous authorization is in progress.
|
||||||
|
/// </summary>
|
||||||
|
[Parameter] public RenderFragment Authorizing { get; private set; }
|
||||||
|
|
||||||
|
protected override async Task OnParametersSetAsync()
|
||||||
|
{
|
||||||
|
// We allow 'ChildContent' for convenience in basic cases, and 'Authorized' for symmetry
|
||||||
|
// with 'NotAuthorized' in other cases. Besides naming, they are equivalent. To avoid
|
||||||
|
// confusion, explicitly prevent the case where both are supplied.
|
||||||
|
if (ChildContent != null && Authorized != null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"When using {nameof(AuthorizeView)}, do not specify both '{nameof(Authorized)}' and '{nameof(ChildContent)}'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// First render in pending state
|
||||||
|
// If the task has already completed, this render will be skipped
|
||||||
|
currentAuthenticationState = null;
|
||||||
|
|
||||||
|
// Then render in completed state
|
||||||
|
currentAuthenticationState = await AuthenticationState;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsAuthorized()
|
||||||
|
{
|
||||||
|
// TODO: Support various authorization condition parameters, equivalent to those offered
|
||||||
|
// by the [Authorize] attribute, e.g., "Roles" and "Policy". This is on hold until we're
|
||||||
|
// able to reference the policy evaluator APIs from this package.
|
||||||
|
|
||||||
|
return currentAuthenticationState.User?.Identity?.IsAuthenticated == true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
@namespace Microsoft.AspNetCore.Components
|
||||||
|
@implements IDisposable
|
||||||
|
@inject AuthenticationStateProvider AuthenticationStateProvider
|
||||||
|
|
||||||
|
<CascadingValue T="Task<AuthenticationState>" Value="@_currentAuthenticationStateTask" ChildContent="@ChildContent" />
|
||||||
|
|
||||||
|
@functions {
|
||||||
|
private Task<AuthenticationState> _currentAuthenticationStateTask;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The content to which the authentication state should be provided.
|
||||||
|
/// </summary>
|
||||||
|
[Parameter] public RenderFragment ChildContent { get; private set; }
|
||||||
|
|
||||||
|
protected override void OnInit()
|
||||||
|
{
|
||||||
|
AuthenticationStateProvider.AuthenticationStateChanged += OnAuthenticationStateChanged;
|
||||||
|
|
||||||
|
_currentAuthenticationStateTask = AuthenticationStateProvider
|
||||||
|
.GetAuthenticationStateAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAuthenticationStateChanged(Task<AuthenticationState> newAuthStateTask)
|
||||||
|
{
|
||||||
|
Invoke(() =>
|
||||||
|
{
|
||||||
|
_currentAuthenticationStateTask = newAuthStateTask;
|
||||||
|
StateHasChanged();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void IDisposable.Dispose()
|
||||||
|
{
|
||||||
|
AuthenticationStateProvider.AuthenticationStateChanged -= OnAuthenticationStateChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -40,6 +40,7 @@ namespace Microsoft.AspNetCore.Components
|
||||||
=> type.IsPrimitive
|
=> type.IsPrimitive
|
||||||
|| type == typeof(string)
|
|| type == typeof(string)
|
||||||
|| type == typeof(DateTime)
|
|| type == typeof(DateTime)
|
||||||
|
|| type == typeof(Type)
|
||||||
|| type == typeof(decimal);
|
|| type == typeof(decimal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<IsShippingPackage>true</IsShippingPackage>
|
<IsShippingPackage>true</IsShippingPackage>
|
||||||
<IsAspNetCoreApp>true</IsAspNetCoreApp>
|
<IsAspNetCoreApp>true</IsAspNetCoreApp>
|
||||||
|
<RazorLangVersion>3.0</RazorLangVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,308 @@
|
||||||
|
// 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.Security.Claims;
|
||||||
|
using System.Security.Principal;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Components.RenderTree;
|
||||||
|
using Microsoft.AspNetCore.Components.Test.Helpers;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components
|
||||||
|
{
|
||||||
|
public class AuthorizeViewTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void RendersNothingIfNotAuthorized()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var renderer = new TestRenderer();
|
||||||
|
var rootComponent = WrapInAuthorizeView(
|
||||||
|
childContent:
|
||||||
|
context => builder => builder.AddContent(0, "This should not be rendered"));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
renderer.AssignRootComponentId(rootComponent);
|
||||||
|
rootComponent.TriggerRender();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var diff = renderer.Batches.Single().GetComponentDiffs<AuthorizeView>().Single();
|
||||||
|
Assert.Empty(diff.Edits);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RendersNotAuthorizedContentIfNotAuthorized()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var renderer = new TestRenderer();
|
||||||
|
var rootComponent = WrapInAuthorizeView(
|
||||||
|
childContent:
|
||||||
|
context => builder => builder.AddContent(0, "This should not be rendered"),
|
||||||
|
notAuthorizedContent:
|
||||||
|
builder => builder.AddContent(0, "You are not authorized"));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
renderer.AssignRootComponentId(rootComponent);
|
||||||
|
rootComponent.TriggerRender();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var diff = renderer.Batches.Single().GetComponentDiffs<AuthorizeView>().Single();
|
||||||
|
Assert.Collection(diff.Edits, edit =>
|
||||||
|
{
|
||||||
|
Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type);
|
||||||
|
AssertFrame.Text(
|
||||||
|
renderer.Batches.Single().ReferenceFrames[edit.ReferenceFrameIndex],
|
||||||
|
"You are not authorized");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RendersNothingIfAuthorizedButNoChildContentOrAuthorizedContentProvided()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var renderer = new TestRenderer();
|
||||||
|
var rootComponent = WrapInAuthorizeView();
|
||||||
|
rootComponent.AuthenticationState = CreateAuthenticationState("Nellie");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
renderer.AssignRootComponentId(rootComponent);
|
||||||
|
rootComponent.TriggerRender();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var diff = renderer.Batches.Single().GetComponentDiffs<AuthorizeView>().Single();
|
||||||
|
Assert.Empty(diff.Edits);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RendersChildContentIfAuthorized()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var renderer = new TestRenderer();
|
||||||
|
var rootComponent = WrapInAuthorizeView(
|
||||||
|
childContent: context => builder =>
|
||||||
|
builder.AddContent(0, $"You are authenticated as {context.User.Identity.Name}"));
|
||||||
|
rootComponent.AuthenticationState = CreateAuthenticationState("Nellie");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
renderer.AssignRootComponentId(rootComponent);
|
||||||
|
rootComponent.TriggerRender();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var diff = renderer.Batches.Single().GetComponentDiffs<AuthorizeView>().Single();
|
||||||
|
Assert.Collection(diff.Edits, edit =>
|
||||||
|
{
|
||||||
|
Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type);
|
||||||
|
AssertFrame.Text(
|
||||||
|
renderer.Batches.Single().ReferenceFrames[edit.ReferenceFrameIndex],
|
||||||
|
"You are authenticated as Nellie");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RendersAuthorizedContentIfAuthorized()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var renderer = new TestRenderer();
|
||||||
|
var rootComponent = WrapInAuthorizeView(
|
||||||
|
authorizedContent: context => builder =>
|
||||||
|
builder.AddContent(0, $"You are authenticated as {context.User.Identity.Name}"));
|
||||||
|
rootComponent.AuthenticationState = CreateAuthenticationState("Nellie");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
renderer.AssignRootComponentId(rootComponent);
|
||||||
|
rootComponent.TriggerRender();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var diff = renderer.Batches.Single().GetComponentDiffs<AuthorizeView>().Single();
|
||||||
|
Assert.Collection(diff.Edits, edit =>
|
||||||
|
{
|
||||||
|
Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type);
|
||||||
|
AssertFrame.Text(
|
||||||
|
renderer.Batches.Single().ReferenceFrames[edit.ReferenceFrameIndex],
|
||||||
|
"You are authenticated as Nellie");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RespondsToChangeInAuthorizationState()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var renderer = new TestRenderer();
|
||||||
|
var rootComponent = WrapInAuthorizeView(
|
||||||
|
childContent: context => builder =>
|
||||||
|
builder.AddContent(0, $"You are authenticated as {context.User.Identity.Name}"));
|
||||||
|
rootComponent.AuthenticationState = CreateAuthenticationState("Nellie");
|
||||||
|
|
||||||
|
// Render in initial state. From other tests, we know this renders
|
||||||
|
// a single batch with the correct output.
|
||||||
|
renderer.AssignRootComponentId(rootComponent);
|
||||||
|
rootComponent.TriggerRender();
|
||||||
|
var authorizeViewComponentId = renderer.Batches.Single()
|
||||||
|
.GetComponentFrames<AuthorizeView>().Single().ComponentId;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
rootComponent.AuthenticationState = CreateAuthenticationState("Ronaldo");
|
||||||
|
rootComponent.TriggerRender();
|
||||||
|
|
||||||
|
// Assert: It's only one new diff. We skip the intermediate "await" render state
|
||||||
|
// because the task was completed synchronously.
|
||||||
|
Assert.Equal(2, renderer.Batches.Count);
|
||||||
|
var batch = renderer.Batches.Last();
|
||||||
|
var diff = batch.DiffsByComponentId[authorizeViewComponentId].Single();
|
||||||
|
Assert.Collection(diff.Edits, edit =>
|
||||||
|
{
|
||||||
|
Assert.Equal(RenderTreeEditType.UpdateText, edit.Type);
|
||||||
|
AssertFrame.Text(
|
||||||
|
batch.ReferenceFrames[edit.ReferenceFrameIndex],
|
||||||
|
"You are authenticated as Ronaldo");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ThrowsIfBothChildContentAndAuthorizedContentProvided()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var renderer = new TestRenderer();
|
||||||
|
var rootComponent = WrapInAuthorizeView(
|
||||||
|
authorizedContent: context => builder => { },
|
||||||
|
childContent: context => builder => { });
|
||||||
|
|
||||||
|
// Act/Assert
|
||||||
|
renderer.AssignRootComponentId(rootComponent);
|
||||||
|
var ex = Assert.Throws<InvalidOperationException>(() =>
|
||||||
|
rootComponent.TriggerRender());
|
||||||
|
Assert.Equal("When using AuthorizeView, do not specify both 'Authorized' and 'ChildContent'.", ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RendersNothingUntilAuthorizationCompleted()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var renderer = new TestRenderer();
|
||||||
|
var rootComponent = WrapInAuthorizeView(
|
||||||
|
notAuthorizedContent: builder => builder.AddContent(0, "You are not authorized"));
|
||||||
|
var authTcs = new TaskCompletionSource<AuthenticationState>();
|
||||||
|
rootComponent.AuthenticationState = authTcs.Task;
|
||||||
|
|
||||||
|
// Act/Assert 1: Auth pending
|
||||||
|
renderer.AssignRootComponentId(rootComponent);
|
||||||
|
rootComponent.TriggerRender();
|
||||||
|
var batch1 = renderer.Batches.Single();
|
||||||
|
var authorizeViewComponentId = batch1.GetComponentFrames<AuthorizeView>().Single().ComponentId;
|
||||||
|
var diff1 = batch1.DiffsByComponentId[authorizeViewComponentId].Single();
|
||||||
|
Assert.Empty(diff1.Edits);
|
||||||
|
|
||||||
|
// Act/Assert 2: Auth process completes asynchronously
|
||||||
|
authTcs.SetResult(new AuthenticationState(new ClaimsPrincipal()));
|
||||||
|
Assert.Equal(2, renderer.Batches.Count);
|
||||||
|
var batch2 = renderer.Batches[1];
|
||||||
|
var diff2 = batch2.DiffsByComponentId[authorizeViewComponentId].Single();
|
||||||
|
Assert.Collection(diff2.Edits, edit =>
|
||||||
|
{
|
||||||
|
Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type);
|
||||||
|
AssertFrame.Text(
|
||||||
|
batch2.ReferenceFrames[edit.ReferenceFrameIndex],
|
||||||
|
"You are not authorized");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RendersAuthorizingContentUntilAuthorizationCompleted()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var renderer = new TestRenderer();
|
||||||
|
var rootComponent = WrapInAuthorizeView(
|
||||||
|
authorizingContent: builder => builder.AddContent(0, "Auth pending..."),
|
||||||
|
authorizedContent: context => builder => builder.AddContent(0, $"Hello, {context.User.Identity.Name}!"));
|
||||||
|
var authTcs = new TaskCompletionSource<AuthenticationState>();
|
||||||
|
rootComponent.AuthenticationState = authTcs.Task;
|
||||||
|
|
||||||
|
// Act/Assert 1: Auth pending
|
||||||
|
renderer.AssignRootComponentId(rootComponent);
|
||||||
|
rootComponent.TriggerRender();
|
||||||
|
var batch1 = renderer.Batches.Single();
|
||||||
|
var authorizeViewComponentId = batch1.GetComponentFrames<AuthorizeView>().Single().ComponentId;
|
||||||
|
var diff1 = batch1.DiffsByComponentId[authorizeViewComponentId].Single();
|
||||||
|
Assert.Collection(diff1.Edits, edit =>
|
||||||
|
{
|
||||||
|
Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type);
|
||||||
|
AssertFrame.Text(
|
||||||
|
batch1.ReferenceFrames[edit.ReferenceFrameIndex],
|
||||||
|
"Auth pending...");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act/Assert 2: Auth process completes asynchronously
|
||||||
|
authTcs.SetResult(CreateAuthenticationState("Monsieur").Result);
|
||||||
|
Assert.Equal(2, renderer.Batches.Count);
|
||||||
|
var batch2 = renderer.Batches[1];
|
||||||
|
var diff2 = batch2.DiffsByComponentId[authorizeViewComponentId].Single();
|
||||||
|
Assert.Collection(diff2.Edits,
|
||||||
|
edit =>
|
||||||
|
{
|
||||||
|
Assert.Equal(RenderTreeEditType.RemoveFrame, edit.Type);
|
||||||
|
Assert.Equal(0, edit.SiblingIndex);
|
||||||
|
},
|
||||||
|
edit =>
|
||||||
|
{
|
||||||
|
Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type);
|
||||||
|
Assert.Equal(0, edit.SiblingIndex);
|
||||||
|
AssertFrame.Text(
|
||||||
|
batch2.ReferenceFrames[edit.ReferenceFrameIndex],
|
||||||
|
"Hello, Monsieur!");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TestAuthStateProviderComponent WrapInAuthorizeView(
|
||||||
|
RenderFragment<AuthenticationState> childContent = null,
|
||||||
|
RenderFragment<AuthenticationState> authorizedContent = null,
|
||||||
|
RenderFragment notAuthorizedContent = null,
|
||||||
|
RenderFragment authorizingContent = null)
|
||||||
|
{
|
||||||
|
return new TestAuthStateProviderComponent(builder =>
|
||||||
|
{
|
||||||
|
builder.OpenComponent<AuthorizeView>(0);
|
||||||
|
builder.AddAttribute(1, nameof(AuthorizeView.ChildContent), childContent);
|
||||||
|
builder.AddAttribute(2, nameof(AuthorizeView.Authorized), authorizedContent);
|
||||||
|
builder.AddAttribute(3, nameof(AuthorizeView.NotAuthorized), notAuthorizedContent);
|
||||||
|
builder.AddAttribute(4, nameof(AuthorizeView.Authorizing), authorizingContent);
|
||||||
|
builder.CloseComponent();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestAuthStateProviderComponent : AutoRenderComponent
|
||||||
|
{
|
||||||
|
private readonly RenderFragment _childContent;
|
||||||
|
|
||||||
|
public Task<AuthenticationState> AuthenticationState { get; set; }
|
||||||
|
= Task.FromResult(new AuthenticationState(new ClaimsPrincipal()));
|
||||||
|
|
||||||
|
public TestAuthStateProviderComponent(RenderFragment childContent)
|
||||||
|
{
|
||||||
|
_childContent = childContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void BuildRenderTree(RenderTreeBuilder builder)
|
||||||
|
{
|
||||||
|
builder.OpenComponent<CascadingValue<Task<AuthenticationState>>>(0);
|
||||||
|
builder.AddAttribute(1, nameof(CascadingValue<Task<AuthenticationState>>.Value), AuthenticationState);
|
||||||
|
builder.AddAttribute(2, RenderTreeBuilder.ChildContent, _childContent);
|
||||||
|
builder.CloseComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Task<AuthenticationState> CreateAuthenticationState(string username)
|
||||||
|
=> Task.FromResult(new AuthenticationState(
|
||||||
|
new ClaimsPrincipal(new TestIdentity { Name = username })));
|
||||||
|
|
||||||
|
class TestIdentity : IIdentity
|
||||||
|
{
|
||||||
|
public string AuthenticationType => "Test";
|
||||||
|
|
||||||
|
public bool IsAuthenticated => true;
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,221 @@
|
||||||
|
// 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.Security.Claims;
|
||||||
|
using System.Security.Principal;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Components.RenderTree;
|
||||||
|
using Microsoft.AspNetCore.Components.Test.Helpers;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components
|
||||||
|
{
|
||||||
|
public class CascadingAuthenticationStateTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void RequiresRegisteredService()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var renderer = new TestRenderer();
|
||||||
|
var component = new AutoRenderFragmentComponent(builder =>
|
||||||
|
{
|
||||||
|
builder.OpenComponent<CascadingAuthenticationState>(0);
|
||||||
|
builder.CloseComponent();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act/Assert
|
||||||
|
renderer.AssignRootComponentId(component);
|
||||||
|
var ex = Assert.Throws<InvalidOperationException>(() => component.TriggerRender());
|
||||||
|
Assert.Contains($"There is no registered service of type '{typeof(AuthenticationStateProvider).FullName}'.", ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SuppliesSynchronouslyAvailableAuthStateToChildContent()
|
||||||
|
{
|
||||||
|
// Arrange: Service
|
||||||
|
var services = new ServiceCollection();
|
||||||
|
var authStateProvider = new TestAuthStateProvider()
|
||||||
|
{
|
||||||
|
CurrentAuthStateTask = Task.FromResult(CreateAuthenticationState("Bert"))
|
||||||
|
};
|
||||||
|
services.AddSingleton<AuthenticationStateProvider>(authStateProvider);
|
||||||
|
|
||||||
|
// Arrange: Renderer and component
|
||||||
|
var renderer = new TestRenderer(services.BuildServiceProvider());
|
||||||
|
var component = new UseCascadingAuthenticationStateComponent();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
renderer.AssignRootComponentId(component);
|
||||||
|
component.TriggerRender();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var batch = renderer.Batches.Single();
|
||||||
|
var receiveAuthStateId = batch.GetComponentFrames<ReceiveAuthStateComponent>().Single().ComponentId;
|
||||||
|
var receiveAuthStateDiff = batch.DiffsByComponentId[receiveAuthStateId].Single();
|
||||||
|
Assert.Collection(receiveAuthStateDiff.Edits, edit =>
|
||||||
|
{
|
||||||
|
Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type);
|
||||||
|
AssertFrame.Text(
|
||||||
|
batch.ReferenceFrames[edit.ReferenceFrameIndex],
|
||||||
|
"Authenticated: True; Name: Bert; Pending: False; Renders: 1");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SuppliesAsynchronouslyAvailableAuthStateToChildContent()
|
||||||
|
{
|
||||||
|
// Arrange: Service
|
||||||
|
var services = new ServiceCollection();
|
||||||
|
var authStateTaskCompletionSource = new TaskCompletionSource<AuthenticationState>();
|
||||||
|
var authStateProvider = new TestAuthStateProvider()
|
||||||
|
{
|
||||||
|
CurrentAuthStateTask = authStateTaskCompletionSource.Task
|
||||||
|
};
|
||||||
|
services.AddSingleton<AuthenticationStateProvider>(authStateProvider);
|
||||||
|
|
||||||
|
// Arrange: Renderer and component
|
||||||
|
var renderer = new TestRenderer(services.BuildServiceProvider());
|
||||||
|
var component = new UseCascadingAuthenticationStateComponent();
|
||||||
|
|
||||||
|
// Act 1: Initial synchronous render
|
||||||
|
renderer.AssignRootComponentId(component);
|
||||||
|
component.TriggerRender();
|
||||||
|
|
||||||
|
// Assert 1: Empty state
|
||||||
|
var batch1 = renderer.Batches.Single();
|
||||||
|
var receiveAuthStateFrame = batch1.GetComponentFrames<ReceiveAuthStateComponent>().Single();
|
||||||
|
var receiveAuthStateId = receiveAuthStateFrame.ComponentId;
|
||||||
|
var receiveAuthStateComponent = (ReceiveAuthStateComponent)receiveAuthStateFrame.Component;
|
||||||
|
var receiveAuthStateDiff1 = batch1.DiffsByComponentId[receiveAuthStateId].Single();
|
||||||
|
Assert.Collection(receiveAuthStateDiff1.Edits, edit =>
|
||||||
|
{
|
||||||
|
Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type);
|
||||||
|
AssertFrame.Text(
|
||||||
|
batch1.ReferenceFrames[edit.ReferenceFrameIndex],
|
||||||
|
"Authenticated: False; Name: ; Pending: True; Renders: 1");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act/Assert 2: Auth state fetch task completes in background
|
||||||
|
// No new renders yet, because the cascading parameter itself hasn't changed
|
||||||
|
authStateTaskCompletionSource.SetResult(CreateAuthenticationState("Bert"));
|
||||||
|
Assert.Single(renderer.Batches);
|
||||||
|
|
||||||
|
// Act/Assert 3: Refresh display
|
||||||
|
receiveAuthStateComponent.TriggerRender();
|
||||||
|
Assert.Equal(2, renderer.Batches.Count);
|
||||||
|
var batch2 = renderer.Batches.Last();
|
||||||
|
var receiveAuthStateDiff2 = batch2.DiffsByComponentId[receiveAuthStateId].Single();
|
||||||
|
Assert.Collection(receiveAuthStateDiff2.Edits, edit =>
|
||||||
|
{
|
||||||
|
Assert.Equal(RenderTreeEditType.UpdateText, edit.Type);
|
||||||
|
AssertFrame.Text(
|
||||||
|
batch2.ReferenceFrames[edit.ReferenceFrameIndex],
|
||||||
|
"Authenticated: True; Name: Bert; Pending: False; Renders: 2");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RespondsToNotificationsFromAuthenticationStateProvider()
|
||||||
|
{
|
||||||
|
// Arrange: Service
|
||||||
|
var services = new ServiceCollection();
|
||||||
|
var authStateProvider = new TestAuthStateProvider()
|
||||||
|
{
|
||||||
|
CurrentAuthStateTask = Task.FromResult(CreateAuthenticationState(null))
|
||||||
|
};
|
||||||
|
services.AddSingleton<AuthenticationStateProvider>(authStateProvider);
|
||||||
|
|
||||||
|
// Arrange: Renderer and component, initially rendered
|
||||||
|
var renderer = new TestRenderer(services.BuildServiceProvider());
|
||||||
|
var component = new UseCascadingAuthenticationStateComponent();
|
||||||
|
renderer.AssignRootComponentId(component);
|
||||||
|
component.TriggerRender();
|
||||||
|
var receiveAuthStateId = renderer.Batches.Single()
|
||||||
|
.GetComponentFrames<ReceiveAuthStateComponent>().Single().ComponentId;
|
||||||
|
|
||||||
|
// Act 2: AuthenticationStateProvider issues notification
|
||||||
|
authStateProvider.TriggerAuthenticationStateChanged(
|
||||||
|
Task.FromResult(CreateAuthenticationState("Bert")));
|
||||||
|
|
||||||
|
// Assert 2: Re-renders content
|
||||||
|
Assert.Equal(2, renderer.Batches.Count);
|
||||||
|
var batch = renderer.Batches.Last();
|
||||||
|
var receiveAuthStateDiff = batch.DiffsByComponentId[receiveAuthStateId].Single();
|
||||||
|
Assert.Collection(receiveAuthStateDiff.Edits, edit =>
|
||||||
|
{
|
||||||
|
Assert.Equal(RenderTreeEditType.UpdateText, edit.Type);
|
||||||
|
AssertFrame.Text(
|
||||||
|
batch.ReferenceFrames[edit.ReferenceFrameIndex],
|
||||||
|
"Authenticated: True; Name: Bert; Pending: False; Renders: 2");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReceiveAuthStateComponent : AutoRenderComponent
|
||||||
|
{
|
||||||
|
int numRenders;
|
||||||
|
|
||||||
|
[CascadingParameter] Task<AuthenticationState> AuthStateTask { get; set; }
|
||||||
|
|
||||||
|
protected override void BuildRenderTree(RenderTreeBuilder builder)
|
||||||
|
{
|
||||||
|
numRenders++;
|
||||||
|
|
||||||
|
if (AuthStateTask.IsCompleted)
|
||||||
|
{
|
||||||
|
var identity = AuthStateTask.Result.User.Identity;
|
||||||
|
builder.AddContent(0, $"Authenticated: {identity.IsAuthenticated}; Name: {identity.Name}; Pending: False; Renders: {numRenders}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder.AddContent(0, $"Authenticated: False; Name: ; Pending: True; Renders: {numRenders}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UseCascadingAuthenticationStateComponent : AutoRenderComponent
|
||||||
|
{
|
||||||
|
protected override void BuildRenderTree(RenderTreeBuilder builder)
|
||||||
|
{
|
||||||
|
builder.OpenComponent<CascadingAuthenticationState>(0);
|
||||||
|
builder.AddAttribute(1, RenderTreeBuilder.ChildContent, new RenderFragment(childBuilder =>
|
||||||
|
{
|
||||||
|
childBuilder.OpenComponent<ReceiveAuthStateComponent>(0);
|
||||||
|
childBuilder.CloseComponent();
|
||||||
|
}));
|
||||||
|
builder.CloseComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestAuthStateProvider : AuthenticationStateProvider
|
||||||
|
{
|
||||||
|
public Task<AuthenticationState> CurrentAuthStateTask { get; set; }
|
||||||
|
|
||||||
|
public override Task<AuthenticationState> GetAuthenticationStateAsync()
|
||||||
|
{
|
||||||
|
return CurrentAuthStateTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void TriggerAuthenticationStateChanged(Task<AuthenticationState> authState)
|
||||||
|
{
|
||||||
|
NotifyAuthenticationStateChanged(authState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AuthenticationState CreateAuthenticationState(string username)
|
||||||
|
=> new AuthenticationState(new ClaimsPrincipal(username == null
|
||||||
|
? new ClaimsIdentity()
|
||||||
|
: (IIdentity)new TestIdentity { Name = username }));
|
||||||
|
|
||||||
|
class TestIdentity : IIdentity
|
||||||
|
{
|
||||||
|
public string AuthenticationType => "Test";
|
||||||
|
|
||||||
|
public bool IsAuthenticated => true;
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -44,6 +44,10 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
jsRuntime.Initialize(client);
|
jsRuntime.Initialize(client);
|
||||||
componentContext.Initialize(client);
|
componentContext.Initialize(client);
|
||||||
|
|
||||||
|
// You can replace the AuthenticationStateProvider with a custom one, but in that case initialization is up to you
|
||||||
|
var authenticationStateProvider = scope.ServiceProvider.GetService<AuthenticationStateProvider>();
|
||||||
|
(authenticationStateProvider as FixedAuthenticationStateProvider)?.Initialize(httpContext.User);
|
||||||
|
|
||||||
var uriHelper = (RemoteUriHelper)scope.ServiceProvider.GetRequiredService<IUriHelper>();
|
var uriHelper = (RemoteUriHelper)scope.ServiceProvider.GetRequiredService<IUriHelper>();
|
||||||
if (client.Connected)
|
if (client.Connected)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
// 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.Security.Claims;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An <see cref="AuthenticationStateProvider"/> intended for use in server-side
|
||||||
|
/// Blazor. The circuit factory will supply a <see cref="ClaimsPrincipal"/> from
|
||||||
|
/// the current <see cref="HttpContext.User"/>, which will stay fixed for the
|
||||||
|
/// lifetime of the circuit since <see cref="HttpContext.User"/> cannot change.
|
||||||
|
///
|
||||||
|
/// This can therefore only be used with redirect-style authentication flows,
|
||||||
|
/// since it requires a new HTTP request in order to become a different user.
|
||||||
|
/// </summary>
|
||||||
|
internal class FixedAuthenticationStateProvider : AuthenticationStateProvider
|
||||||
|
{
|
||||||
|
private Task<AuthenticationState> _authenticationStateTask;
|
||||||
|
|
||||||
|
public void Initialize(ClaimsPrincipal user)
|
||||||
|
{
|
||||||
|
_authenticationStateTask = Task.FromResult(new AuthenticationState(user));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task<AuthenticationState> GetAuthenticationStateAsync()
|
||||||
|
=> _authenticationStateTask
|
||||||
|
?? throw new InvalidOperationException($"{nameof(GetAuthenticationStateAsync)} was called before {nameof(Initialize)}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -67,6 +67,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
services.AddScoped<IUriHelper, RemoteUriHelper>();
|
services.AddScoped<IUriHelper, RemoteUriHelper>();
|
||||||
services.AddScoped<IJSRuntime, RemoteJSRuntime>();
|
services.AddScoped<IJSRuntime, RemoteJSRuntime>();
|
||||||
services.AddScoped<IComponentContext, RemoteComponentContext>();
|
services.AddScoped<IComponentContext, RemoteComponentContext>();
|
||||||
|
services.AddScoped<AuthenticationStateProvider, FixedAuthenticationStateProvider>();
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
// 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.Security.Claims;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Components.Server.Circuits;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components.Server.Tests.Circuits
|
||||||
|
{
|
||||||
|
public class FixedAuthenticationStateProviderTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task CannotProvideAuthenticationStateBeforeInitialization()
|
||||||
|
{
|
||||||
|
await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||||||
|
new FixedAuthenticationStateProvider()
|
||||||
|
.GetAuthenticationStateAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SuppliesAuthenticationStateWithFixedUser()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var user = new ClaimsPrincipal();
|
||||||
|
var provider = new FixedAuthenticationStateProvider();
|
||||||
|
provider.Initialize(user);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var authenticationState = await provider.GetAuthenticationStateAsync();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(authenticationState);
|
||||||
|
Assert.Same(user, authenticationState.User);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
// 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 Microsoft.AspNetCore.Components.RenderTree;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components.Test.Helpers
|
||||||
|
{
|
||||||
|
public class AutoRenderFragmentComponent : AutoRenderComponent
|
||||||
|
{
|
||||||
|
private readonly RenderFragment _renderFragment;
|
||||||
|
|
||||||
|
public AutoRenderFragmentComponent(RenderFragment renderFragment)
|
||||||
|
{
|
||||||
|
_renderFragment = renderFragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void BuildRenderTree(RenderTreeBuilder builder)
|
||||||
|
=> _renderFragment(builder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.Components.RenderTree;
|
using Microsoft.AspNetCore.Components.RenderTree;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Test.Helpers
|
namespace Microsoft.AspNetCore.Components.Test.Helpers
|
||||||
|
|
@ -18,6 +19,12 @@ namespace Microsoft.AspNetCore.Components.Test.Helpers
|
||||||
public IList<int> DisposedComponentIDs { get; set; }
|
public IList<int> DisposedComponentIDs { get; set; }
|
||||||
public RenderTreeFrame[] ReferenceFrames { get; set; }
|
public RenderTreeFrame[] ReferenceFrames { get; set; }
|
||||||
|
|
||||||
|
public IEnumerable<RenderTreeFrame> GetComponentFrames<T>() where T : IComponent
|
||||||
|
=> ReferenceFrames.Where(f => f.FrameType == RenderTreeFrameType.Component && f.Component is T);
|
||||||
|
|
||||||
|
public IEnumerable<RenderTreeDiff> GetComponentDiffs<T>() where T : IComponent
|
||||||
|
=> GetComponentFrames<T>().SelectMany(f => DiffsByComponentId[f.ComponentId]);
|
||||||
|
|
||||||
internal void AddDiff(RenderTreeDiff diff)
|
internal void AddDiff(RenderTreeDiff diff)
|
||||||
{
|
{
|
||||||
var componentId = diff.ComponentId;
|
var componentId = diff.ComponentId;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue