Skip to content

Commit bc011b5

Browse files
SteveSandersonMSrynowak
authored andcommitted
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
1 parent 9969e99 commit bc011b5

18 files changed

+502
-232
lines changed

eng/GenAPI.exclusions.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ T:Microsoft.AspNetCore.Mvc.ApplicationModels.PageParameterModel
55
T:Microsoft.AspNetCore.Mvc.ApplicationModels.PagePropertyModel
66
# Manually implemented - https://github.com/aspnet/AspNetCore/issues/8825
77
T:Microsoft.AspNetCore.Components.AuthorizeView
8+
T:Microsoft.AspNetCore.Components.AuthorizeViewCore
89
T:Microsoft.AspNetCore.Components.CascadingAuthenticationState
910
T:Microsoft.AspNetCore.Components.CascadingValue`1
1011
T:Microsoft.AspNetCore.Components.Forms.DataAnnotationsValidator
@@ -18,6 +19,6 @@ T:Microsoft.AspNetCore.Components.Forms.InputText
1819
T:Microsoft.AspNetCore.Components.Forms.InputTextArea
1920
T:Microsoft.AspNetCore.Components.Forms.ValidationMessage`1
2021
T:Microsoft.AspNetCore.Components.Forms.ValidationSummary
21-
T:Microsoft.AspNetCore.Components.Layouts.LayoutDisplay
22+
T:Microsoft.AspNetCore.Components.PageDisplay
2223
T:Microsoft.AspNetCore.Components.Routing.NavLink
2324
T:Microsoft.AspNetCore.Components.Routing.Router

src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.Manual.cs

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,24 +49,31 @@ public readonly partial struct RenderTreeFrame
4949
// Built-in components: https://github.com/aspnet/AspNetCore/issues/8825
5050
namespace Microsoft.AspNetCore.Components
5151
{
52-
public partial class AuthorizeView : Microsoft.AspNetCore.Components.ComponentBase
52+
public partial class AuthorizeView : Microsoft.AspNetCore.Components.AuthorizeViewCore
5353
{
5454
public AuthorizeView() { }
5555
[Microsoft.AspNetCore.Components.ParameterAttribute]
56+
public string Policy { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; } }
57+
[Microsoft.AspNetCore.Components.ParameterAttribute]
58+
public object Resource { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; } }
59+
[Microsoft.AspNetCore.Components.ParameterAttribute]
60+
public string Roles { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; } }
61+
protected override Microsoft.AspNetCore.Authorization.IAuthorizeData[] GetAuthorizeData() { throw null; }
62+
}
63+
64+
public abstract partial class AuthorizeViewCore : Microsoft.AspNetCore.Components.ComponentBase
65+
{
66+
public AuthorizeViewCore() { }
67+
[Microsoft.AspNetCore.Components.ParameterAttribute]
5668
public Microsoft.AspNetCore.Components.RenderFragment<Microsoft.AspNetCore.Components.AuthenticationState> Authorized { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; } }
5769
[Microsoft.AspNetCore.Components.ParameterAttribute]
5870
public Microsoft.AspNetCore.Components.RenderFragment Authorizing { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; } }
5971
[Microsoft.AspNetCore.Components.ParameterAttribute]
6072
public Microsoft.AspNetCore.Components.RenderFragment<Microsoft.AspNetCore.Components.AuthenticationState> ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; } }
6173
[Microsoft.AspNetCore.Components.ParameterAttribute]
6274
public Microsoft.AspNetCore.Components.RenderFragment<Microsoft.AspNetCore.Components.AuthenticationState> NotAuthorized { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; } }
63-
[Microsoft.AspNetCore.Components.ParameterAttribute]
64-
public string Policy { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; } }
65-
[Microsoft.AspNetCore.Components.ParameterAttribute]
66-
public string Roles { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; } }
67-
[Microsoft.AspNetCore.Components.ParameterAttribute]
68-
public object Resource { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; } }
6975
protected override void BuildRenderTree(Microsoft.AspNetCore.Components.RenderTree.RenderTreeBuilder builder) { }
76+
protected abstract Microsoft.AspNetCore.Authorization.IAuthorizeData[] GetAuthorizeData();
7077
[System.Diagnostics.DebuggerStepThroughAttribute]
7178
protected override System.Threading.Tasks.Task OnParametersSetAsync() { throw null; }
7279
}
@@ -218,9 +225,13 @@ void System.IDisposable.Dispose() { }
218225

