-
Notifications
You must be signed in to change notification settings - Fork 9.8k
/
AuthorizeViewCore.cs
136 lines (116 loc) · 5.9 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
131
132
133
134
135
136
// 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.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; }
[Inject] private IAuthorizationService AuthorizationService { get; set; }
/// <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 (currentAuthenticationState == null)
{
builder.AddContent(0, Authorizing);
}
else if (isAuthorized)
{
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.");
}
// 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();
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.");
}
}
}
}
}