Integrate authorization into Blazor router (#10491)
* Split AuthorizeView in two, so "Core" part can be reused from routing * Rename LayoutDisplay to PageDisplay * Integrate authorization with Router/PageDisplay * CR: Replace AuthorizeViewCore.razor with AuthorizeViewCore.cs * Update tests * Update ref assemblies * Add E2E tests * Update ref assembly exclusions * More manual ref assembly updating * Oh these ref assemblies
This commit is contained in:
parent
9969e99ef4
commit
bc011b5c97
|
|
@ -5,6 +5,7 @@ T:Microsoft.AspNetCore.Mvc.ApplicationModels.PageParameterModel
|
||||||
T:Microsoft.AspNetCore.Mvc.ApplicationModels.PagePropertyModel
|
T:Microsoft.AspNetCore.Mvc.ApplicationModels.PagePropertyModel
|
||||||
# Manually implemented - https://github.com/aspnet/AspNetCore/issues/8825
|
# Manually implemented - https://github.com/aspnet/AspNetCore/issues/8825
|
||||||
T:Microsoft.AspNetCore.Components.AuthorizeView
|
T:Microsoft.AspNetCore.Components.AuthorizeView
|
||||||
|
T:Microsoft.AspNetCore.Components.AuthorizeViewCore
|
||||||
T:Microsoft.AspNetCore.Components.CascadingAuthenticationState
|
T:Microsoft.AspNetCore.Components.CascadingAuthenticationState
|
||||||
T:Microsoft.AspNetCore.Components.CascadingValue`1
|
T:Microsoft.AspNetCore.Components.CascadingValue`1
|
||||||
T:Microsoft.AspNetCore.Components.Forms.DataAnnotationsValidator
|
T:Microsoft.AspNetCore.Components.Forms.DataAnnotationsValidator
|
||||||
|
|
@ -18,6 +19,6 @@ T:Microsoft.AspNetCore.Components.Forms.InputText
|
||||||
T:Microsoft.AspNetCore.Components.Forms.InputTextArea
|
T:Microsoft.AspNetCore.Components.Forms.InputTextArea
|
||||||
T:Microsoft.AspNetCore.Components.Forms.ValidationMessage`1
|
T:Microsoft.AspNetCore.Components.Forms.ValidationMessage`1
|
||||||
T:Microsoft.AspNetCore.Components.Forms.ValidationSummary
|
T:Microsoft.AspNetCore.Components.Forms.ValidationSummary
|
||||||
T:Microsoft.AspNetCore.Components.Layouts.LayoutDisplay
|
T:Microsoft.AspNetCore.Components.PageDisplay
|
||||||
T:Microsoft.AspNetCore.Components.Routing.NavLink
|
T:Microsoft.AspNetCore.Components.Routing.NavLink
|
||||||
T:Microsoft.AspNetCore.Components.Routing.Router
|
T:Microsoft.AspNetCore.Components.Routing.Router
|
||||||
|
|
@ -49,10 +49,22 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
// Built-in components: https://github.com/aspnet/AspNetCore/issues/8825
|
// Built-in components: https://github.com/aspnet/AspNetCore/issues/8825
|
||||||
namespace Microsoft.AspNetCore.Components
|
namespace Microsoft.AspNetCore.Components
|
||||||
{
|
{
|
||||||
public partial class AuthorizeView : Microsoft.AspNetCore.Components.ComponentBase
|
public partial class AuthorizeView : Microsoft.AspNetCore.Components.AuthorizeViewCore
|
||||||
{
|
{
|
||||||
public AuthorizeView() { }
|
public AuthorizeView() { }
|
||||||
[Microsoft.AspNetCore.Components.ParameterAttribute]
|
[Microsoft.AspNetCore.Components.ParameterAttribute]
|
||||||
|
public string Policy { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; } }
|
||||||
|
[Microsoft.AspNetCore.Components.ParameterAttribute]
|
||||||
|
public object Resource { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; } }
|
||||||
|
[Microsoft.AspNetCore.Components.ParameterAttribute]
|
||||||
|
public string Roles { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; } }
|
||||||
|
protected override Microsoft.AspNetCore.Authorization.IAuthorizeData[] GetAuthorizeData() { throw null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract partial class AuthorizeViewCore : Microsoft.AspNetCore.Components.ComponentBase
|
||||||
|
{
|
||||||
|
public AuthorizeViewCore() { }
|
||||||
|
[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; } }
|
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]
|
[Microsoft.AspNetCore.Components.ParameterAttribute]
|
||||||
public Microsoft.AspNetCore.Components.RenderFragment Authorizing { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; } }
|
public Microsoft.AspNetCore.Components.RenderFragment Authorizing { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; } }
|
||||||
|
|
@ -60,13 +72,8 @@ namespace Microsoft.AspNetCore.Components
|
||||||
public Microsoft.AspNetCore.Components.RenderFragment<Microsoft.AspNetCore.Components.AuthenticationState> ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; } }
|
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]
|
[Microsoft.AspNetCore.Components.ParameterAttribute]
|
||||||
public Microsoft.AspNetCore.Components.RenderFragment<Microsoft.AspNetCore.Components.AuthenticationState> NotAuthorized { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; } }
|
public Microsoft.AspNetCore.Components.RenderFragment<Microsoft.AspNetCore.Components.AuthenticationState> NotAuthorized { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; } }
|
||||||
[Microsoft.AspNetCore.Components.ParameterAttribute]
|
|
||||||
public string Policy { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; } }
|
|
||||||
[Microsoft.AspNetCore.Components.ParameterAttribute]
|
|
||||||
public string Roles { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; } }
|
|
||||||
[Microsoft.AspNetCore.Components.ParameterAttribute]
|
|
||||||
public object Resource { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; } }
|
|
||||||
protected override void BuildRenderTree(Microsoft.AspNetCore.Components.RenderTree.RenderTreeBuilder builder) { }
|
protected override void BuildRenderTree(Microsoft.AspNetCore.Components.RenderTree.RenderTreeBuilder builder) { }
|
||||||
|
protected abstract Microsoft.AspNetCore.Authorization.IAuthorizeData[] GetAuthorizeData();
|
||||||
[System.Diagnostics.DebuggerStepThroughAttribute]
|
[System.Diagnostics.DebuggerStepThroughAttribute]
|
||||||
protected override System.Threading.Tasks.Task OnParametersSetAsync() { throw null; }
|
protected override System.Threading.Tasks.Task OnParametersSetAsync() { throw null; }
|
||||||
}
|
}
|
||||||
|
|
@ -218,9 +225,13 @@ namespace Microsoft.AspNetCore.Components.Forms
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Layouts
|
namespace Microsoft.AspNetCore.Components.Layouts
|
||||||
{
|
{
|
||||||
public partial class LayoutDisplay : Microsoft.AspNetCore.Components.IComponent
|
public partial class PageDisplay : Microsoft.AspNetCore.Components.IComponent
|
||||||
{
|
{
|
||||||
public LayoutDisplay() { }
|
public PageDisplay() { }
|
||||||
|
[Microsoft.AspNetCore.Components.ParameterAttribute]
|
||||||
|
public Microsoft.AspNetCore.Components.RenderFragment AuthorizingContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; }}
|
||||||
|
[Microsoft.AspNetCore.Components.ParameterAttribute]
|
||||||
|
public Microsoft.AspNetCore.Components.RenderFragment<Microsoft.AspNetCore.Components.AuthenticationState> NotAuthorizedContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; }}
|
||||||
[Microsoft.AspNetCore.Components.ParameterAttribute]
|
[Microsoft.AspNetCore.Components.ParameterAttribute]
|
||||||
public System.Type Page { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; }}
|
public System.Type Page { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; }}
|
||||||
[Microsoft.AspNetCore.Components.ParameterAttribute]
|
[Microsoft.AspNetCore.Components.ParameterAttribute]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
// 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.Collections.Concurrent;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components.Auth
|
||||||
|
{
|
||||||
|
internal static class AttributeAuthorizeDataCache
|
||||||
|
{
|
||||||
|
private static ConcurrentDictionary<Type, IAuthorizeData[]> _cache
|
||||||
|
= new ConcurrentDictionary<Type, IAuthorizeData[]>();
|
||||||
|
|
||||||
|
public static IAuthorizeData[] GetAuthorizeDataForType(Type type)
|
||||||
|
{
|
||||||
|
IAuthorizeData[] result;
|
||||||
|
if (!_cache.TryGetValue(type, out result))
|
||||||
|
{
|
||||||
|
result = ComputeAuthorizeDataForType(type);
|
||||||
|
_cache[type] = result; // Safe race - doesn't matter if it overwrites
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IAuthorizeData[] ComputeAuthorizeDataForType(Type type)
|
||||||
|
{
|
||||||
|
// Allow Anonymous skips all authorization
|
||||||
|
var allAttributes = type.GetCustomAttributes(inherit: true);
|
||||||
|
if (allAttributes.OfType<IAllowAnonymous>().Any())
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var authorizeDataAttributes = allAttributes.OfType<IAuthorizeData>().ToArray();
|
||||||
|
return authorizeDataAttributes.Length > 0 ? authorizeDataAttributes : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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 Microsoft.AspNetCore.Authorization;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Displays differing content depending on the user's authorization status.
|
||||||
|
/// </summary>
|
||||||
|
public class AuthorizeView : AuthorizeViewCore
|
||||||
|
{
|
||||||
|
private readonly IAuthorizeData[] selfAsAuthorizeData;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs an instance of <see cref="AuthorizeView"/>.
|
||||||
|
/// </summary>
|
||||||
|
public AuthorizeView()
|
||||||
|
{
|
||||||
|
selfAsAuthorizeData = new[] { new AuthorizeDataAdapter(this) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The policy name that determines whether the content can be displayed.
|
||||||
|
/// </summary>
|
||||||
|
[Parameter] public string Policy { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A comma delimited list of roles that are allowed to display the content.
|
||||||
|
/// </summary>
|
||||||
|
[Parameter] public string Roles { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the data used for authorization.
|
||||||
|
/// </summary>
|
||||||
|
protected override IAuthorizeData[] GetAuthorizeData()
|
||||||
|
=> selfAsAuthorizeData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,99 +0,0 @@
|
||||||
@namespace Microsoft.AspNetCore.Components
|
|
||||||
@using System.Security.Claims
|
|
||||||
@using Microsoft.AspNetCore.Authorization
|
|
||||||
@inject IAuthorizationService AuthorizationService
|
|
||||||
@inject IAuthorizationPolicyProvider AuthorizationPolicyProvider
|
|
||||||
|
|
||||||
@if (currentAuthenticationState == null)
|
|
||||||
{
|
|
||||||
@Authorizing
|
|
||||||
}
|
|
||||||
else if (isAuthorized)
|
|
||||||
{
|
|
||||||
@((Authorized ?? ChildContent)?.Invoke(currentAuthenticationState))
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
@(NotAuthorized?.Invoke(currentAuthenticationState))
|
|
||||||
}
|
|
||||||
|
|
||||||
@functions {
|
|
||||||
private IAuthorizeData[] selfAsAuthorizeData;
|
|
||||||
private AuthenticationState currentAuthenticationState;
|
|
||||||
private bool isAuthorized;
|
|
||||||
|
|
||||||
[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<AuthenticationState> 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; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The policy name that determines whether the content can be displayed.
|
|
||||||
/// </summary>
|
|
||||||
[Parameter] public string Policy { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A comma delimited list of roles that are allowed to display the content.
|
|
||||||
/// </summary>
|
|
||||||
[Parameter] public string Roles { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The resource to which access is being controlled.
|
|
||||||
/// </summary>
|
|
||||||
[Parameter] public object Resource { get; private set; }
|
|
||||||
|
|
||||||
protected override void OnInit()
|
|
||||||
{
|
|
||||||
selfAsAuthorizeData = new[]
|
|
||||||
{
|
|
||||||
new AuthorizeDataAdapter((AuthorizeView)(object)this)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
// Importantly, we *don't* call StateHasChanged between the following async steps,
|
|
||||||
// otherwise we'd display an incorrect UI state while waiting for IsAuthorizedAsync
|
|
||||||
currentAuthenticationState = await AuthenticationState;
|
|
||||||
isAuthorized = await IsAuthorizedAsync(currentAuthenticationState.User);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<bool> IsAuthorizedAsync(ClaimsPrincipal user)
|
|
||||||
{
|
|
||||||
var policy = await AuthorizationPolicy.CombineAsync(
|
|
||||||
AuthorizationPolicyProvider, selfAsAuthorizeData);
|
|
||||||
var result = await AuthorizationService.AuthorizeAsync(user, Resource, policy);
|
|
||||||
return result.Succeeded;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
// 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.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Components.RenderTree;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A base class for components that display differing content depending on the user's authorization status.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class AuthorizeViewCore : ComponentBase
|
||||||
|
{
|
||||||
|
private AuthenticationState currentAuthenticationState;
|
||||||
|
private bool isAuthorized;
|
||||||
|
|
||||||
|
/// <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<AuthenticationState> 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; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The resource to which access is being controlled.
|
||||||
|
/// </summary>
|
||||||
|
[Parameter] public object Resource { get; private set; }
|
||||||
|
|
||||||
|
[CascadingParameter] private Task<AuthenticationState> AuthenticationState { get; set; }
|
||||||
|
|
||||||
|
[Inject] private IAuthorizationPolicyProvider AuthorizationPolicyProvider { get; set; }
|
||||||
|
|
||||||
|
[Inject] private IAuthorizationService AuthorizationService { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildRenderTree(RenderTreeBuilder builder)
|
||||||
|
{
|
||||||
|
if (currentAuthenticationState == null)
|
||||||
|
{
|
||||||
|
builder.AddContent(0, Authorizing);
|
||||||
|
}
|
||||||
|
else if (isAuthorized)
|
||||||
|
{
|
||||||
|
var authorizedContent = Authorized ?? ChildContent;
|
||||||
|
builder.AddContent(1, authorizedContent?.Invoke(currentAuthenticationState));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder.AddContent(2, NotAuthorized?.Invoke(currentAuthenticationState));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
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($"Do not specify both '{nameof(Authorized)}' and '{nameof(ChildContent)}'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AuthenticationState == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Authorization requires a cascading parameter of type Task<{nameof(AuthenticationState)}>. Consider using {typeof(CascadingAuthenticationState).Name} to supply this.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// First render in pending state
|
||||||
|
// If the task has already completed, this render will be skipped
|
||||||
|
currentAuthenticationState = null;
|
||||||
|
|
||||||
|
// Then render in completed state
|
||||||
|
// Importantly, we *don't* call StateHasChanged between the following async steps,
|
||||||
|
// otherwise we'd display an incorrect UI state while waiting for IsAuthorizedAsync
|
||||||
|
currentAuthenticationState = await AuthenticationState;
|
||||||
|
isAuthorized = await IsAuthorizedAsync(currentAuthenticationState.User);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the data required to apply authorization rules.
|
||||||
|
/// </summary>
|
||||||
|
protected abstract IAuthorizeData[] GetAuthorizeData();
|
||||||
|
|
||||||
|
private async Task<bool> IsAuthorizedAsync(ClaimsPrincipal user)
|
||||||
|
{
|
||||||
|
var authorizeData = GetAuthorizeData();
|
||||||
|
var policy = await AuthorizationPolicy.CombineAsync(
|
||||||
|
AuthorizationPolicyProvider, authorizeData);
|
||||||
|
var result = await AuthorizationService.AuthorizeAsync(user, Resource, policy);
|
||||||
|
return result.Succeeded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,90 +0,0 @@
|
||||||
// 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.Collections.Generic;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Components;
|
|
||||||
using Microsoft.AspNetCore.Components.RenderTree;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Layouts
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Displays the specified page component, rendering it inside its layout
|
|
||||||
/// and any further nested layouts.
|
|
||||||
/// </summary>
|
|
||||||
public class LayoutDisplay : IComponent
|
|
||||||
{
|
|
||||||
internal const string NameOfPage = nameof(Page);
|
|
||||||
internal const string NameOfPageParameters = nameof(PageParameters);
|
|
||||||
|
|
||||||
private RenderHandle _renderHandle;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the type of the page component to display.
|
|
||||||
/// The type must implement <see cref="IComponent"/>.
|
|
||||||
/// </summary>
|
|
||||||
[Parameter]
|
|
||||||
public Type Page { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the parameters to pass to the page.
|
|
||||||
/// </summary>
|
|
||||||
[Parameter]
|
|
||||||
public IDictionary<string, object> PageParameters { get; private set; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void Configure(RenderHandle renderHandle)
|
|
||||||
{
|
|
||||||
_renderHandle = renderHandle;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public Task SetParametersAsync(ParameterCollection parameters)
|
|
||||||
{
|
|
||||||
parameters.SetParameterProperties(this);
|
|
||||||
Render();
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Render()
|
|
||||||
{
|
|
||||||
// In the middle, we render the requested page
|
|
||||||
var fragment = RenderComponentWithBody(Page, bodyParam: null);
|
|
||||||
|
|
||||||
// Repeatedly wrap it in each layer of nested layout until we get
|
|
||||||
// to a layout that has no parent
|
|
||||||
Type layoutType = Page;
|
|
||||||
while ((layoutType = GetLayoutType(layoutType)) != null)
|
|
||||||
{
|
|
||||||
fragment = RenderComponentWithBody(layoutType, fragment);
|
|
||||||
}
|
|
||||||
|
|
||||||
_renderHandle.Render(fragment);
|
|
||||||
}
|
|
||||||
|
|
||||||
private RenderFragment RenderComponentWithBody(Type componentType, RenderFragment bodyParam) => builder =>
|
|
||||||
{
|
|
||||||
builder.OpenComponent(0, componentType);
|
|
||||||
if (bodyParam != null)
|
|
||||||
{
|
|
||||||
builder.AddAttribute(1, LayoutComponentBase.BodyPropertyName, bodyParam);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (PageParameters != null)
|
|
||||||
{
|
|
||||||
foreach (var kvp in PageParameters)
|
|
||||||
{
|
|
||||||
builder.AddAttribute(1, kvp.Key, kvp.Value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
builder.CloseComponent();
|
|
||||||
};
|
|
||||||
|
|
||||||
private Type GetLayoutType(Type type)
|
|
||||||
=> type.GetCustomAttribute<LayoutAttribute>()?.LayoutType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
// 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.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Components.Auth;
|
||||||
|
using Microsoft.AspNetCore.Components.Layouts;
|
||||||
|
using Microsoft.AspNetCore.Components.RenderTree;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Displays the specified page component, rendering it inside its layout
|
||||||
|
/// and any further nested layouts, plus applying any authorization rules.
|
||||||
|
/// </summary>
|
||||||
|
public class PageDisplay : IComponent
|
||||||
|
{
|
||||||
|
private RenderHandle _renderHandle;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the type of the page component to display.
|
||||||
|
/// The type must implement <see cref="IComponent"/>.
|
||||||
|
/// </summary>
|
||||||
|
[Parameter]
|
||||||
|
public Type Page { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the parameters to pass to the page.
|
||||||
|
/// </summary>
|
||||||
|
[Parameter]
|
||||||
|
public IDictionary<string, object> PageParameters { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The content that will be displayed if the user is not authorized.
|
||||||
|
/// </summary>
|
||||||
|
[Parameter]
|
||||||
|
public RenderFragment<AuthenticationState> NotAuthorizedContent { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The content that will be displayed while asynchronous authorization is in progress.
|
||||||
|
/// </summary>
|
||||||
|
[Parameter]
|
||||||
|
public RenderFragment AuthorizingContent { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Configure(RenderHandle renderHandle)
|
||||||
|
{
|
||||||
|
_renderHandle = renderHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task SetParametersAsync(ParameterCollection parameters)
|
||||||
|
{
|
||||||
|
parameters.SetParameterProperties(this);
|
||||||
|
Render();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Render()
|
||||||
|
{
|
||||||
|
// In the middle goes the requested page
|
||||||
|
var fragment = (RenderFragment)RenderPageWithParameters;
|
||||||
|
|
||||||
|
// Around that goes an AuthorizeViewCore
|
||||||
|
fragment = WrapInAuthorizeViewCore(fragment);
|
||||||
|
|
||||||
|
// Then repeatedly wrap that in each layer of nested layout until we get
|
||||||
|
// to a layout that has no parent
|
||||||
|
Type layoutType = Page;
|
||||||
|
while ((layoutType = GetLayoutType(layoutType)) != null)
|
||||||
|
{
|
||||||
|
fragment = WrapInLayout(layoutType, fragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderHandle.Render(fragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RenderFragment WrapInLayout(Type layoutType, RenderFragment bodyParam) => builder =>
|
||||||
|
{
|
||||||
|
builder.OpenComponent(0, layoutType);
|
||||||
|
builder.AddAttribute(1, LayoutComponentBase.BodyPropertyName, bodyParam);
|
||||||
|
builder.CloseComponent();
|
||||||
|
};
|
||||||
|
|
||||||
|
private void RenderPageWithParameters(RenderTreeBuilder builder)
|
||||||
|
{
|
||||||
|
builder.OpenComponent(0, Page);
|
||||||
|
|
||||||
|
if (PageParameters != null)
|
||||||
|
{
|
||||||
|
foreach (var kvp in PageParameters)
|
||||||
|
{
|
||||||
|
builder.AddAttribute(1, kvp.Key, kvp.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.CloseComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private RenderFragment WrapInAuthorizeViewCore(RenderFragment pageFragment)
|
||||||
|
{
|
||||||
|
var authorizeData = AttributeAuthorizeDataCache.GetAuthorizeDataForType(Page);
|
||||||
|
if (authorizeData == null)
|
||||||
|
{
|
||||||
|
// No authorization, so no need to wrap the fragment
|
||||||
|
return pageFragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some authorization data exists, so we do need to wrap the fragment
|
||||||
|
RenderFragment<AuthenticationState> authorizedContent = context => pageFragment;
|
||||||
|
return builder =>
|
||||||
|
{
|
||||||
|
builder.OpenComponent<AuthorizeViewWithSuppliedData>(0);
|
||||||
|
builder.AddAttribute(1, nameof(AuthorizeViewWithSuppliedData.AuthorizeDataParam), authorizeData);
|
||||||
|
builder.AddAttribute(2, nameof(AuthorizeViewWithSuppliedData.Authorized), authorizedContent);
|
||||||
|
builder.AddAttribute(3, nameof(AuthorizeViewWithSuppliedData.NotAuthorized), NotAuthorizedContent ?? DefaultNotAuthorizedContent);
|
||||||
|
builder.AddAttribute(4, nameof(AuthorizeViewWithSuppliedData.Authorizing), AuthorizingContent);
|
||||||
|
builder.CloseComponent();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Type GetLayoutType(Type type)
|
||||||
|
=> type.GetCustomAttribute<LayoutAttribute>()?.LayoutType;
|
||||||
|
|
||||||
|
private class AuthorizeViewWithSuppliedData : AuthorizeViewCore
|
||||||
|
{
|
||||||
|
[Parameter] public IAuthorizeData[] AuthorizeDataParam { get; private set; }
|
||||||
|
|
||||||
|
protected override IAuthorizeData[] GetAuthorizeData() => AuthorizeDataParam;
|
||||||
|
}
|
||||||
|
|
||||||
|
// There has to be some default content. If we render blank by default, developers
|
||||||
|
// will find it hard to guess why their UI isn't appearing.
|
||||||
|
private static RenderFragment DefaultNotAuthorizedContent(AuthenticationState authenticationState)
|
||||||
|
=> builder => builder.AddContent(0, "Not authorized");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -40,6 +40,16 @@ namespace Microsoft.AspNetCore.Components.Routing
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Parameter] public RenderFragment NotFoundContent { get; private set; }
|
[Parameter] public RenderFragment NotFoundContent { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The content that will be displayed if the user is not authorized.
|
||||||
|
/// </summary>
|
||||||
|
[Parameter] public RenderFragment<AuthenticationState> NotAuthorizedContent { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The content that will be displayed while asynchronous authorization is in progress.
|
||||||
|
/// </summary>
|
||||||
|
[Parameter] public RenderFragment AuthorizingContent { get; private set; }
|
||||||
|
|
||||||
private RouteTable Routes { get; set; }
|
private RouteTable Routes { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -78,9 +88,11 @@ namespace Microsoft.AspNetCore.Components.Routing
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected virtual void Render(RenderTreeBuilder builder, Type handler, IDictionary<string, object> parameters)
|
protected virtual void Render(RenderTreeBuilder builder, Type handler, IDictionary<string, object> parameters)
|
||||||
{
|
{
|
||||||
builder.OpenComponent(0, typeof(LayoutDisplay));
|
builder.OpenComponent(0, typeof(PageDisplay));
|
||||||
builder.AddAttribute(1, LayoutDisplay.NameOfPage, handler);
|
builder.AddAttribute(1, nameof(PageDisplay.Page), handler);
|
||||||
builder.AddAttribute(2, LayoutDisplay.NameOfPageParameters, parameters);
|
builder.AddAttribute(2, nameof(PageDisplay.PageParameters), parameters);
|
||||||
|
builder.AddAttribute(3, nameof(PageDisplay.NotAuthorizedContent), NotAuthorizedContent);
|
||||||
|
builder.AddAttribute(4, nameof(PageDisplay.AuthorizingContent), AuthorizingContent);
|
||||||
builder.CloseComponent();
|
builder.CloseComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -248,7 +248,7 @@ namespace Microsoft.AspNetCore.Components
|
||||||
renderer.AssignRootComponentId(rootComponent);
|
renderer.AssignRootComponentId(rootComponent);
|
||||||
var ex = Assert.Throws<InvalidOperationException>(() =>
|
var ex = Assert.Throws<InvalidOperationException>(() =>
|
||||||
rootComponent.TriggerRender());
|
rootComponent.TriggerRender());
|
||||||
Assert.Equal("When using AuthorizeView, do not specify both 'Authorized' and 'ChildContent'.", ex.Message);
|
Assert.Equal("Do not specify both 'Authorized' and 'ChildContent'.", ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
||||||
|
|
@ -11,26 +11,26 @@ using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Test
|
namespace Microsoft.AspNetCore.Components.Test
|
||||||
{
|
{
|
||||||
public class LayoutTest
|
public class PageDisplayTest
|
||||||
{
|
{
|
||||||
private TestRenderer _renderer = new TestRenderer();
|
private TestRenderer _renderer = new TestRenderer();
|
||||||
private LayoutDisplay _layoutDisplayComponent = new LayoutDisplay();
|
private PageDisplay _pageDisplayComponent = new PageDisplay();
|
||||||
private int _layoutDisplayComponentId;
|
private int _pageDisplayComponentId;
|
||||||
|
|
||||||
public LayoutTest()
|
public PageDisplayTest()
|
||||||
{
|
{
|
||||||
_renderer = new TestRenderer();
|
_renderer = new TestRenderer();
|
||||||
_layoutDisplayComponent = new LayoutDisplay();
|
_pageDisplayComponent = new PageDisplay();
|
||||||
_layoutDisplayComponentId = _renderer.AssignRootComponentId(_layoutDisplayComponent);
|
_pageDisplayComponentId = _renderer.AssignRootComponentId(_pageDisplayComponent);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void DisplaysComponentInsideLayout()
|
public void DisplaysComponentInsideLayout()
|
||||||
{
|
{
|
||||||
// Arrange/Act
|
// Arrange/Act
|
||||||
_renderer.Invoke(() => _layoutDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary<string, object>
|
_renderer.Invoke(() => _pageDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary<string, object>
|
||||||
{
|
{
|
||||||
{ LayoutDisplay.NameOfPage, typeof(ComponentWithLayout) }
|
{ nameof(PageDisplay.Page), typeof(ComponentWithLayout) }
|
||||||
})));
|
})));
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -85,9 +85,9 @@ namespace Microsoft.AspNetCore.Components.Test
|
||||||
public void DisplaysComponentInsideNestedLayout()
|
public void DisplaysComponentInsideNestedLayout()
|
||||||
{
|
{
|
||||||
// Arrange/Act
|
// Arrange/Act
|
||||||
_renderer.Invoke(() => _layoutDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary<string, object>
|
_renderer.Invoke(() => _pageDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary<string, object>
|
||||||
{
|
{
|
||||||
{ LayoutDisplay.NameOfPage, typeof(ComponentWithNestedLayout) }
|
{ nameof(PageDisplay.Page), typeof(ComponentWithNestedLayout) }
|
||||||
})));
|
})));
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -112,15 +112,15 @@ namespace Microsoft.AspNetCore.Components.Test
|
||||||
public void CanChangeDisplayedPageWithSameLayout()
|
public void CanChangeDisplayedPageWithSameLayout()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
_renderer.Invoke(() => _layoutDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary<string, object>
|
_renderer.Invoke(() => _pageDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary<string, object>
|
||||||
{
|
{
|
||||||
{ LayoutDisplay.NameOfPage, typeof(ComponentWithLayout) }
|
{ nameof(PageDisplay.Page), typeof(ComponentWithLayout) }
|
||||||
})));
|
})));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
_renderer.Invoke(() => _layoutDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary<string, object>
|
_renderer.Invoke(() => _pageDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary<string, object>
|
||||||
{
|
{
|
||||||
{ LayoutDisplay.NameOfPage, typeof(DifferentComponentWithLayout) }
|
{ nameof(PageDisplay.Page), typeof(DifferentComponentWithLayout) }
|
||||||
})));
|
})));
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -163,15 +163,15 @@ namespace Microsoft.AspNetCore.Components.Test
|
||||||
public void CanChangeDisplayedPageWithDifferentLayout()
|
public void CanChangeDisplayedPageWithDifferentLayout()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
_renderer.Invoke(() => _layoutDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary<string, object>
|
_renderer.Invoke(() => _pageDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary<string, object>
|
||||||
{
|
{
|
||||||
{ LayoutDisplay.NameOfPage, typeof(ComponentWithLayout) }
|
{ nameof(PageDisplay.Page), typeof(ComponentWithLayout) }
|
||||||
})));
|
})));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
_renderer.Invoke(() => _layoutDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary<string, object>
|
_renderer.Invoke(() => _pageDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary<string, object>
|
||||||
{
|
{
|
||||||
{ LayoutDisplay.NameOfPage, typeof(ComponentWithNestedLayout) }
|
{ nameof(PageDisplay.Page), typeof(ComponentWithNestedLayout) }
|
||||||
})));
|
})));
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -16,6 +16,10 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
// These strings correspond to the links in BasicTestApp\AuthTest\Links.razor
|
// These strings correspond to the links in BasicTestApp\AuthTest\Links.razor
|
||||||
const string CascadingAuthenticationStateLink = "Cascading authentication state";
|
const string CascadingAuthenticationStateLink = "Cascading authentication state";
|
||||||
const string AuthorizeViewCases = "AuthorizeView cases";
|
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";
|
||||||
|
|
||||||
public AuthTest(
|
public AuthTest(
|
||||||
BrowserFixture browserFixture,
|
BrowserFixture browserFixture,
|
||||||
|
|
@ -54,7 +58,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void AuthorizeViewCases_NoAuthorizationRule_Unauthenticated()
|
public void AuthorizeViewCases_NoAuthorizationRule_NotAuthorized()
|
||||||
{
|
{
|
||||||
SignInAs(null, null);
|
SignInAs(null, null);
|
||||||
var appElement = MountAndNavigateToAuthTest(AuthorizeViewCases);
|
var appElement = MountAndNavigateToAuthTest(AuthorizeViewCases);
|
||||||
|
|
@ -64,7 +68,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void AuthorizeViewCases_NoAuthorizationRule_Authenticated()
|
public void AuthorizeViewCases_NoAuthorizationRule_Authorized()
|
||||||
{
|
{
|
||||||
SignInAs("Some User", null);
|
SignInAs("Some User", null);
|
||||||
var appElement = MountAndNavigateToAuthTest(AuthorizeViewCases);
|
var appElement = MountAndNavigateToAuthTest(AuthorizeViewCases);
|
||||||
|
|
@ -73,7 +77,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void AuthorizeViewCases_RequireRole_Authenticated()
|
public void AuthorizeViewCases_RequireRole_Authorized()
|
||||||
{
|
{
|
||||||
SignInAs("Some User", "IrrelevantRole,TestRole");
|
SignInAs("Some User", "IrrelevantRole,TestRole");
|
||||||
var appElement = MountAndNavigateToAuthTest(AuthorizeViewCases);
|
var appElement = MountAndNavigateToAuthTest(AuthorizeViewCases);
|
||||||
|
|
@ -82,7 +86,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void AuthorizeViewCases_RequireRole_Unauthenticated()
|
public void AuthorizeViewCases_RequireRole_NotAuthorized()
|
||||||
{
|
{
|
||||||
SignInAs("Some User", "IrrelevantRole");
|
SignInAs("Some User", "IrrelevantRole");
|
||||||
var appElement = MountAndNavigateToAuthTest(AuthorizeViewCases);
|
var appElement = MountAndNavigateToAuthTest(AuthorizeViewCases);
|
||||||
|
|
@ -91,7 +95,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void AuthorizeViewCases_RequirePolicy_Authenticated()
|
public void AuthorizeViewCases_RequirePolicy_Authorized()
|
||||||
{
|
{
|
||||||
SignInAs("Bert", null);
|
SignInAs("Bert", null);
|
||||||
var appElement = MountAndNavigateToAuthTest(AuthorizeViewCases);
|
var appElement = MountAndNavigateToAuthTest(AuthorizeViewCases);
|
||||||
|
|
@ -100,7 +104,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void AuthorizeViewCases_RequirePolicy_Unauthenticated()
|
public void AuthorizeViewCases_RequirePolicy_NotAuthorized()
|
||||||
{
|
{
|
||||||
SignInAs("Mallory", null);
|
SignInAs("Mallory", null);
|
||||||
var appElement = MountAndNavigateToAuthTest(AuthorizeViewCases);
|
var appElement = MountAndNavigateToAuthTest(AuthorizeViewCases);
|
||||||
|
|
@ -108,6 +112,78 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
appElement.FindElement(By.CssSelector("#authorize-policy .not-authorized")).Text);
|
appElement.FindElement(By.CssSelector("#authorize-policy .not-authorized")).Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Router_AllowAnonymous_Anonymous()
|
||||||
|
{
|
||||||
|
SignInAs(null, null);
|
||||||
|
var appElement = MountAndNavigateToAuthTest(PageAllowingAnonymous);
|
||||||
|
Browser.Equal("Welcome to PageAllowingAnonymous!", () =>
|
||||||
|
appElement.FindElement(By.CssSelector("#auth-success")).Text);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Router_AllowAnonymous_Authenticated()
|
||||||
|
{
|
||||||
|
SignInAs("Bert", null);
|
||||||
|
var appElement = MountAndNavigateToAuthTest(PageAllowingAnonymous);
|
||||||
|
Browser.Equal("Welcome to PageAllowingAnonymous!", () =>
|
||||||
|
appElement.FindElement(By.CssSelector("#auth-success")).Text);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Router_RequireAuthorization_Authorized()
|
||||||
|
{
|
||||||
|
SignInAs("Bert", null);
|
||||||
|
var appElement = MountAndNavigateToAuthTest(PageRequiringAuthorization);
|
||||||
|
Browser.Equal("Welcome to PageRequiringAuthorization!", () =>
|
||||||
|
appElement.FindElement(By.CssSelector("#auth-success")).Text);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Router_RequireAuthorization_NotAuthorized()
|
||||||
|
{
|
||||||
|
SignInAs(null, null);
|
||||||
|
var appElement = MountAndNavigateToAuthTest(PageRequiringAuthorization);
|
||||||
|
Browser.Equal("Sorry, anonymous, you're not authorized.", () =>
|
||||||
|
appElement.FindElement(By.CssSelector("#auth-failure")).Text);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Router_RequirePolicy_Authorized()
|
||||||
|
{
|
||||||
|
SignInAs("Bert", null);
|
||||||
|
var appElement = MountAndNavigateToAuthTest(PageRequiringPolicy);
|
||||||
|
Browser.Equal("Welcome to PageRequiringPolicy!", () =>
|
||||||
|
appElement.FindElement(By.CssSelector("#auth-success")).Text);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Router_RequirePolicy_NotAuthorized()
|
||||||
|
{
|
||||||
|
SignInAs("Mallory", null);
|
||||||
|
var appElement = MountAndNavigateToAuthTest(PageRequiringPolicy);
|
||||||
|
Browser.Equal("Sorry, Mallory, you're not authorized.", () =>
|
||||||
|
appElement.FindElement(By.CssSelector("#auth-failure")).Text);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Router_RequireRole_Authorized()
|
||||||
|
{
|
||||||
|
SignInAs("Bert", "IrrelevantRole,TestRole");
|
||||||
|
var appElement = MountAndNavigateToAuthTest(PageRequiringRole);
|
||||||
|
Browser.Equal("Welcome to PageRequiringRole!", () =>
|
||||||
|
appElement.FindElement(By.CssSelector("#auth-success")).Text);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Router_RequireRole_NotAuthorized()
|
||||||
|
{
|
||||||
|
SignInAs("Bert", "IrrelevantRole");
|
||||||
|
var appElement = MountAndNavigateToAuthTest(PageRequiringRole);
|
||||||
|
Browser.Equal("Sorry, Bert, you're not authorized.", () =>
|
||||||
|
appElement.FindElement(By.CssSelector("#auth-failure")).Text);
|
||||||
|
}
|
||||||
|
|
||||||
IWebElement MountAndNavigateToAuthTest(string authLinkText)
|
IWebElement MountAndNavigateToAuthTest(string authLinkText)
|
||||||
{
|
{
|
||||||
Navigate(ServerPathBase);
|
Navigate(ServerPathBase);
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,14 @@
|
||||||
*@
|
*@
|
||||||
|
|
||||||
<CascadingAuthenticationState>
|
<CascadingAuthenticationState>
|
||||||
<Router AppAssembly=typeof(BasicTestApp.Program).Assembly />
|
<Router AppAssembly=typeof(BasicTestApp.Program).Assembly>
|
||||||
|
<AuthorizingContent>Authorizing...</AuthorizingContent>
|
||||||
|
<NotAuthorizedContent>
|
||||||
|
<div id="auth-failure">
|
||||||
|
Sorry, @(context.User.Identity.Name ?? "anonymous"), you're not authorized.
|
||||||
|
</div>
|
||||||
|
</NotAuthorizedContent>
|
||||||
|
</Router>
|
||||||
</CascadingAuthenticationState>
|
</CascadingAuthenticationState>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
@using Microsoft.AspNetCore.Components.Routing
|
|
||||||
<ul id="auth-links">
|
<ul id="auth-links">
|
||||||
<li><NavLink href="AuthHome" Match=NavLinkMatch.All>Home</NavLink></li>
|
<li><a href="AuthHome">Home</a></li>
|
||||||
<li><NavLink href="CascadingAuthenticationStateConsumer">Cascading authentication state</NavLink></li>
|
<li><a href="CascadingAuthenticationStateConsumer">Cascading authentication state</a></li>
|
||||||
<li><NavLink href="AuthorizeViewCases">AuthorizeView cases</NavLink></li>
|
<li><a href="AuthorizeViewCases">AuthorizeView cases</a></li>
|
||||||
|
<li><a href="PageAllowingAnonymous">Page allowing anonymous</a></li>
|
||||||
|
<li><a href="PageRequiringAuthorization">Page requiring any authentication</a></li>
|
||||||
|
<li><a href="PageRequiringPolicy">Page requiring policy</a></li>
|
||||||
|
<li><a href="PageRequiringRole">Page requiring role</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p>To change the underlying authentication state, <a target="_blank" href="/Authentication">go here</a>.</p>
|
<p>To change the underlying authentication state, <a target="_blank" href="/Authentication">go here</a>.</p>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
@page "/PageAllowingAnonymous"
|
||||||
|
@using Microsoft.AspNetCore.Authorization
|
||||||
|
@attribute [Authorize(Roles = "NobodyIsInThisRole")]
|
||||||
|
@attribute [AllowAnonymous]
|
||||||
|
<div id="auth-success">Welcome to PageAllowingAnonymous!</div>
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
@page "/PageRequiringAuthorization"
|
||||||
|
@using Microsoft.AspNetCore.Authorization
|
||||||
|
@attribute [Authorize]
|
||||||
|
<div id="auth-success">Welcome to PageRequiringAuthorization!</div>
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
@page "/PageRequiringPolicy"
|
||||||
|
@using Microsoft.AspNetCore.Authorization
|
||||||
|
@attribute [Authorize(Policy = "NameMustStartWithB")]
|
||||||
|
<div id="auth-success">Welcome to PageRequiringPolicy!</div>
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
@page "/PageRequiringRole"
|
||||||
|
@using Microsoft.AspNetCore.Authorization
|
||||||
|
@attribute [Authorize(Roles = "TestRole")]
|
||||||
|
<div id="auth-success">Welcome to PageRequiringRole!</div>
|
||||||
Loading…
Reference in New Issue