219226
namespace Microsoft.AspNetCore.Components.Layouts
220227
{
221-
public partial class LayoutDisplay : Microsoft.AspNetCore.Components.IComponent
228+
public partial class PageDisplay : Microsoft.AspNetCore.Components.IComponent
222229
{
223-
public LayoutDisplay() { }
230+
public PageDisplay() { }
231+
[Microsoft.AspNetCore.Components.ParameterAttribute]
232+
public Microsoft.AspNetCore.Components.RenderFragment AuthorizingContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; }}
233+
[Microsoft.AspNetCore.Components.ParameterAttribute]
234+
public Microsoft.AspNetCore.Components.RenderFragment<Microsoft.AspNetCore.Components.AuthenticationState> NotAuthorizedContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; }}
224235
[Microsoft.AspNetCore.Components.ParameterAttribute]
225236
public System.Type Page { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; }}
226237
[Microsoft.AspNetCore.Components.ParameterAttribute]
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Concurrent;
6+
using System.Linq;
7+
using System.Reflection;
8+
using Microsoft.AspNetCore.Authorization;
9+
10+
namespace Microsoft.AspNetCore.Components.Auth
11+
{
12+
internal static class AttributeAuthorizeDataCache
13+
{
14+
private static ConcurrentDictionary<Type, IAuthorizeData[]> _cache
15+
= new ConcurrentDictionary<Type, IAuthorizeData[]>();
16+
17+
public static IAuthorizeData[] GetAuthorizeDataForType(Type type)
18+
{
19+
IAuthorizeData[] result;
20+
if (!_cache.TryGetValue(type, out result))
21+
{
22+
result = ComputeAuthorizeDataForType(type);
23+
_cache[type] = result; // Safe race - doesn't matter if it overwrites
24+
}
25+
26+
return result;
27+
}
28+
29+
private static IAuthorizeData[] ComputeAuthorizeDataForType(Type type)
30+
{
31+
// Allow Anonymous skips all authorization
32+
var allAttributes = type.GetCustomAttributes(inherit: true);
33+
if (allAttributes.OfType<IAllowAnonymous>().Any())
34+
{
35+
return null;
36+
}
37+
38+
var authorizeDataAttributes = allAttributes.OfType<IAuthorizeData>().ToArray();
39+
return authorizeDataAttributes.Length > 0 ? authorizeDataAttributes : null;
40+
}
41+
}
42+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using Microsoft.AspNetCore.Authorization;
5+
6+
namespace Microsoft.AspNetCore.Components
7+
{
8+
/// <summary>
9+
/// Displays differing content depending on the user's authorization status.
10+
/// </summary>
11+
public class AuthorizeView : AuthorizeViewCore
12+
{
13+
private readonly IAuthorizeData[] selfAsAuthorizeData;
14+
15+
/// <summary>
16+
/// Constructs an instance of <see cref="AuthorizeView"/>.
17+
/// </summary>
18+
public AuthorizeView()
19+
{
20+
selfAsAuthorizeData = new[] { new AuthorizeDataAdapter(this) };
21+
}
22+
23+
/// <summary>
24+
/// The policy name that determines whether the content can be displayed.
25+
/// </summary>
26+
[Parameter] public string Policy { get; private set; }
27+
28+
/// <summary>
29+
/// A comma delimited list of roles that are allowed to display the content.
30+
/// </summary>
31+
[Parameter] public string Roles { get; private set; }
32+
33+
/// <summary>
34+
/// Gets the data used for authorization.
35+
/// </summary>
36+
protected override IAuthorizeData[] GetAuthorizeData()
37+
=> selfAsAuthorizeData;
38+
}
39+
}

src/Components/Components/src/Auth/AuthorizeView.razor

Lines changed: 0 additions & 99 deletions
This file was deleted.
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Security.Claims;
6+
using System.Threading.Tasks;
7+
using Microsoft.AspNetCore.Authorization;
8+
using Microsoft.AspNetCore.Components.RenderTree;
9+
10+
namespace Microsoft.AspNetCore.Components
11+
{
12+
/// <summary>
13+
/// A base class for components that display differing content depending on the user's authorization status.
14+
/// </summary>
15+
public abstract class AuthorizeViewCore : ComponentBase
16+
{
17+
private AuthenticationState currentAuthenticationState;
18+
private bool isAuthorized;
19+
20+
/// <summary>
21+
/// The content that will be displayed if the user is authorized.
22+
/// </summary>
23+
[Parameter] public RenderFragment<AuthenticationState> ChildContent { get; private set; }
24+
25+
/// <summary>
26+
/// The content that will be displayed if the user is not authorized.
27+
/// </summary>
28+
[Parameter] public RenderFragment<AuthenticationState> NotAuthorized { get; private set; }
29+
30+
/// <summary>
31+
/// The content that will be displayed if the user is authorized.
32+
/// If you specify a value for this parameter, do not also specify a value for <see cref="ChildContent"/>.
33+
/// </summary>
34+
[Parameter] public RenderFragment<AuthenticationState> Authorized { get; private set; }
35+
36+
/// <summary>
37+
/// The content that will be displayed while asynchronous authorization is in progress.
38+
/// </summary>
39+
[Parameter] public RenderFragment Authorizing { get; private set; }
40+
41+
/// <summary>
42+
/// The resource to which access is being controlled.
43+
/// </summary>
44+
[Parameter] public object Resource { get; private set; }
45+
46+
[CascadingParameter] private Task<AuthenticationState> AuthenticationState { get; set; }
47+
48+
[Inject] private IAuthorizationPolicyProvider AuthorizationPolicyProvider { get; set; }
49+
50+
[Inject] private IAuthorizationService AuthorizationService { get; set; }
51+
52+
/// <inheritdoc />
53+
protected override void BuildRenderTree(RenderTreeBuilder builder)
54+
{
55+
if (currentAuthenticationState == null)
56+
{
57+
builder.AddContent(0, Authorizing);
58+
}
59+
else if (isAuthorized)
60+
{
61+
var authorizedContent = Authorized ?? ChildContent;
62+
builder.AddContent(1, authorizedContent?.Invoke(currentAuthenticationState));
63+
}
64+
else
65+
{
66+
builder.AddContent(2, NotAuthorized?.Invoke(currentAuthenticationState));
67+
}
68+
}
69+
70+
/// <inheritdoc />
71+
protected override async Task OnParametersSetAsync()
72+
{
73+
// We allow 'ChildContent' for convenience in basic cases, and 'Authorized' for symmetry
74+
// with 'NotAuthorized' in other cases. Besides naming, they are equivalent. To avoid
75+
// confusion, explicitly prevent the case where both are supplied.
76+
if (ChildContent != null && Authorized != null)
77+
{
78+
throw new InvalidOperationException($"Do not specify both '{nameof(Authorized)}' and '{nameof(ChildContent)}'.");
79+
}
80+
81+
if (AuthenticationState == null)
82+
{
83+
throw new InvalidOperationException($"Authorization requires a cascading parameter of type Task<{nameof(AuthenticationState)}>. Consider using {typeof(CascadingAuthenticationState).Name} to supply this.");
84+
}
85+
86+
// First render in pending state
87+
// If the task has already completed, this render will be skipped
88+
currentAuthenticationState = null;
89+
90+
// Then render in completed state
91+
// Importantly, we *don't* call StateHasChanged between the following async steps,
92+
// otherwise we'd display an incorrect UI state while waiting for IsAuthorizedAsync
93+
currentAuthenticationState = await AuthenticationState;
94+
isAuthorized = await IsAuthorizedAsync(currentAuthenticationState.User);
95+
}
96+
97+
/// <summary>
98+
/// Gets the data required to apply authorization rules.
99+
/// </summary>
100+
protected abstract IAuthorizeData[] GetAuthorizeData();
101+
102+
private async Task<bool> IsAuthorizedAsync(ClaimsPrincipal user)
103+
{
104+
var authorizeData = GetAuthorizeData();
105+
var policy = await AuthorizationPolicy.CombineAsync(
106+
AuthorizationPolicyProvider, authorizeData);
107+
var result = await AuthorizationService.AuthorizeAsync(user, Resource, policy);
108+
return result.Succeeded;
109+
}
110+
}
111+
}

0 commit comments

Comments
 (0)