Skip to content

Commit d18a033

Browse files
Integrate AuthorizeView with actual authorization (#10487)
1 parent 405d8bb commit d18a033

18 files changed

+529
-51
lines changed

src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHostBuilder.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
using Microsoft.AspNetCore.Components;
99
using Microsoft.AspNetCore.Components.Routing;
1010
using Microsoft.Extensions.DependencyInjection;
11+
using Microsoft.Extensions.DependencyInjection.Extensions;
12+
using Microsoft.Extensions.Logging;
1113
using Microsoft.JSInterop;
1214

1315
namespace Microsoft.AspNetCore.Blazor.Hosting
@@ -92,6 +94,7 @@ private void CreateServiceProvider()
9294
services.AddSingleton<IComponentContext, WebAssemblyComponentContext>();
9395
services.AddSingleton<IUriHelper>(WebAssemblyUriHelper.Instance);
9496
services.AddSingleton<INavigationInterception>(WebAssemblyNavigationInterception.Instance);
97+
services.AddSingleton<ILoggerFactory, WebAssemblyLoggerFactory>();
9598
services.AddSingleton<HttpClient>(s =>
9699
{
97100
// Creating the URI helper needs to wait until the JS Runtime is initialized, so defer it.
@@ -102,6 +105,10 @@ private void CreateServiceProvider()
102105
};
103106
});
104107

108+
// Needed for authorization
109+
services.AddOptions();
110+
services.TryAdd(ServiceDescriptor.Singleton(typeof(ILogger<>), typeof(WebAssemblyConsoleLogger<>)));
111+
105112
foreach (var configureServicesAction in _configureServicesActions)
106113
{
107114
configureServicesAction(_BrowserHostBuilderContext, services);
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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 Microsoft.Extensions.Logging;
6+
7+
namespace Microsoft.AspNetCore.Blazor.Services
8+
{
9+
internal class WebAssemblyConsoleLogger<T> : ILogger<T>, ILogger
10+
{
11+
public IDisposable BeginScope<TState>(TState state)
12+
{
13+
return NoOpDisposable.Instance;
14+
}
15+
16+
public bool IsEnabled(LogLevel logLevel)
17+
{
18+
return logLevel >= LogLevel.Warning;
19+
}
20+
21+
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
22+
{
23+
var formattedMessage = formatter(state, exception);
24+
Console.WriteLine($"[{logLevel}] {formattedMessage}");
25+
}
26+
27+
private class NoOpDisposable : IDisposable
28+
{
29+
public static NoOpDisposable Instance = new NoOpDisposable();
30+
31+
public void Dispose() { }
32+
}
33+
}
34+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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.Extensions.Logging;
5+
6+
namespace Microsoft.AspNetCore.Blazor.Services
7+
{
8+
internal class WebAssemblyLoggerFactory : ILoggerFactory
9+
{
10+
public void AddProvider(ILoggerProvider provider)
11+
{
12+
// No-op
13+
}
14+
15+
public ILogger CreateLogger(string categoryName)
16+
=> new WebAssemblyConsoleLogger<object>();
17+
18+
public void Dispose()
19+
{
20+
// No-op
21+
}
22+
}
23+
}

src/Components/Components/ref/Microsoft.AspNetCore.Components.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
</PropertyGroup>
66
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
77
<Compile Include="Microsoft.AspNetCore.Components.netstandard2.0.cs" />
8+
<Reference Include="Microsoft.AspNetCore.Authorization" />
89
<Reference Include="Microsoft.JSInterop" />
910
<Reference Include="System.ComponentModel.Annotations" />
1011
</ItemGroup>

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,13 @@ public AuthorizeView() { }
5959
[Microsoft.AspNetCore.Components.ParameterAttribute]
6060
public Microsoft.AspNetCore.Components.RenderFragment<Microsoft.AspNetCore.Components.AuthenticationState> ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; } }
6161
[Microsoft.AspNetCore.Components.ParameterAttribute]
62-
public Microsoft.AspNetCore.Components.RenderFragment NotAuthorized { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; } }
62+
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; } }
6369
protected override void BuildRenderTree(Microsoft.AspNetCore.Components.RenderTree.RenderTreeBuilder builder) { }
6470
[System.Diagnostics.DebuggerStepThroughAttribute]
6571
protected override System.Threading.Tasks.Task OnParametersSetAsync() { throw null; }
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 System;
5+
using Microsoft.AspNetCore.Authorization;
6+
7+
namespace Microsoft.AspNetCore.Components
8+
{
9+
// This is so the AuthorizeView can avoid implementing IAuthorizeData (even privately)
10+
internal class AuthorizeDataAdapter : IAuthorizeData
11+
{
12+
private readonly AuthorizeView _component;
13+
14+
public AuthorizeDataAdapter(AuthorizeView component)
15+
{
16+
_component = component ?? throw new ArgumentNullException(nameof(component));
17+
}
18+
19+
public string Policy
20+
{
21+
get => _component.Policy;
22+
set => throw new NotSupportedException();
23+
}
24+
25+
public string Roles
26+
{
27+
get => _component.Roles;
28+
set => throw new NotSupportedException();
29+
}
30+
31+
// AuthorizeView doesn't expose any such parameter, as it wouldn't be used anyway,
32+
// since we already have the ClaimsPrincipal by the time AuthorizeView gets involved.
33+
public string AuthenticationSchemes
34+
{
35+
get => null;
36+
set => throw new NotSupportedException();
37+
}
38+
}
39+
}

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

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,26 @@
11
@namespace Microsoft.AspNetCore.Components
2+
@using System.Security.Claims
3+
@using Microsoft.AspNetCore.Authorization
4+
@inject IAuthorizationService AuthorizationService
5+
@inject IAuthorizationPolicyProvider AuthorizationPolicyProvider
26

