diff --git a/src/Microsoft.AspNetCore.Authorization/AuthorizationHandlerContext.cs b/src/Microsoft.AspNetCore.Authorization/AuthorizationHandlerContext.cs index 5dc57c278a..b6378e4073 100644 --- a/src/Microsoft.AspNetCore.Authorization/AuthorizationHandlerContext.cs +++ b/src/Microsoft.AspNetCore.Authorization/AuthorizationHandlerContext.cs @@ -42,32 +42,32 @@ namespace Microsoft.AspNetCore.Authorization /// /// The collection of all the for the current authorization action. /// - public IEnumerable Requirements { get; } + public virtual IEnumerable Requirements { get; } /// /// The representing the current user. /// - public ClaimsPrincipal User { get; } + public virtual ClaimsPrincipal User { get; } /// /// The optional resource to evaluate the against. /// - public object Resource { get; } + public virtual object Resource { get; } /// /// Gets the requirements that have not yet been marked as succeeded. /// - public IEnumerable PendingRequirements { get { return _pendingRequirements; } } + public virtual IEnumerable PendingRequirements { get { return _pendingRequirements; } } /// /// Flag indicating whether the current authorization processing has failed. /// - public bool HasFailed { get { return _failCalled; } } + public virtual bool HasFailed { get { return _failCalled; } } /// /// Flag indicating whether the current authorization processing has succeeded. /// - public bool HasSucceeded + public virtual bool HasSucceeded { get { @@ -79,7 +79,7 @@ namespace Microsoft.AspNetCore.Authorization /// Called to indicate will /// never return true, even if all requirements are met. /// - public void Fail() + public virtual void Fail() { _failCalled = true; } @@ -89,7 +89,7 @@ namespace Microsoft.AspNetCore.Authorization /// successfully evaluated. /// /// The requirement whose evaluation has succeeded. - public void Succeed(IAuthorizationRequirement requirement) + public virtual void Succeed(IAuthorizationRequirement requirement) { _succeedCalled = true; _pendingRequirements.Remove(requirement); diff --git a/src/Microsoft.AspNetCore.Authorization/AuthorizationServiceCollectionExtensions.cs b/src/Microsoft.AspNetCore.Authorization/AuthorizationServiceCollectionExtensions.cs index f56ea5c19e..a9961b69ef 100644 --- a/src/Microsoft.AspNetCore.Authorization/AuthorizationServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNetCore.Authorization/AuthorizationServiceCollectionExtensions.cs @@ -28,6 +28,7 @@ namespace Microsoft.Extensions.DependencyInjection services.TryAdd(ServiceDescriptor.Transient()); 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/DefaultAuthorizationHandlerContextFactory.cs b/src/Microsoft.AspNetCore.Authorization/DefaultAuthorizationHandlerContextFactory.cs new file mode 100644 index 0000000000..2dae5e5e73 --- /dev/null +++ b/src/Microsoft.AspNetCore.Authorization/DefaultAuthorizationHandlerContextFactory.cs @@ -0,0 +1,29 @@ +// 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.Collections.Generic; +using System.Security.Claims; + +namespace Microsoft.AspNetCore.Authorization +{ + /// + /// A type used to provide a used for authorization. + /// + public class DefaultAuthorizationHandlerContextFactory : IAuthorizationHandlerContextFactory + { + /// + /// Creates a used for authorization. + /// + /// The requirements to evaluate. + /// The user to evaluate the requirements against. + /// + /// An optional resource the policy should be checked with. + /// If a resource is not required for policy evaluation you may pass null as the value. + /// + /// The . + public virtual AuthorizationHandlerContext CreateContext(IEnumerable requirements, ClaimsPrincipal user, object resource) + { + return new AuthorizationHandlerContext(requirements, user, resource); + } + } +} diff --git a/src/Microsoft.AspNetCore.Authorization/DefaultAuthorizationService.cs b/src/Microsoft.AspNetCore.Authorization/DefaultAuthorizationService.cs index 45ee4aa4a2..89777eab01 100644 --- a/src/Microsoft.AspNetCore.Authorization/DefaultAuthorizationService.cs +++ b/src/Microsoft.AspNetCore.Authorization/DefaultAuthorizationService.cs @@ -16,6 +16,7 @@ namespace Microsoft.AspNetCore.Authorization /// public class DefaultAuthorizationService : IAuthorizationService { + private readonly IAuthorizationHandlerContextFactory _contextFactory; private readonly IAuthorizationEvaluator _evaluator; private readonly IAuthorizationPolicyProvider _policyProvider; private readonly IList _handlers; @@ -27,7 +28,7 @@ namespace Microsoft.AspNetCore.Authorization /// 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) : this(policyProvider, handlers, logger, new DefaultAuthorizationEvaluator()) { } + public DefaultAuthorizationService(IAuthorizationPolicyProvider policyProvider, IEnumerable handlers, ILogger logger) : this(policyProvider, handlers, logger, new DefaultAuthorizationHandlerContextFactory(), new DefaultAuthorizationEvaluator()) { } /// /// Creates a new instance of . @@ -35,8 +36,9 @@ namespace Microsoft.AspNetCore.Authorization /// The used to provide policies. /// The handlers used to fulfill s. /// The logger used to log messages, warnings and errors. + /// The used to create the context to handle the authorization. /// The used to determine if authorzation was successful. - public DefaultAuthorizationService(IAuthorizationPolicyProvider policyProvider, IEnumerable handlers, ILogger logger, IAuthorizationEvaluator evaluator) + public DefaultAuthorizationService(IAuthorizationPolicyProvider policyProvider, IEnumerable handlers, ILogger logger, IAuthorizationHandlerContextFactory contextFactory, IAuthorizationEvaluator evaluator) { if (policyProvider == null) { @@ -50,6 +52,10 @@ namespace Microsoft.AspNetCore.Authorization { throw new ArgumentNullException(nameof(logger)); } + if (contextFactory == null) + { + throw new ArgumentNullException(nameof(contextFactory)); + } if (evaluator == null) { throw new ArgumentNullException(nameof(evaluator)); @@ -59,6 +65,7 @@ namespace Microsoft.AspNetCore.Authorization _policyProvider = policyProvider; _logger = logger; _evaluator = evaluator; + _contextFactory = contextFactory; } /// @@ -78,7 +85,7 @@ namespace Microsoft.AspNetCore.Authorization throw new ArgumentNullException(nameof(requirements)); } - var authContext = new AuthorizationHandlerContext(requirements, user, resource); + var authContext = _contextFactory.CreateContext(requirements, user, resource); foreach (var handler in _handlers) { await handler.HandleAsync(authContext); diff --git a/src/Microsoft.AspNetCore.Authorization/IAuthorizationHandlerContextFactory.cs b/src/Microsoft.AspNetCore.Authorization/IAuthorizationHandlerContextFactory.cs new file mode 100644 index 0000000000..272109eea9 --- /dev/null +++ b/src/Microsoft.AspNetCore.Authorization/IAuthorizationHandlerContextFactory.cs @@ -0,0 +1,26 @@ +// 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.Collections.Generic; +using System.Security.Claims; + +namespace Microsoft.AspNetCore.Authorization +{ + /// + /// A type used to provide a used for authorization. + /// + public interface IAuthorizationHandlerContextFactory + { + /// + /// Creates a used for authorization. + /// + /// The requirements to evaluate. + /// The user to evaluate the requirements against. + /// + /// An optional resource the policy should be checked with. + /// If a resource is not required for policy evaluation you may pass null as the value. + /// + /// The . + AuthorizationHandlerContext CreateContext(IEnumerable requirements, ClaimsPrincipal user, object resource); + } +} diff --git a/test/Microsoft.AspNetCore.Authorization.Test/DefaultAuthorizationServiceTests.cs b/test/Microsoft.AspNetCore.Authorization.Test/DefaultAuthorizationServiceTests.cs index 749a11dc34..7f9e5642ae 100644 --- a/test/Microsoft.AspNetCore.Authorization.Test/DefaultAuthorizationServiceTests.cs +++ b/test/Microsoft.AspNetCore.Authorization.Test/DefaultAuthorizationServiceTests.cs @@ -1038,11 +1038,52 @@ namespace Microsoft.AspNetCore.Authorization.Test { var authorizationService = BuildAuthorizationService(services => { - // This will ignore the policy options services.AddSingleton(); services.AddAuthorization(options => options.AddPolicy("Fail", p => p.RequireAssertion(c => false))); }); Assert.True(await authorizationService.AuthorizeAsync(null, "Fail")); } + + + public class BadContextMaker : IAuthorizationHandlerContextFactory + { + public AuthorizationHandlerContext CreateContext(IEnumerable requirements, ClaimsPrincipal user, object resource) + { + return new BadContext(); + } + } + + public class BadContext : AuthorizationHandlerContext + { + public BadContext() : base(new List(), null, null) { } + + public override bool HasFailed + { + get + { + return true; + } + } + + public override bool HasSucceeded + { + get + { + return false; + } + } + } + + [Fact] + public async Task CanUseCustomContextThatAlwaysFails() + { + var authorizationService = BuildAuthorizationService(services => + { + services.AddSingleton(); + services.AddAuthorization(options => options.AddPolicy("Success", p => p.RequireAssertion(c => true))); + }); + Assert.False(await authorizationService.AuthorizeAsync(null, "Success")); + } + } } \ No newline at end of file