From e24e28d7af318d6f4177339cc5035a9d2a3342f0 Mon Sep 17 00:00:00 2001
From: 8snit <8snit@outlook.com>
Date: Sun, 12 Jun 2016 22:24:00 +0200
Subject: [PATCH] introduced IAuthorizationEvaluator, make
AuthorizationHandlerContext threadsafe, added authz sample
---
Security.sln | 17 ++-
.../AuthorizationSample.xproj | 18 +++
samples/AuthorizationSample/Customer.cs | 10 ++
.../CustomerOperationAuthorizationHandler.cs | 55 +++++++++
.../AuthorizationSample/CustomerOperations.cs | 23 ++++
.../AuthorizationSample/PermissionService.cs | 18 +++
samples/AuthorizationSample/Program.cs | 20 ++++
.../Properties/launchSettings.json | 28 +++++
samples/AuthorizationSample/Startup.cs | 113 ++++++++++++++++++
.../SupervisorOperationHandler.cs | 20 ++++
samples/AuthorizationSample/project.json | 37 ++++++
samples/AuthorizationSample/web.config | 11 ++
.../AuthorizationHandlerContext.cs | 51 ++------
...uthorizationServiceCollectionExtensions.cs | 1 +
.../DefaultAuthorizationEvaluator.cs | 20 ++++
.../DefaultAuthorizationService.cs | 13 +-
.../IAuthorizationEvaluator.cs | 17 +++
17 files changed, 430 insertions(+), 42 deletions(-)
create mode 100644 samples/AuthorizationSample/AuthorizationSample.xproj
create mode 100644 samples/AuthorizationSample/Customer.cs
create mode 100644 samples/AuthorizationSample/CustomerOperationAuthorizationHandler.cs
create mode 100644 samples/AuthorizationSample/CustomerOperations.cs
create mode 100644 samples/AuthorizationSample/PermissionService.cs
create mode 100644 samples/AuthorizationSample/Program.cs
create mode 100644 samples/AuthorizationSample/Properties/launchSettings.json
create mode 100644 samples/AuthorizationSample/Startup.cs
create mode 100644 samples/AuthorizationSample/SupervisorOperationHandler.cs
create mode 100644 samples/AuthorizationSample/project.json
create mode 100644 samples/AuthorizationSample/web.config
create mode 100644 src/Microsoft.AspNetCore.Authorization/DefaultAuthorizationEvaluator.cs
create mode 100644 src/Microsoft.AspNetCore.Authorization/IAuthorizationEvaluator.cs
diff --git a/Security.sln b/Security.sln
index 2ed873270..3f8e3004e 100644
--- a/Security.sln
+++ b/Security.sln
@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
-VisualStudioVersion = 14.0.24720.0
+VisualStudioVersion = 14.0.25123.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4D2B6A51-2F9F-44F5-8131-EA5CAC053652}"
EndProject
@@ -57,6 +57,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Owin.Security.Int
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "OpenIdConnect.AzureAdSample", "samples\OpenIdConnect.AzureAdSample\OpenIdConnect.AzureAdSample.xproj", "{3A7AD414-EBDE-4F92-B307-4E8F19B6117E}"
EndProject
+Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "AuthorizationSample", "samples\AuthorizationSample\AuthorizationSample.xproj", "{CE608B3F-5533-41B8-BC54-AE822AAAEDEB}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -325,6 +327,18 @@ Global
{3A7AD414-EBDE-4F92-B307-4E8F19B6117E}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{3A7AD414-EBDE-4F92-B307-4E8F19B6117E}.Release|x86.ActiveCfg = Release|Any CPU
{3A7AD414-EBDE-4F92-B307-4E8F19B6117E}.Release|x86.Build.0 = Release|Any CPU
+ {CE608B3F-5533-41B8-BC54-AE822AAAEDEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CE608B3F-5533-41B8-BC54-AE822AAAEDEB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CE608B3F-5533-41B8-BC54-AE822AAAEDEB}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {CE608B3F-5533-41B8-BC54-AE822AAAEDEB}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {CE608B3F-5533-41B8-BC54-AE822AAAEDEB}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {CE608B3F-5533-41B8-BC54-AE822AAAEDEB}.Debug|x86.Build.0 = Debug|Any CPU
+ {CE608B3F-5533-41B8-BC54-AE822AAAEDEB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CE608B3F-5533-41B8-BC54-AE822AAAEDEB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CE608B3F-5533-41B8-BC54-AE822AAAEDEB}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {CE608B3F-5533-41B8-BC54-AE822AAAEDEB}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {CE608B3F-5533-41B8-BC54-AE822AAAEDEB}.Release|x86.ActiveCfg = Release|Any CPU
+ {CE608B3F-5533-41B8-BC54-AE822AAAEDEB}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -352,5 +366,6 @@ Global
{A7922DD8-09F1-43E4-938B-CC523EA08898} = {4D2B6A51-2F9F-44F5-8131-EA5CAC053652}
{A2B5DC39-68D5-4145-A8CC-6AEAB7D33A24} = {7BF11F3A-60B6-4796-B504-579C67FFBA34}
{3A7AD414-EBDE-4F92-B307-4E8F19B6117E} = {F8C0AA27-F3FB-4286-8E4C-47EF86B539FF}
+ {CE608B3F-5533-41B8-BC54-AE822AAAEDEB} = {F8C0AA27-F3FB-4286-8E4C-47EF86B539FF}
EndGlobalSection
EndGlobal
diff --git a/samples/AuthorizationSample/AuthorizationSample.xproj b/samples/AuthorizationSample/AuthorizationSample.xproj
new file mode 100644
index 000000000..27ee06b2d
--- /dev/null
+++ b/samples/AuthorizationSample/AuthorizationSample.xproj
@@ -0,0 +1,18 @@
+
+
+
+ 14.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+
+
+
+ ce608b3f-5533-41b8-bc54-ae822aaaedeb
+ ..\..\artifacts\obj\$(MSBuildProjectName)
+ ..\..\artifacts\bin\
+
+
+ 2.0
+ 22569
+
+
+
\ No newline at end of file
diff --git a/samples/AuthorizationSample/Customer.cs b/samples/AuthorizationSample/Customer.cs
new file mode 100644
index 000000000..2e0b50ee9
--- /dev/null
+++ b/samples/AuthorizationSample/Customer.cs
@@ -0,0 +1,10 @@
+namespace AuthorizationSample
+{
+ public class Customer
+ {
+ public int Id { get; set; }
+ public string Name { get; set; }
+ public string Region { get; set; }
+ public bool Fortune500 { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/samples/AuthorizationSample/CustomerOperationAuthorizationHandler.cs b/samples/AuthorizationSample/CustomerOperationAuthorizationHandler.cs
new file mode 100644
index 000000000..9072147a1
--- /dev/null
+++ b/samples/AuthorizationSample/CustomerOperationAuthorizationHandler.cs
@@ -0,0 +1,55 @@
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Authorization.Infrastructure;
+
+namespace AuthorizationSample
+{
+ public class CustomerOperationAuthorizationHandler :
+ AuthorizationHandler
+ {
+ private readonly IPermissionService _permissions;
+
+ public CustomerOperationAuthorizationHandler(IPermissionService permissions)
+ {
+ _permissions = permissions;
+ }
+
+ protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context,
+ OperationAuthorizationRequirement requirement,
+ Customer resource)
+ {
+ if (!context.User.HasClaim("department", "sales"))
+ {
+ return;
+ }
+
+ if (!context.User.HasClaim("region", resource.Region))
+ {
+ return;
+ }
+
+ if (resource.Fortune500)
+ {
+ if (!context.User.HasClaim("status", "senior"))
+ {
+ return;
+ }
+ }
+
+ var discountOperationAuthorizationRequirement = requirement as DiscountOperationAuthorizationRequirement;
+ if (discountOperationAuthorizationRequirement != null)
+ {
+ var salesRep = context.User.FindFirst("id").Value;
+ if (!await _permissions.IsDiscountAllowedAsync(
+ salesRep,
+ resource.Id,
+ discountOperationAuthorizationRequirement.Amount))
+ {
+ return;
+ }
+ }
+
+ context.Succeed(requirement);
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/AuthorizationSample/CustomerOperations.cs b/samples/AuthorizationSample/CustomerOperations.cs
new file mode 100644
index 000000000..ff06c104e
--- /dev/null
+++ b/samples/AuthorizationSample/CustomerOperations.cs
@@ -0,0 +1,23 @@
+using Microsoft.AspNetCore.Authorization.Infrastructure;
+
+namespace AuthorizationSample
+{
+ public static class CustomerOperations
+ {
+ public static OperationAuthorizationRequirement Manage = new OperationAuthorizationRequirement {Name = "Manage"};
+
+ public static OperationAuthorizationRequirement GiveDiscount(int amount)
+ {
+ return new DiscountOperationAuthorizationRequirement
+ {
+ Name = "GiveDiscount" + amount,
+ Amount = amount
+ };
+ }
+ }
+
+ public class DiscountOperationAuthorizationRequirement : OperationAuthorizationRequirement
+ {
+ public int Amount { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/samples/AuthorizationSample/PermissionService.cs b/samples/AuthorizationSample/PermissionService.cs
new file mode 100644
index 000000000..528eb25bf
--- /dev/null
+++ b/samples/AuthorizationSample/PermissionService.cs
@@ -0,0 +1,18 @@
+using System.Threading.Tasks;
+
+namespace AuthorizationSample
+{
+ public interface IPermissionService
+ {
+ Task IsDiscountAllowedAsync(string id, int customerId, int amount);
+ }
+
+ public class PermissionService : IPermissionService
+ {
+ public async Task IsDiscountAllowedAsync(string id, int customerId, int amount)
+ {
+ await Task.Delay(100);
+ return amount < 10;
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/AuthorizationSample/Program.cs b/samples/AuthorizationSample/Program.cs
new file mode 100644
index 000000000..474a67b81
--- /dev/null
+++ b/samples/AuthorizationSample/Program.cs
@@ -0,0 +1,20 @@
+using System.IO;
+using Microsoft.AspNetCore.Hosting;
+
+namespace AuthorizationSample
+{
+ public static class Program
+ {
+ public static void Main(string[] args)
+ {
+ var host = new WebHostBuilder()
+ .UseKestrel()
+ .UseContentRoot(Directory.GetCurrentDirectory())
+ .UseIISIntegration()
+ .UseStartup()
+ .Build();
+
+ host.Run();
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/AuthorizationSample/Properties/launchSettings.json b/samples/AuthorizationSample/Properties/launchSettings.json
new file mode 100644
index 000000000..afe862f4f
--- /dev/null
+++ b/samples/AuthorizationSample/Properties/launchSettings.json
@@ -0,0 +1,28 @@
+{
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:1788/",
+ "sslPort": 0
+ }
+ },
+ "profiles": {
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "CookieSample": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "launchUrl": "http://localhost:12345",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development",
+ "ASPNETCORE_URLS": "http://localhost:12345"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/AuthorizationSample/Startup.cs b/samples/AuthorizationSample/Startup.cs
new file mode 100644
index 000000000..c2dbe6ac9
--- /dev/null
+++ b/samples/AuthorizationSample/Startup.cs
@@ -0,0 +1,113 @@
+using System;
+using System.Linq;
+using System.Security.Claims;
+using Microsoft.AspNetCore.Authentication.Cookies;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace AuthorizationSample
+{
+ public class Startup
+ {
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddAuthentication();
+
+ services.AddAuthorization();
+
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ }
+
+ public void Configure(IApplicationBuilder app, ILoggerFactory loggerfactory)
+ {
+ loggerfactory.AddConsole(LogLevel.Information);
+
+ app.UseCookieAuthentication(new CookieAuthenticationOptions
+ {
+ AutomaticAuthenticate = true
+ });
+
+ app.Run(async context =>
+ {
+ context.Response.ContentType = "text/plain";
+ var authorizationService = context.RequestServices.GetService();
+
+ var users = new[]
+ {
+ new[]
+ {
+ new Claim("id", "1"),
+ new Claim("name", "Alice"),
+ new Claim("email", "alice@smith.com"),
+ new Claim("status", "intern"),
+ new Claim("department", "sales"),
+ new Claim("region", "north")
+ },
+ new[]
+ {
+ new Claim("id", "2"),
+ new Claim("name", "Bob"),
+ new Claim("email", "bob@smith.com"),
+ new Claim("status", "senior"),
+ new Claim("department", "sales"),
+ new Claim("region", "north")
+ },
+ new[]
+ {
+ new Claim("id", "3"),
+ new Claim("name", "John"),
+ new Claim("email", "john@doe.com"),
+ new Claim("role", "supervisor"),
+ new Claim("department", "finance")
+ }
+ }.Select(
+ c =>
+ new ClaimsPrincipal(new ClaimsIdentity(c, CookieAuthenticationDefaults.AuthenticationScheme,
+ "name", "role")));
+
+ var operations = new[]
+ {CustomerOperations.Manage, CustomerOperations.GiveDiscount(5), CustomerOperations.GiveDiscount(15)};
+
+ var customers = new[]
+ {
+ new Customer
+ {
+ Name = "Acme Corp",
+ Region = "north",
+ Fortune500 = false
+ },
+ new Customer
+ {
+ Name = "Bcme Corp",
+ Region = "south",
+ Fortune500 = false
+ },
+ new Customer
+ {
+ Name = "Jcme Corp",
+ Region = "north",
+ Fortune500 = true
+ }
+ };
+
+ foreach (var user in users)
+ {
+ foreach (var operation in operations)
+ {
+ foreach (var customer in customers)
+ {
+ var isAuthorized = await authorizationService.AuthorizeAsync(user, customer, operation);
+ await context.Response.WriteAsync(
+ $"user {user.FindFirst("name").Value,-15} isAuthorized: {isAuthorized,-15} for operation: {operation.Name,-15} with customer: {customer.Name,-15} {Environment.NewLine}");
+ }
+ }
+ }
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/AuthorizationSample/SupervisorOperationHandler.cs b/samples/AuthorizationSample/SupervisorOperationHandler.cs
new file mode 100644
index 000000000..fbdfad071
--- /dev/null
+++ b/samples/AuthorizationSample/SupervisorOperationHandler.cs
@@ -0,0 +1,20 @@
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Authorization.Infrastructure;
+
+namespace AuthorizationSample
+{
+ public class SupervisorOperationHandler : AuthorizationHandler
+ {
+ protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context,
+ OperationAuthorizationRequirement requirement)
+ {
+ if (!context.User.IsInRole("supervisor"))
+ {
+ return;
+ }
+
+ context.Succeed(requirement);
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/AuthorizationSample/project.json b/samples/AuthorizationSample/project.json
new file mode 100644
index 000000000..7e0305973
--- /dev/null
+++ b/samples/AuthorizationSample/project.json
@@ -0,0 +1,37 @@
+{
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.0.1-*",
+ "Microsoft.AspNetCore.Authentication.Cookies": "1.0.0-*",
+ "Microsoft.AspNetCore.Authorization": "1.0.0-*",
+ "Microsoft.AspNetCore.DataProtection": "1.0.0-*",
+ "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0-*",
+ "Microsoft.AspNetCore.Server.Kestrel": "1.0.0-*",
+ "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0-*",
+ "Microsoft.Extensions.Logging.Console": "1.0.0-*"
+ },
+ "buildOptions": {
+ "emitEntryPoint": true
+ },
+ "frameworks": {
+ "net451": {},
+ "netcoreapp1.0": {
+ "dependencies": {
+ "Microsoft.NETCore.App": {
+ "version": "1.0.0-*",
+ "type": "platform"
+ }
+ }
+ }
+ },
+ "publishOptions": {
+ "include": [
+ "web.config"
+ ]
+ },
+ "tools": {
+ "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-*"
+ },
+ "scripts": {
+ "postpublish": "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%"
+ }
+}
\ No newline at end of file
diff --git a/samples/AuthorizationSample/web.config b/samples/AuthorizationSample/web.config
new file mode 100644
index 000000000..36be478b0
--- /dev/null
+++ b/samples/AuthorizationSample/web.config
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Authorization/AuthorizationHandlerContext.cs b/src/Microsoft.AspNetCore.Authorization/AuthorizationHandlerContext.cs
index 5dc57c278..166cd15e2 100644
--- a/src/Microsoft.AspNetCore.Authorization/AuthorizationHandlerContext.cs
+++ b/src/Microsoft.AspNetCore.Authorization/AuthorizationHandlerContext.cs
@@ -2,8 +2,8 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
-using System.Linq;
using System.Security.Claims;
namespace Microsoft.AspNetCore.Authorization
@@ -13,10 +13,6 @@ namespace Microsoft.AspNetCore.Authorization
///
public class AuthorizationHandlerContext
{
- private HashSet _pendingRequirements;
- private bool _failCalled;
- private bool _succeedCalled;
-
///
/// Creates a new instance of .
///
@@ -36,7 +32,8 @@ public AuthorizationHandlerContext(
Requirements = requirements;
User = user;
Resource = resource;
- _pendingRequirements = new HashSet(requirements);
+ FailedRequirements = new ConcurrentQueue();
+ SucceededRequirements = new ConcurrentQueue();
}
///
@@ -54,45 +51,23 @@ public AuthorizationHandlerContext(
///
public object Resource { get; }
- ///
- /// Gets the requirements that have not yet been marked as succeeded.
- ///
- public IEnumerable PendingRequirements { get { return _pendingRequirements; } }
+ public ConcurrentQueue FailedRequirements { get; }
- ///
- /// Flag indicating whether the current authorization processing has failed.
- ///
- public bool HasFailed { get { return _failCalled; } }
+ public ConcurrentQueue SucceededRequirements { get; }
- ///
- /// Flag indicating whether the current authorization processing has succeeded.
- ///
- public bool HasSucceeded
+ public void Fail(IAuthorizationRequirement requirement = null)
{
- get
- {
- return !_failCalled && _succeedCalled && !PendingRequirements.Any();
- }
+ FailedRequirements.Enqueue(requirement);
}
- ///
- /// Called to indicate will
- /// never return true, even if all requirements are met.
- ///
- public void Fail()
- {
- _failCalled = true;
- }
-
- ///
- /// Called to mark the specified as being
- /// successfully evaluated.
- ///
- /// The requirement whose evaluation has succeeded.
public void Succeed(IAuthorizationRequirement requirement)
{
- _succeedCalled = true;
- _pendingRequirements.Remove(requirement);
+ if (requirement == null)
+ {
+ throw new ArgumentNullException(nameof(requirement));
+ }
+
+ SucceededRequirements.Enqueue(requirement);
}
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Authorization/AuthorizationServiceCollectionExtensions.cs b/src/Microsoft.AspNetCore.Authorization/AuthorizationServiceCollectionExtensions.cs
index c92be6232..f56ea5c19 100644
--- a/src/Microsoft.AspNetCore.Authorization/AuthorizationServiceCollectionExtensions.cs
+++ b/src/Microsoft.AspNetCore.Authorization/AuthorizationServiceCollectionExtensions.cs
@@ -27,6 +27,7 @@ public static IServiceCollection AddAuthorization(this IServiceCollection servic
services.TryAdd(ServiceDescriptor.Transient());
services.TryAdd(ServiceDescriptor.Transient());
+ services.TryAdd(ServiceDescriptor.Transient());
services.TryAddEnumerable(ServiceDescriptor.Transient());
return services;
}
diff --git a/src/Microsoft.AspNetCore.Authorization/DefaultAuthorizationEvaluator.cs b/src/Microsoft.AspNetCore.Authorization/DefaultAuthorizationEvaluator.cs
new file mode 100644
index 000000000..f5d6f1831
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Authorization/DefaultAuthorizationEvaluator.cs
@@ -0,0 +1,20 @@
+// 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.Linq;
+
+namespace Microsoft.AspNetCore.Authorization
+{
+ public class DefaultAuthorizationEvaluator : IAuthorizationEvaluator
+ {
+ public bool HasFailed(AuthorizationHandlerContext context)
+ {
+ return context.FailedRequirements.Any();
+ }
+
+ public bool HasSucceeded(AuthorizationHandlerContext context)
+ {
+ return !context.FailedRequirements.Any() && context.SucceededRequirements.Any() && !context.Requirements.Except(context.FailedRequirements).Except(context.SucceededRequirements).Any();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Authorization/DefaultAuthorizationService.cs b/src/Microsoft.AspNetCore.Authorization/DefaultAuthorizationService.cs
index 6665204ee..9c41cbb3e 100644
--- a/src/Microsoft.AspNetCore.Authorization/DefaultAuthorizationService.cs
+++ b/src/Microsoft.AspNetCore.Authorization/DefaultAuthorizationService.cs
@@ -19,14 +19,16 @@ public class DefaultAuthorizationService : IAuthorizationService
private readonly IAuthorizationPolicyProvider _policyProvider;
private readonly IList _handlers;
private readonly ILogger _logger;
+ private readonly IAuthorizationEvaluator _authorizationEvaluator;
///
/// Creates a new instance of .
///
/// The used to provide policies.
/// The handlers used to fulfill s.
- /// The logger used to log messages, warnings and errors.
- public DefaultAuthorizationService(IAuthorizationPolicyProvider policyProvider, IEnumerable handlers, ILogger logger)
+ /// The logger used to log messages, warnings and errors.
+ ///
+ public DefaultAuthorizationService(IAuthorizationPolicyProvider policyProvider, IEnumerable handlers, ILogger logger, IAuthorizationEvaluator authorizationEvaluator)
{
if (policyProvider == null)
{
@@ -40,10 +42,15 @@ public DefaultAuthorizationService(IAuthorizationPolicyProvider policyProvider,
{
throw new ArgumentNullException(nameof(logger));
}
+ if (authorizationEvaluator == null)
+ {
+ throw new ArgumentNullException(nameof(authorizationEvaluator));
+ }
_handlers = handlers.ToArray();
_policyProvider = policyProvider;
_logger = logger;
+ _authorizationEvaluator = authorizationEvaluator;
}
///
@@ -69,7 +76,7 @@ public async Task AuthorizeAsync(ClaimsPrincipal user, object resource, IE
await handler.HandleAsync(authContext);
}
- if (authContext.HasSucceeded)
+ if (_authorizationEvaluator.HasSucceeded(authContext))
{
_logger.UserAuthorizationSucceeded(GetUserNameForLogging(user));
return true;
diff --git a/src/Microsoft.AspNetCore.Authorization/IAuthorizationEvaluator.cs b/src/Microsoft.AspNetCore.Authorization/IAuthorizationEvaluator.cs
new file mode 100644
index 000000000..ad12304a7
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Authorization/IAuthorizationEvaluator.cs
@@ -0,0 +1,17 @@
+// 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.Collections.Generic;
+using System.Linq;
+using System.Security.Claims;
+
+namespace Microsoft.AspNetCore.Authorization
+{
+ public interface IAuthorizationEvaluator
+ {
+ bool HasFailed(AuthorizationHandlerContext context);
+
+ bool HasSucceeded(AuthorizationHandlerContext context);
+ }
+}
\ No newline at end of file