37
@if (currentAuthenticationState == null)
48
{
59
@Authorizing
610
}
7-
else if (IsAuthorized())
11+
else if (isAuthorized)
812
{
913
@((Authorized ?? ChildContent)?.Invoke(currentAuthenticationState))
1014
}
1115
else
1216
{
13-
@NotAuthorized
17+
@(NotAuthorized?.Invoke(currentAuthenticationState))
1418
}
1519

1620
@functions {
21+
private IAuthorizeData[] selfAsAuthorizeData;
1722
private AuthenticationState currentAuthenticationState;
23+
private bool isAuthorized;
1824

1925
[CascadingParameter] private Task<AuthenticationState> AuthenticationState { get; set; }
2026

@@ -26,7 +32,7 @@ else
2632
/// <summary>
2733
/// The content that will be displayed if the user is not authorized.
2834
/// </summary>
29-
[Parameter] public RenderFragment NotAuthorized { get; private set; }
35+
[Parameter] public RenderFragment<AuthenticationState> NotAuthorized { get; private set; }
3036

3137
/// <summary>
3238
/// The content that will be displayed if the user is authorized.
@@ -39,6 +45,29 @@ else
3945
/// </summary>
4046
[Parameter] public RenderFragment Authorizing { get; private set; }
4147

48+
/// <summary>
49+
/// The policy name that determines whether the content can be displayed.
50+
/// </summary>
51+
[Parameter] public string Policy { get; private set; }
52+
53+
/// <summary>
54+
/// A comma delimited list of roles that are allowed to display the content.
55+
/// </summary>
56+
[Parameter] public string Roles { get; private set; }
57+
58+
/// <summary>
59+
/// The resource to which access is being controlled.
60+
/// </summary>
61+
[Parameter] public object Resource { get; private set; }
62+
63+
protected override void OnInit()
64+
{
65+
selfAsAuthorizeData = new[]
66+
{
67+
new AuthorizeDataAdapter((AuthorizeView)(object)this)
68+
};
69+
}
70+
4271
protected override async Task OnParametersSetAsync()
4372
{
4473
// We allow 'ChildContent' for convenience in basic cases, and 'Authorized' for symmetry
@@ -54,15 +83,17 @@ else
5483
currentAuthenticationState = null;
5584

5685
// Then render in completed state
86+
// Importantly, we *don't* call StateHasChanged between the following async steps,
87+
// otherwise we'd display an incorrect UI state while waiting for IsAuthorizedAsync
5788
currentAuthenticationState = await AuthenticationState;
89+
isAuthorized = await IsAuthorizedAsync(currentAuthenticationState.User);
5890
}
5991

60-
private bool IsAuthorized()
92+
private async Task<bool> IsAuthorizedAsync(ClaimsPrincipal user)
6193
{
62-
// TODO: Support various authorization condition parameters, equivalent to those offered
63-
// by the [Authorize] attribute, e.g., "Roles" and "Policy". This is on hold until we're
64-
// able to reference the policy evaluator APIs from this package.
65-
66-
return currentAuthenticationState.User?.Identity?.IsAuthenticated == true;
94+
var policy = await AuthorizationPolicy.CombineAsync(
95+
AuthorizationPolicyProvider, selfAsAuthorizeData);
96+
var result = await AuthorizationService.AuthorizeAsync(user, Resource, policy);
97+
return result.Succeeded;
6798
}
6899
}

src/Components/Components/src/Microsoft.AspNetCore.Components.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
</PropertyGroup>
1111

1212
<ItemGroup>
13+
<Reference Include="Microsoft.AspNetCore.Authorization" />
1314
<Reference Include="Microsoft.JSInterop" />
1415
<Reference Include="System.ComponentModel.Annotations" />
1516
</ItemGroup>

0 commit comments

Comments
 (0)