-
Notifications
You must be signed in to change notification settings - Fork 9.8k
/
AuthorizeViewCore.cs
130 lines (109 loc) · 5.26 KB
/
AuthorizeViewCore.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components.Rendering;
namespace Microsoft.AspNetCore.Components.Authorization;
/// <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; set; }
/// <summary>
/// The content that will be displayed if the user is not authorized.
/// </summary>
[Parameter] public RenderFragment<AuthenticationState>? NotAuthorized { get; 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; set; }
/// <summary>
/// The content that will be displayed while asynchronous authorization is in progress.
/// </summary>
[Parameter] public RenderFragment? Authorizing { get; set; }
/// <summary>
/// The resource to which access is being controlled.
/// </summary>
[Parameter] public object? Resource { get; set; }
[CascadingParameter] private Task<AuthenticationState>? AuthenticationState { get; set; }
[Inject] private IAuthorizationPolicyProvider AuthorizationPolicyProvider { get; set; } = default!;
[Inject] private IAuthorizationService AuthorizationService { get; set; } = default!;
/// <inheritdoc />
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
// We're using the same sequence number for each of the content items here
// so that we can update existing instances if they are the same shape
if (isAuthorized == null)
{
builder.AddContent(0, Authorizing);
}
else if (isAuthorized == true)
{
var authorized = Authorized ?? ChildContent;
builder.AddContent(0, authorized?.Invoke(currentAuthenticationState!));
}
else
{
builder.AddContent(0, 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.");
}
// Clear the previous result of authorization
// This will cause the Authorizing state to be displayed until the authorization has been completed
isAuthorized = null;
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();
if (authorizeData == null)
{
// No authorization applies, so no need to consult the authorization service
return true;
}
EnsureNoAuthenticationSchemeSpecified(authorizeData);
var policy = await AuthorizationPolicy.CombineAsync(
AuthorizationPolicyProvider, authorizeData);
var result = await AuthorizationService.AuthorizeAsync(user, Resource, policy!);
return result.Succeeded;
}
private static void EnsureNoAuthenticationSchemeSpecified(IAuthorizeData[] authorizeData)
{
// It's not meaningful to specify a nonempty scheme, since by the time Components
// authorization runs, we already have a specific ClaimsPrincipal (we're stateful).
// To avoid any confusion, ensure the developer isn't trying to specify a scheme.
for (var i = 0; i < authorizeData.Length; i++)
{
var entry = authorizeData[i];
if (!string.IsNullOrEmpty(entry.AuthenticationSchemes))
{
throw new NotSupportedException($"The authorization data specifies an authentication scheme with value '{entry.AuthenticationSchemes}'. Authentication schemes cannot be specified for components.");
}
}
}
}