From 123065c0ae9e069714c08b8511aa7ea9a6ccbde5 Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Sat, 17 Jan 2015 17:11:34 -0800 Subject: [PATCH] Add some sugar for AuthZ - Register passthrough handler by default - AddPolicy overload that takesAction - Chaining policy overloads/methods - More fluent apis for PolicyBuilder Fixes #122, #114 --- .../AuthorizationOptions.cs | 8 + .../AuthorizationPolicyBuilder.cs | 37 +++- .../PassThroughAuthorizationHandler.cs | 1 - .../ServiceCollectionExtensions.cs | 1 + .../DefaultAuthorizationServiceTests.cs | 201 +++++++++++++----- 5 files changed, 189 insertions(+), 59 deletions(-) diff --git a/src/Microsoft.AspNet.Security/AuthorizationOptions.cs b/src/Microsoft.AspNet.Security/AuthorizationOptions.cs index 667ee4fcba..8a53e574d2 100644 --- a/src/Microsoft.AspNet.Security/AuthorizationOptions.cs +++ b/src/Microsoft.AspNet.Security/AuthorizationOptions.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Open Technologies, Inc. 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.Collections.Generic; namespace Microsoft.AspNet.Security @@ -15,6 +16,13 @@ namespace Microsoft.AspNet.Security PolicyMap[name] = policy; } + public void AddPolicy([NotNull] string name, [NotNull] Action configurePolicy) + { + var policyBuilder = new AuthorizationPolicyBuilder(); + configurePolicy(policyBuilder); + PolicyMap[name] = policyBuilder.Build(); + } + public AuthorizationPolicy GetPolicy([NotNull] string name) { return PolicyMap.ContainsKey(name) ? PolicyMap[name] : null; diff --git a/src/Microsoft.AspNet.Security/AuthorizationPolicyBuilder.cs b/src/Microsoft.AspNet.Security/AuthorizationPolicyBuilder.cs index 520bc77460..7b8617f543 100644 --- a/src/Microsoft.AspNet.Security/AuthorizationPolicyBuilder.cs +++ b/src/Microsoft.AspNet.Security/AuthorizationPolicyBuilder.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; +using System.Linq; using System.Security.Claims; namespace Microsoft.AspNet.Security @@ -10,14 +11,42 @@ namespace Microsoft.AspNet.Security { public AuthorizationPolicyBuilder(params string[] activeAuthenticationTypes) { - foreach (var authType in activeAuthenticationTypes) { - ActiveAuthenticationTypes.Add(authType); - } + AddAuthenticationTypes(activeAuthenticationTypes); + } + + public AuthorizationPolicyBuilder(AuthorizationPolicy policy) + { + Combine(policy); } public IList Requirements { get; set; } = new List(); public IList ActiveAuthenticationTypes { get; set; } = new List(); + public AuthorizationPolicyBuilder AddAuthenticationTypes(params string[] activeAuthTypes) + { + foreach (var authType in activeAuthTypes) + { + ActiveAuthenticationTypes.Add(authType); + } + return this; + } + + public AuthorizationPolicyBuilder AddRequirements(params IAuthorizationRequirement[] requirements) + { + foreach (var req in requirements) + { + Requirements.Add(req); + } + return this; + } + + public AuthorizationPolicyBuilder Combine(AuthorizationPolicy policy) + { + AddAuthenticationTypes(policy.ActiveAuthenticationTypes.ToArray()); + AddRequirements(policy.Requirements.ToArray()); + return this; + } + public AuthorizationPolicyBuilder RequiresClaim([NotNull] string claimType, params string[] requiredValues) { Requirements.Add(new ClaimsAuthorizationRequirement @@ -55,4 +84,4 @@ namespace Microsoft.AspNet.Security return new AuthorizationPolicy(Requirements, ActiveAuthenticationTypes); } } -} +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Security/PassThroughAuthorizationHandler.cs b/src/Microsoft.AspNet.Security/PassThroughAuthorizationHandler.cs index 0dfc6ab289..837b2f2676 100644 --- a/src/Microsoft.AspNet.Security/PassThroughAuthorizationHandler.cs +++ b/src/Microsoft.AspNet.Security/PassThroughAuthorizationHandler.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Open Technologies, Inc. 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.Linq; using System.Threading.Tasks; diff --git a/src/Microsoft.AspNet.Security/ServiceCollectionExtensions.cs b/src/Microsoft.AspNet.Security/ServiceCollectionExtensions.cs index cceec4cc28..41b0978cdf 100644 --- a/src/Microsoft.AspNet.Security/ServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNet.Security/ServiceCollectionExtensions.cs @@ -22,6 +22,7 @@ namespace Microsoft.Framework.DependencyInjection services.TryAdd(describe.Transient()); services.Add(describe.Transient()); services.Add(describe.Transient()); + services.Add(describe.Transient()); if (configureOptions != null) { services.Configure(configureOptions); diff --git a/test/Microsoft.AspNet.Security.Test/DefaultAuthorizationServiceTests.cs b/test/Microsoft.AspNet.Security.Test/DefaultAuthorizationServiceTests.cs index 00f6cde0aa..1ba7054390 100644 --- a/test/Microsoft.AspNet.Security.Test/DefaultAuthorizationServiceTests.cs +++ b/test/Microsoft.AspNet.Security.Test/DefaultAuthorizationServiceTests.cs @@ -9,7 +9,6 @@ using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Security; using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.DependencyInjection.Fallback; -using Microsoft.Framework.OptionsModel; using Moq; using Xunit; @@ -53,11 +52,9 @@ namespace Microsoft.AspNet.Security.Test // Arrange var authorizationService = BuildAuthorizationService(services => { - services.Configure(options => + services.ConfigureAuthorization(options => { - options.AddPolicy("Basic", new AuthorizationPolicyBuilder() - .RequiresClaim("Permission", "CanViewPage") - .Build()); + options.AddPolicy("Basic", policy => policy.RequiresClaim("Permission", "CanViewPage")); }); }); var context = SetupContext(new ClaimsIdentity(new Claim[] { new Claim("Permission", "CanViewPage") }, "Basic")); @@ -75,10 +72,9 @@ namespace Microsoft.AspNet.Security.Test // Arrange var authorizationService = BuildAuthorizationService(services => { - services.Configure(options => + services.ConfigureAuthorization(options => { - var policy = new AuthorizationPolicyBuilder("Basic").RequiresClaim("Permission", "CanViewPage"); - options.AddPolicy("Basic", policy.Build()); + options.AddPolicy("Basic", policy => policy.RequiresClaim("Permission", "CanViewPage")); }); }); var context = SetupContext(new ClaimsIdentity(new Claim[] { new Claim("Permission", "CanViewPage") }, "Basic")); @@ -96,10 +92,9 @@ namespace Microsoft.AspNet.Security.Test // Arrange var authorizationService = BuildAuthorizationService(services => { - services.Configure(options => + services.ConfigureAuthorization(options => { - var policy = new AuthorizationPolicyBuilder().RequiresClaim("Permission", "CanViewPage", "CanViewAnything"); - options.AddPolicy("Basic", policy.Build()); + options.AddPolicy("Basic", policy => policy.RequiresClaim("Permission", "CanViewPage", "CanViewAnything")); }); }); var context = SetupContext( @@ -124,10 +119,9 @@ namespace Microsoft.AspNet.Security.Test // Arrange var authorizationService = BuildAuthorizationService(services => { - services.Configure(options => + services.ConfigureAuthorization(options => { - var policy = new AuthorizationPolicyBuilder().RequiresClaim("Permission", "CanViewPage", "CanViewAnything"); - options.AddPolicy("Basic", policy.Build()); + options.AddPolicy("Basic", policy => policy.RequiresClaim("Permission", "CanViewPage", "CanViewAnything")); }); }); var context = SetupContext( @@ -151,10 +145,9 @@ namespace Microsoft.AspNet.Security.Test // Arrange var authorizationService = BuildAuthorizationService(services => { - services.Configure(options => + services.ConfigureAuthorization(options => { - var policy = new AuthorizationPolicyBuilder().RequiresClaim("Permission", "CanViewPage", "CanViewAnything"); - options.AddPolicy("Basic", policy.Build()); + options.AddPolicy("Basic", policy => policy.RequiresClaim("Permission", "CanViewPage", "CanViewAnything")); }); }); var context = SetupContext( @@ -178,10 +171,9 @@ namespace Microsoft.AspNet.Security.Test // Arrange var authorizationService = BuildAuthorizationService(services => { - services.Configure(options => + services.ConfigureAuthorization(options => { - var policy = new AuthorizationPolicyBuilder().RequiresClaim("Permission", "CanViewPage"); - options.AddPolicy("Basic", policy.Build()); + options.AddPolicy("Basic", policy => policy.RequiresClaim("Permission", "CanViewPage")); }); }); var context = SetupContext( @@ -205,10 +197,9 @@ namespace Microsoft.AspNet.Security.Test // Arrange var authorizationService = BuildAuthorizationService(services => { - services.Configure(options => + services.ConfigureAuthorization(options => { - var policy = new AuthorizationPolicyBuilder().RequiresClaim("Permission", "CanViewPage"); - options.AddPolicy("Basic", policy.Build()); + options.AddPolicy("Basic", policy => policy.RequiresClaim("Permission", "CanViewPage")); }); }); var context = SetupContext( @@ -230,10 +221,9 @@ namespace Microsoft.AspNet.Security.Test // Arrange var authorizationService = BuildAuthorizationService(services => { - services.Configure(options => + services.ConfigureAuthorization(options => { - var policy = new AuthorizationPolicyBuilder().RequiresClaim("Permission", "CanViewPage"); - options.AddPolicy("Basic", policy.Build()); + options.AddPolicy("Basic", policy => policy.RequiresClaim("Permission", "CanViewPage")); }); }); var context = SetupContext(); @@ -252,10 +242,9 @@ namespace Microsoft.AspNet.Security.Test // Arrange var authorizationService = BuildAuthorizationService(services => { - services.Configure(options => + services.ConfigureAuthorization(options => { - var policy = new AuthorizationPolicyBuilder("Basic").RequiresClaim("Permission", "CanViewPage"); - options.AddPolicy("Basic", policy.Build()); + options.AddPolicy("Basic", policy => policy.RequiresClaim("Permission", "CanViewPage")); }); }); var context = SetupContext(new ClaimsIdentity()); @@ -273,10 +262,9 @@ namespace Microsoft.AspNet.Security.Test // Arrange var authorizationService = BuildAuthorizationService(services => { - services.Configure(options => + services.ConfigureAuthorization(options => { - var policy = new AuthorizationPolicyBuilder().RequiresClaim("Permission", "CanViewPage"); - options.AddPolicy("Basic", policy.Build()); + options.AddPolicy("Basic", policy => policy.RequiresClaim("Permission", "CanViewPage")); }); }); var context = SetupContext( @@ -433,13 +421,11 @@ namespace Microsoft.AspNet.Security.Test public async Task RolePolicyCanBlockNoRole() { // Arrange - var authorizationService = BuildAuthorizationService(services => { - services.Configure(options => + services.ConfigureAuthorization(options => { - var policy = new AuthorizationPolicyBuilder().RequiresRole("Admin", "Users"); - options.AddPolicy("Basic", policy.Build()); + options.AddPolicy("Basic", policy => policy.RequiresRole("Admin", "Users")); }); }); var context = SetupContext( @@ -462,10 +448,9 @@ namespace Microsoft.AspNet.Security.Test // Arrange var authorizationService = BuildAuthorizationService(services => { - services.Configure(options => + services.ConfigureAuthorization(options => { - var policy = new AuthorizationPolicyBuilder(); - options.AddPolicy("Basic", policy.Build()); + options.AddPolicy("Basic", policy => { }); }); }); var context = SetupContext( @@ -489,10 +474,9 @@ namespace Microsoft.AspNet.Security.Test // Arrange var authorizationService = BuildAuthorizationService(services => { - services.Configure(options => + services.ConfigureAuthorization(options => { - var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser(); - options.AddPolicy("Any", policy.Build()); + options.AddPolicy("Any", policy => policy.RequireAuthenticatedUser()); }); }); var context = SetupContext( @@ -516,10 +500,9 @@ namespace Microsoft.AspNet.Security.Test // Arrange var authorizationService = BuildAuthorizationService(services => { - services.Configure(options => + services.ConfigureAuthorization(options => { - var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser(); - options.AddPolicy("Any", policy.Build()); + options.AddPolicy("Any", policy => policy.RequireAuthenticatedUser()); }); }); var context = SetupContext(new ClaimsIdentity()); @@ -546,11 +529,9 @@ namespace Microsoft.AspNet.Security.Test // Arrange var authorizationService = BuildAuthorizationService(services => { - services.Configure(options => + services.ConfigureAuthorization(options => { - var policy = new AuthorizationPolicyBuilder(); - policy.Requirements.Add(new CustomRequirement()); - options.AddPolicy("Custom", policy.Build()); + options.AddPolicy("Custom", policy => policy.Requirements.Add(new CustomRequirement())); }); }); var context = SetupContext(); @@ -562,7 +543,6 @@ namespace Microsoft.AspNet.Security.Test Assert.False(allowed); } - [Fact] public async Task CustomReqWithHandlerSucceeds() { @@ -570,11 +550,9 @@ namespace Microsoft.AspNet.Security.Test var authorizationService = BuildAuthorizationService(services => { services.AddTransient(); - services.Configure(options => + services.ConfigureAuthorization(options => { - var policy = new AuthorizationPolicyBuilder(); - policy.Requirements.Add(new CustomRequirement()); - options.AddPolicy("Custom", policy.Build()); + options.AddPolicy("Custom", policy => policy.Requirements.Add(new CustomRequirement())); }); }); var context = SetupContext(); @@ -586,5 +564,120 @@ namespace Microsoft.AspNet.Security.Test Assert.True(allowed); } + public class PassThroughRequirement : AuthorizationHandler, IAuthorizationRequirement + { + public PassThroughRequirement(bool succeed) + { + Succeed = succeed; + } + + public bool Succeed { get; set; } + + public override Task CheckAsync(AuthorizationContext context, PassThroughRequirement requirement) + { + return Task.FromResult(Succeed); + } + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task PassThroughRequirementWillSucceedWithoutCustomHandler(bool shouldSucceed) + { + // Arrange + var authorizationService = BuildAuthorizationService(services => + { + services.ConfigureAuthorization(options => + { + options.AddPolicy("Passthrough", policy => policy.Requirements.Add(new PassThroughRequirement(shouldSucceed))); + }); + }); + var context = SetupContext(); + + // Act + var allowed = await authorizationService.AuthorizeAsync("Passthrough", context.Object); + + // Assert + Assert.Equal(shouldSucceed, allowed); + } + + public async Task CanCombinePolicies() + { + // Arrange + var authorizationService = BuildAuthorizationService(services => + { + services.ConfigureAuthorization(options => + { + var basePolicy = new AuthorizationPolicyBuilder().RequiresClaim("Base", "Value").Build(); + options.AddPolicy("Combineed", policy => policy.Combine(basePolicy).RequiresClaim("Claim", "Exists")); + }); + }); + var context = SetupContext( + new ClaimsIdentity( + new Claim[] { + new Claim("Base", "Value"), + new Claim("Claim", "Exists") + }, + "AuthType") + ); + + // Act + var allowed = await authorizationService.AuthorizeAsync("Combined", context.Object); + + // Assert + Assert.True(allowed); + } + + public async Task CombinePoliciesWillFailIfBasePolicyFails() + { + // Arrange + var authorizationService = BuildAuthorizationService(services => + { + services.ConfigureAuthorization(options => + { + var basePolicy = new AuthorizationPolicyBuilder().RequiresClaim("Base", "Value").Build(); + options.AddPolicy("Combined", policy => policy.Combine(basePolicy).RequiresClaim("Claim", "Exists")); + }); + }); + var context = SetupContext( + new ClaimsIdentity( + new Claim[] { + new Claim("Claim", "Exists") + }, + "AuthType") + ); + + // Act + var allowed = await authorizationService.AuthorizeAsync("Combined", context.Object); + + // Assert + Assert.False(allowed); + } + + public async Task CombinedPoliciesWillFailIfExtraRequirementFails() + { + // Arrange + var authorizationService = BuildAuthorizationService(services => + { + services.ConfigureAuthorization(options => + { + var basePolicy = new AuthorizationPolicyBuilder().RequiresClaim("Base", "Value").Build(); + options.AddPolicy("Combined", policy => policy.Combine(basePolicy).RequiresClaim("Claim", "Exists")); + }); + }); + var context = SetupContext( + new ClaimsIdentity( + new Claim[] { + new Claim("Base", "Value"), + }, + "AuthType") + ); + + // Act + var allowed = await authorizationService.AuthorizeAsync("Combined", context.Object); + + // Assert + Assert.False(allowed); + } } } \ No newline at end of file