From abf67cbdaca1cd01ba517bf3ff9fbfeb12fcd6e8 Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Tue, 1 Nov 2022 11:30:39 -0700 Subject: [AuthZ] Add IRequirementData (#44342) --- .../Core/src/IAuthorizationRequirementData.cs | 18 ++++ .../src/PublicAPI/net462/PublicAPI.Unshipped.txt | 2 + .../src/PublicAPI/net7.0/PublicAPI.Unshipped.txt | 2 + .../netstandard2.0/PublicAPI.Unshipped.txt | 2 + .../Policy/src/AuthorizationMiddleware.cs | 18 ++++ .../test/AuthorizationMiddlewareTests.cs | 108 ++++++++++++++++++++- 6 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 src/Security/Authorization/Core/src/IAuthorizationRequirementData.cs diff --git a/src/Security/Authorization/Core/src/IAuthorizationRequirementData.cs b/src/Security/Authorization/Core/src/IAuthorizationRequirementData.cs new file mode 100644 index 0000000000..7b21894c06 --- /dev/null +++ b/src/Security/Authorization/Core/src/IAuthorizationRequirementData.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace Microsoft.AspNetCore.Authorization; + +/// +/// Interface that can produce authorization requirements. +/// +public interface IAuthorizationRequirementData +{ + /// + /// Returns that should be satisfied for authorization. + /// + /// used for authorization. + IEnumerable GetRequirements(); +} diff --git a/src/Security/Authorization/Core/src/PublicAPI/net462/PublicAPI.Unshipped.txt b/src/Security/Authorization/Core/src/PublicAPI/net462/PublicAPI.Unshipped.txt index 38f8cdf2c0..1b9c663162 100644 --- a/src/Security/Authorization/Core/src/PublicAPI/net462/PublicAPI.Unshipped.txt +++ b/src/Security/Authorization/Core/src/PublicAPI/net462/PublicAPI.Unshipped.txt @@ -1,6 +1,8 @@ #nullable enable Microsoft.AspNetCore.Authorization.AuthorizationBuilder Microsoft.AspNetCore.Authorization.AuthorizationBuilder.AuthorizationBuilder(Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> void +Microsoft.AspNetCore.Authorization.IAuthorizationRequirementData +Microsoft.AspNetCore.Authorization.IAuthorizationRequirementData.GetRequirements() -> System.Collections.Generic.IEnumerable! Microsoft.AspNetCore.Authorization.Infrastructure.PassThroughAuthorizationHandler.PassThroughAuthorizationHandler(Microsoft.Extensions.Options.IOptions! options) -> void static Microsoft.AspNetCore.Authorization.AuthorizationPolicy.CombineAsync(Microsoft.AspNetCore.Authorization.IAuthorizationPolicyProvider! policyProvider, System.Collections.Generic.IEnumerable! authorizeData, System.Collections.Generic.IEnumerable! policies) -> System.Threading.Tasks.Task! virtual Microsoft.AspNetCore.Authorization.AuthorizationBuilder.AddDefaultPolicy(string! name, Microsoft.AspNetCore.Authorization.AuthorizationPolicy! policy) -> Microsoft.AspNetCore.Authorization.AuthorizationBuilder! diff --git a/src/Security/Authorization/Core/src/PublicAPI/net7.0/PublicAPI.Unshipped.txt b/src/Security/Authorization/Core/src/PublicAPI/net7.0/PublicAPI.Unshipped.txt index ae0a6aaae0..1755112163 100644 --- a/src/Security/Authorization/Core/src/PublicAPI/net7.0/PublicAPI.Unshipped.txt +++ b/src/Security/Authorization/Core/src/PublicAPI/net7.0/PublicAPI.Unshipped.txt @@ -3,6 +3,8 @@ Microsoft.AspNetCore.Authorization.AuthorizationBuilder Microsoft.AspNetCore.Authorization.AuthorizationBuilder.AuthorizationBuilder(Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> void Microsoft.AspNetCore.Authorization.IAuthorizationPolicyProvider.AllowsCachingPolicies.get -> bool Microsoft.AspNetCore.Authorization.Infrastructure.PassThroughAuthorizationHandler.PassThroughAuthorizationHandler(Microsoft.Extensions.Options.IOptions! options) -> void +Microsoft.AspNetCore.Authorization.IAuthorizationRequirementData +Microsoft.AspNetCore.Authorization.IAuthorizationRequirementData.GetRequirements() -> System.Collections.Generic.IEnumerable! static Microsoft.AspNetCore.Authorization.AuthorizationPolicy.CombineAsync(Microsoft.AspNetCore.Authorization.IAuthorizationPolicyProvider! policyProvider, System.Collections.Generic.IEnumerable! authorizeData, System.Collections.Generic.IEnumerable! policies) -> System.Threading.Tasks.Task! virtual Microsoft.AspNetCore.Authorization.AuthorizationBuilder.AddDefaultPolicy(string! name, Microsoft.AspNetCore.Authorization.AuthorizationPolicy! policy) -> Microsoft.AspNetCore.Authorization.AuthorizationBuilder! virtual Microsoft.AspNetCore.Authorization.AuthorizationBuilder.AddDefaultPolicy(string! name, System.Action! configurePolicy) -> Microsoft.AspNetCore.Authorization.AuthorizationBuilder! diff --git a/src/Security/Authorization/Core/src/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt b/src/Security/Authorization/Core/src/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt index 38f8cdf2c0..b2c12aec57 100644 --- a/src/Security/Authorization/Core/src/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/Security/Authorization/Core/src/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt @@ -2,6 +2,8 @@ Microsoft.AspNetCore.Authorization.AuthorizationBuilder Microsoft.AspNetCore.Authorization.AuthorizationBuilder.AuthorizationBuilder(Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> void Microsoft.AspNetCore.Authorization.Infrastructure.PassThroughAuthorizationHandler.PassThroughAuthorizationHandler(Microsoft.Extensions.Options.IOptions! options) -> void +Microsoft.AspNetCore.Authorization.IAuthorizationRequirementData +Microsoft.AspNetCore.Authorization.IAuthorizationRequirementData.GetRequirements() -> System.Collections.Generic.IEnumerable! static Microsoft.AspNetCore.Authorization.AuthorizationPolicy.CombineAsync(Microsoft.AspNetCore.Authorization.IAuthorizationPolicyProvider! policyProvider, System.Collections.Generic.IEnumerable! authorizeData, System.Collections.Generic.IEnumerable! policies) -> System.Threading.Tasks.Task! virtual Microsoft.AspNetCore.Authorization.AuthorizationBuilder.AddDefaultPolicy(string! name, Microsoft.AspNetCore.Authorization.AuthorizationPolicy! policy) -> Microsoft.AspNetCore.Authorization.AuthorizationBuilder! virtual Microsoft.AspNetCore.Authorization.AuthorizationBuilder.AddDefaultPolicy(string! name, System.Action! configurePolicy) -> Microsoft.AspNetCore.Authorization.AuthorizationBuilder! diff --git a/src/Security/Authorization/Policy/src/AuthorizationMiddleware.cs b/src/Security/Authorization/Policy/src/AuthorizationMiddleware.cs index 5a3c17a4d5..2ff13e3779 100644 --- a/src/Security/Authorization/Policy/src/AuthorizationMiddleware.cs +++ b/src/Security/Authorization/Policy/src/AuthorizationMiddleware.cs @@ -111,6 +111,24 @@ public class AuthorizationMiddleware policy = await AuthorizationPolicy.CombineAsync(_policyProvider, authorizeData, policies); + var requirementData = endpoint?.Metadata?.GetOrderedMetadata() ?? Array.Empty(); + if (requirementData.Count > 0) + { + var reqPolicy = new AuthorizationPolicyBuilder(); + foreach (var rd in requirementData) + { + foreach (var r in rd.GetRequirements()) + { + reqPolicy.AddRequirements(r); + } + } + + // Combine policy with requirements or just use requirements if no policy + policy = (policy is null) + ? reqPolicy.Build() + : AuthorizationPolicy.Combine(policy, reqPolicy.Build()); + } + // Cache the computed policy if (policy != null && canCachePolicy) { diff --git a/src/Security/Authorization/test/AuthorizationMiddlewareTests.cs b/src/Security/Authorization/test/AuthorizationMiddlewareTests.cs index 2d8fb8a570..bc599a6a11 100644 --- a/src/Security/Authorization/test/AuthorizationMiddlewareTests.cs +++ b/src/Security/Authorization/test/AuthorizationMiddlewareTests.cs @@ -3,6 +3,7 @@ using System.Security.Claims; using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization.Infrastructure; using Microsoft.AspNetCore.Authorization.Policy; using Microsoft.AspNetCore.Authorization.Test.TestObjects; using Microsoft.AspNetCore.Builder; @@ -244,7 +245,9 @@ public class AuthorizationMiddlewareTests var next = new TestRequestDelegate(); var logger = new Mock>(); - var endpoint = CreateEndpoint(new AuthorizeAttribute("whatever")); + var req = new AssertionRequirement(_ => true); + + var endpoint = CreateEndpoint(new AuthorizeAttribute("whatever"), new ReqAttribute(req)); var services = new ServiceCollection() .AddAuthorization() .AddSingleton(CreateDataSource(endpoint)).BuildServiceProvider(); @@ -384,6 +387,109 @@ public class AuthorizationMiddlewareTests Assert.True(calledPolicy); } + public class ReqAttribute : Attribute, IAuthorizationRequirementData + { + IEnumerable _reqs; + public ReqAttribute(params IAuthorizationRequirement[] req) + => _reqs = req; + + public IEnumerable GetRequirements() + => _reqs; + } + + public class ReqAuthorizeAttribute : AuthorizeAttribute, IAuthorizationRequirementData + { + IEnumerable _reqs; + public ReqAuthorizeAttribute(params IAuthorizationRequirement[] req) + => _reqs = req; + + public IEnumerable GetRequirements() + => _reqs; + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task CanApplyRequirementAttributeDirectlyToEndpoint(bool assertSuccess) + { + // Arrange + var calledPolicy = false; + var req = new AssertionRequirement(_ => { calledPolicy = true; return assertSuccess; }); + var policyProvider = new Mock(); + var next = new TestRequestDelegate(); + var middleware = CreateMiddleware(next.Invoke, policyProvider.Object); + var context = GetHttpContext(anonymous: true, endpoint: CreateEndpoint(new ReqAttribute(req))); + + // Act & Assert + await middleware.Invoke(context); + Assert.True(calledPolicy); + Assert.Equal(assertSuccess, next.Called); + } + + [Theory] + [InlineData(true, true)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(false, false)] + public async Task CanApplyMultipleRequirements(bool assertSuccess, bool assert2Success) + { + // Arrange + var calledPolicy = false; + var req = new AssertionRequirement(_ => { calledPolicy = true; return assertSuccess; }); + var req2 = new AssertionRequirement(_ => { calledPolicy = true; return assert2Success; }); + var policyProvider = new Mock(); + var next = new TestRequestDelegate(); + var middleware = CreateMiddleware(next.Invoke, policyProvider.Object); + var context = GetHttpContext(anonymous: true, endpoint: CreateEndpoint(new ReqAttribute(req, req2))); + + // Act & Assert + await middleware.Invoke(context); + Assert.True(calledPolicy); + Assert.Equal(assertSuccess && assert2Success, next.Called); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task CanApplyRequirementAttributeWithAuthorizeDirectlyToEndpoint(bool assertSuccess) + { + // Arrange + var calledPolicy = false; + var req = new AssertionRequirement(_ => { calledPolicy = true; return assertSuccess; }); + var policyProvider = new Mock(); + policyProvider.Setup(p => p.GetDefaultPolicyAsync()).ReturnsAsync(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build()); + var next = new TestRequestDelegate(); + var middleware = CreateMiddleware(next.Invoke, policyProvider.Object); + var context = GetHttpContext(anonymous: false, endpoint: CreateEndpoint(new AuthorizeAttribute(), new ReqAttribute(req))); + + // Act & Assert + await middleware.Invoke(context); + Assert.True(calledPolicy); + Assert.Equal(assertSuccess, next.Called); + } + + [Theory] + [InlineData(true, true)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(false, false)] + public async Task CanApplyAuthorizeRequirementAttributeDirectlyToEndpoint(bool anonymous, bool assertSuccess) + { + // Arrange + var calledPolicy = false; + var req = new AssertionRequirement(_ => { calledPolicy = true; return assertSuccess; }); + var policyProvider = new Mock(); + policyProvider.Setup(p => p.GetDefaultPolicyAsync()).ReturnsAsync(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build()); + var next = new TestRequestDelegate(); + var middleware = CreateMiddleware(next.Invoke, policyProvider.Object); + var context = GetHttpContext(anonymous: anonymous, endpoint: CreateEndpoint(new ReqAuthorizeAttribute(req))); + + // Act & Assert + await middleware.Invoke(context); + Assert.True(calledPolicy); + Assert.Equal(assertSuccess && !anonymous, next.Called); + } + [Fact] public async Task Invoke_ValidClaimShouldNotFail() { -- cgit v1.2.3