Skip to content

Add AuthorizationBuilder #42264

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jun 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 149 additions & 0 deletions src/Security/Authorization/Core/src/AuthorizationBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;

namespace Microsoft.AspNetCore.Authorization;

/// <summary>
/// Used to configure authorization
/// </summary>
public class AuthorizationBuilder
{
/// <summary>
/// Initializes a new instance of <see cref="AuthorizationBuilder"/>.
/// </summary>
/// <param name="services">The services being configured.</param>
public AuthorizationBuilder(IServiceCollection services)
=> Services = services;

/// <summary>
/// The services being configured.
/// </summary>
public virtual IServiceCollection Services { get; }

/// <summary>
/// Determines whether authorization handlers should be invoked after <see cref="AuthorizationHandlerContext.HasFailed"/>.
/// Defaults to true.
/// </summary>
/// <returns>The builder.</returns>
public virtual AuthorizationBuilder SetInvokeHandlersAfterFailure(bool invoke)
{
Services.Configure<AuthorizationOptions>(o => o.InvokeHandlersAfterFailure = invoke);
return this;
}

/// <summary>
/// Sets the default authorization policy. Defaults to require authenticated users.
/// </summary>
/// <remarks>
/// The default policy used when evaluating <see cref="IAuthorizeData"/> with no policy name specified.
/// </remarks>
/// <returns>The builder.</returns>
public virtual AuthorizationBuilder SetDefaultPolicy(AuthorizationPolicy policy)
{
Services.Configure<AuthorizationOptions>(o => o.DefaultPolicy = policy);
return this;
}

/// <summary>
/// Sets the fallback authorization policy used by <see cref="AuthorizationPolicy.CombineAsync(IAuthorizationPolicyProvider, IEnumerable{IAuthorizeData})"/>
/// when no IAuthorizeData have been provided. As a result, the AuthorizationMiddleware uses the fallback policy
/// if there are no <see cref="IAuthorizeData"/> instances for a resource. If a resource has any <see cref="IAuthorizeData"/>
/// then they are evaluated instead of the fallback policy. By default the fallback policy is null, and usually will have no
/// effect unless you have the AuthorizationMiddleware in your pipeline. It is not used in any way by the
/// default <see cref="IAuthorizationService"/>.
/// </summary>
/// <returns>The builder.</returns>
public virtual AuthorizationBuilder SetFallbackPolicy(AuthorizationPolicy? policy)
{
Services.Configure<AuthorizationOptions>(o => o.FallbackPolicy = policy);
return this;
}

/// <summary>
/// Adds a <see cref="AuthorizationPolicy"/> which can be used by <see cref="IAuthorizationService"/>.
/// </summary>
/// <param name="name">The name of this policy.</param>
/// <param name="policy">The <see cref="AuthorizationPolicy"/>.></param>
/// <returns>The builder.</returns>
public virtual AuthorizationBuilder AddPolicy(string name, AuthorizationPolicy policy)
{
Services.Configure<AuthorizationOptions>(o => o.AddPolicy(name, policy));
return this;
}

/// <summary>
/// Add a policy that is built from a delegate with the provided name.
/// </summary>
/// <param name="name">The name of the policy.</param>
/// <param name="configurePolicy">The delegate that will be used to build the policy.</param>
/// <returns>The builder.</returns>
public virtual AuthorizationBuilder AddPolicy(string name, Action<AuthorizationPolicyBuilder> configurePolicy)
{
Services.Configure<AuthorizationOptions>(o => o.AddPolicy(name, configurePolicy));
return this;
}

/// <summary>
/// Add a policy that is built from a delegate with the provided name and used as the default policy.
/// </summary>
/// <param name="name">The name of the default policy.</param>
/// <param name="policy">The default <see cref="AuthorizationPolicy"/>.></param>
/// <returns>The builder.</returns>
public virtual AuthorizationBuilder AddDefaultPolicy(string name, AuthorizationPolicy policy)
{
SetDefaultPolicy(policy);
return AddPolicy(name, policy);
}

/// <summary>
/// Add a policy that is built from a delegate with the provided name and used as the DefaultPolicy.
/// </summary>
/// <param name="name">The name of the DefaultPolicy.</param>
/// <param name="configurePolicy">The delegate that will be used to build the DefaultPolicy.</param>
/// <returns>The builder.</returns>
public virtual AuthorizationBuilder AddDefaultPolicy(string name, Action<AuthorizationPolicyBuilder> configurePolicy)
{
if (configurePolicy == null)
{
throw new ArgumentNullException(nameof(configurePolicy));
}

var policyBuilder = new AuthorizationPolicyBuilder();
configurePolicy(policyBuilder);
return AddDefaultPolicy(name, policyBuilder.Build());
}

/// <summary>
/// Add a policy that is built from a delegate with the provided name and used as the FallbackPolicy.
/// </summary>
/// <param name="name">The name of the FallbackPolicy.</param>
/// <param name="policy">The Fallback <see cref="AuthorizationPolicy"/>.></param>
/// <returns>The builder.</returns>
public virtual AuthorizationBuilder AddFallbackPolicy(string name, AuthorizationPolicy policy)
{
SetFallbackPolicy(policy);
return AddPolicy(name, policy);
}

/// <summary>
/// Add a policy that is built from a delegate with the provided name and used as the FallbackPolicy.
/// </summary>
/// <param name="name">The name of the Fallback policy.</param>
/// <param name="configurePolicy">The delegate that will be used to build the Fallback policy.</param>
/// <returns>The builder.</returns>
public virtual AuthorizationBuilder AddFallbackPolicy(string name, Action<AuthorizationPolicyBuilder> configurePolicy)
{
if (configurePolicy == null)
{
throw new ArgumentNullException(nameof(configurePolicy));
}

var policyBuilder = new AuthorizationPolicyBuilder();
configurePolicy(policyBuilder);
return AddFallbackPolicy(name, policyBuilder.Build());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class AuthorizationOptions
private Dictionary<string, AuthorizationPolicy> PolicyMap { get; } = new Dictionary<string, AuthorizationPolicy>(StringComparer.OrdinalIgnoreCase);

/// <summary>
/// Determines whether authentication handlers should be invoked after <see cref="AuthorizationHandlerContext.HasFailed"/>.
/// Determines whether authorization handlers should be invoked after <see cref="AuthorizationHandlerContext.HasFailed"/>.
/// Defaults to true.
/// </summary>
public bool InvokeHandlersAfterFailure { get; set; } = true;
Expand Down
12 changes: 12 additions & 0 deletions src/Security/Authorization/Core/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
#nullable enable
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<Microsoft.AspNetCore.Authorization.AuthorizationOptions!>! options) -> void
static Microsoft.AspNetCore.Authorization.AuthorizationPolicy.CombineAsync(Microsoft.AspNetCore.Authorization.IAuthorizationPolicyProvider! policyProvider, System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Authorization.IAuthorizeData!>! authorizeData, System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Authorization.AuthorizationPolicy!>! policies) -> System.Threading.Tasks.Task<Microsoft.AspNetCore.Authorization.AuthorizationPolicy?>!
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<Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder!>! configurePolicy) -> Microsoft.AspNetCore.Authorization.AuthorizationBuilder!
virtual Microsoft.AspNetCore.Authorization.AuthorizationBuilder.AddFallbackPolicy(string! name, Microsoft.AspNetCore.Authorization.AuthorizationPolicy! policy) -> Microsoft.AspNetCore.Authorization.AuthorizationBuilder!
virtual Microsoft.AspNetCore.Authorization.AuthorizationBuilder.AddFallbackPolicy(string! name, System.Action<Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder!>! configurePolicy) -> Microsoft.AspNetCore.Authorization.AuthorizationBuilder!
virtual Microsoft.AspNetCore.Authorization.AuthorizationBuilder.AddPolicy(string! name, Microsoft.AspNetCore.Authorization.AuthorizationPolicy! policy) -> Microsoft.AspNetCore.Authorization.AuthorizationBuilder!
virtual Microsoft.AspNetCore.Authorization.AuthorizationBuilder.AddPolicy(string! name, System.Action<Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder!>! configurePolicy) -> Microsoft.AspNetCore.Authorization.AuthorizationBuilder!
virtual Microsoft.AspNetCore.Authorization.AuthorizationBuilder.Services.get -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
virtual Microsoft.AspNetCore.Authorization.AuthorizationBuilder.SetDefaultPolicy(Microsoft.AspNetCore.Authorization.AuthorizationPolicy! policy) -> Microsoft.AspNetCore.Authorization.AuthorizationBuilder!
virtual Microsoft.AspNetCore.Authorization.AuthorizationBuilder.SetFallbackPolicy(Microsoft.AspNetCore.Authorization.AuthorizationPolicy? policy) -> Microsoft.AspNetCore.Authorization.AuthorizationBuilder!
virtual Microsoft.AspNetCore.Authorization.AuthorizationBuilder.SetInvokeHandlersAfterFailure(bool invoke) -> Microsoft.AspNetCore.Authorization.AuthorizationBuilder!
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ namespace Microsoft.Extensions.DependencyInjection;
/// </summary>
public static class PolicyServiceCollectionExtensions
{
/// <summary>
/// Adds authorization services to the specified <see cref="IServiceCollection" />.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
/// <returns>The <see cref="AuthorizationBuilder"/> so that additional calls can be chained.</returns>
public static AuthorizationBuilder AddAuthorizationBuilder(this IServiceCollection services)
=> new AuthorizationBuilder(services.AddAuthorization());

/// <summary>
/// Adds the authorization policy evaluator service to the specified <see cref="IServiceCollection" />.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#nullable enable
static Microsoft.AspNetCore.Builder.AuthorizationEndpointConventionBuilderExtensions.RequireAuthorization<TBuilder>(this TBuilder builder, Microsoft.AspNetCore.Authorization.AuthorizationPolicy! policy) -> TBuilder
static Microsoft.AspNetCore.Builder.AuthorizationEndpointConventionBuilderExtensions.RequireAuthorization<TBuilder>(this TBuilder builder, System.Action<Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder!>! configurePolicy) -> TBuilder
static Microsoft.Extensions.DependencyInjection.PolicyServiceCollectionExtensions.AddAuthorizationBuilder(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.AspNetCore.Authorization.AuthorizationBuilder!
116 changes: 116 additions & 0 deletions src/Security/Authorization/test/AuthorizationBuilderTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.AspNetCore.Authorization.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

namespace Microsoft.AspNetCore.Authorization.Test;

public class AuthorizationBuilderTests
{
[Fact]
public void CanSetFallbackPolicy()
{
// Arrange
var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
var builder = TestHelpers.CreateAuthorizationBuilder()
// Act
.SetFallbackPolicy(policy);

var options = builder.Services.BuildServiceProvider().GetRequiredService<IOptions<AuthorizationOptions>>().Value;

// Assert
Assert.Equal(policy, options.FallbackPolicy);
}

[Fact]
public void CanUnSetFallbackPolicy()
{
// Arrange
var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
var builder = TestHelpers.CreateAuthorizationBuilder()
.SetFallbackPolicy(policy)
// Act
.SetFallbackPolicy(null);

var options = builder.Services.BuildServiceProvider().GetRequiredService<IOptions<AuthorizationOptions>>().Value;

// Assert
Assert.Null(options.FallbackPolicy);
}

[Fact]
public void CanSetDefaultPolicy()
{
// Arrange
var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
var builder = TestHelpers.CreateAuthorizationBuilder()
// Act
.SetDefaultPolicy(policy);

var options = builder.Services.BuildServiceProvider().GetRequiredService<IOptions<AuthorizationOptions>>().Value;

// Assert
Assert.Equal(policy, options.DefaultPolicy);
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public void CanSetInvokeHandlersAfterFailure(bool invoke)
{
// Arrange
var builder = TestHelpers.CreateAuthorizationBuilder()
// Act
.SetInvokeHandlersAfterFailure(invoke);

var options = builder.Services.BuildServiceProvider().GetRequiredService<IOptions<AuthorizationOptions>>().Value;

// Assert
Assert.Equal(invoke, options.InvokeHandlersAfterFailure);
}

[Fact]
public void CanAddPolicyInstance()
{
// Arrange
var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
var builder = TestHelpers.CreateAuthorizationBuilder()
// Act
.AddPolicy("name", policy);

var options = builder.Services.BuildServiceProvider().GetRequiredService<IOptions<AuthorizationOptions>>().Value;

// Assert
Assert.Equal(policy, options.GetPolicy("name"));
}

[Fact]
public void CanAddPolicyDelegate()
{
// Arrange
var builder = TestHelpers.CreateAuthorizationBuilder()
// Act
.AddPolicy("name", p => p.RequireAssertion(_ => true));

var options = builder.Services.BuildServiceProvider().GetRequiredService<IOptions<AuthorizationOptions>>().Value;

// Assert
var policy = options.GetPolicy("name");
Assert.NotNull(policy);
Assert.Equal(1, policy.Requirements.Count);
Assert.IsType<AssertionRequirement>(policy.Requirements.First());
}
}

internal class TestHelpers
{
public static AuthorizationBuilder CreateAuthorizationBuilder()
{
var services = new ServiceCollection();
services.AddLogging();
services.AddOptions();
return services.AddAuthorizationBuilder();
}
}
Loading