diff --git a/samples/MvcSample.Web/FiltersController.cs b/samples/MvcSample.Web/FiltersController.cs index e9909c8719..04af33664d 100644 --- a/samples/MvcSample.Web/FiltersController.cs +++ b/samples/MvcSample.Web/FiltersController.cs @@ -3,6 +3,7 @@ using System; using Microsoft.AspNet.Mvc; +using Microsoft.AspNet.Security; using MvcSample.Web.Filters; using MvcSample.Web.Models; @@ -50,14 +51,12 @@ namespace MvcSample.Web return new ChallengeResult(); } - [Authorize("CanViewPage")] public ActionResult NotGrantedClaim(int age = 20, string userName = "SampleUser") { return Index(age, userName); } [FakeUser] - [Authorize("CanViewAnything")] public ActionResult AllGranted(int age = 20, string userName = "SampleUser") { return Index(age, userName); diff --git a/samples/MvcSample.Web/Startup.cs b/samples/MvcSample.Web/Startup.cs index 0382af244f..6e777dbf62 100644 --- a/samples/MvcSample.Web/Startup.cs +++ b/samples/MvcSample.Web/Startup.cs @@ -40,19 +40,6 @@ namespace MvcSample.Web app.UseServices(services => { - services.ConfigureAuthorization(auth => - { - auth.AddPolicy("CanViewPage", - new AuthorizationPolicyBuilder() - .RequiresClaim("Permission", "CanViewPage", "CanViewAnything").Build()); - auth.AddPolicy("CanViewAnything", - new AuthorizationPolicyBuilder() - .RequiresClaim("Permission", "CanViewAnything").Build()); - // This policy basically requires that the auth type is present - var basicPolicy = new AuthorizationPolicyBuilder("Basic").RequiresClaim(ClaimTypes.NameIdentifier); - auth.AddPolicy("RequireBasic", basicPolicy.Build()); - }); - services.AddMvc(); services.AddSingleton(); services.AddSingleton(); @@ -131,4 +118,4 @@ namespace MvcSample.Web }); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultActionModelBuilder.cs b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultActionModelBuilder.cs index 76af6f93f3..38eebcf974 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultActionModelBuilder.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultActionModelBuilder.cs @@ -8,6 +8,8 @@ using System.Reflection; using Microsoft.AspNet.Mvc.Description; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.Routing; +using Microsoft.AspNet.Security; +using Microsoft.Framework.OptionsModel; namespace Microsoft.AspNet.Mvc.ApplicationModels { @@ -16,6 +18,13 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels /// public class DefaultActionModelBuilder : IActionModelBuilder { + private readonly AuthorizationOptions _authorizationOptions; + + public DefaultActionModelBuilder(IOptions options) + { + _authorizationOptions = options?.Options ?? new AuthorizationOptions(); + } + /// public IEnumerable BuildActionModels([NotNull] TypeInfo typeInfo, [NotNull] MethodInfo methodInfo) { @@ -256,6 +265,12 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels AddRange(actionModel.ActionConstraints, attributes.OfType()); AddRange(actionModel.Filters, attributes.OfType()); + var policy = AuthorizationPolicy.Combine(_authorizationOptions, attributes.OfType()); + if (policy != null) + { + actionModel.Filters.Add(new AuthorizeFilter(policy)); + } + var actionName = attributes.OfType().FirstOrDefault(); if (actionName?.Name != null) { diff --git a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultControllerModelBuilder.cs b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultControllerModelBuilder.cs index 5c44f1240b..e4f655204f 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultControllerModelBuilder.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultControllerModelBuilder.cs @@ -8,7 +8,9 @@ using System.Reflection; using Microsoft.AspNet.Mvc.Description; using Microsoft.AspNet.Mvc.Filters; using Microsoft.AspNet.Mvc.Routing; +using Microsoft.AspNet.Security; using Microsoft.Framework.Logging; +using Microsoft.Framework.OptionsModel; namespace Microsoft.AspNet.Mvc.ApplicationModels { @@ -19,15 +21,20 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels { private readonly IActionModelBuilder _actionModelBuilder; private readonly ILogger _logger; + private readonly AuthorizationOptions _authorizationOptions; /// /// Creates a new . /// /// The used to create actions. - public DefaultControllerModelBuilder(IActionModelBuilder actionModelBuilder, ILoggerFactory loggerFactory) + public DefaultControllerModelBuilder( + IActionModelBuilder actionModelBuilder, + ILoggerFactory loggerFactory, + IOptions options) { _actionModelBuilder = actionModelBuilder; _logger = loggerFactory.Create(); + _authorizationOptions = options?.Options ?? new AuthorizationOptions(); } /// @@ -72,6 +79,12 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels AddRange(controllerModel.Filters, attributes.OfType()); AddRange(controllerModel.RouteConstraints, attributes.OfType()); + var policy = AuthorizationPolicy.Combine(_authorizationOptions, attributes.OfType()); + if (policy != null) + { + controllerModel.Filters.Add(new AuthorizeFilter(policy)); + } + AddRange( controllerModel.AttributeRoutes, attributes.OfType().Select(rtp => new AttributeRouteModel(rtp))); diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/AuthorizeAttribute.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/AuthorizeAttribute.cs deleted file mode 100644 index 9c46d71217..0000000000 --- a/src/Microsoft.AspNet.Mvc.Core/Filters/AuthorizeAttribute.cs +++ /dev/null @@ -1,88 +0,0 @@ -// 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.Threading.Tasks; -using Microsoft.AspNet.Mvc.Core; -using Microsoft.AspNet.Security; -using Microsoft.Framework.DependencyInjection; - -namespace Microsoft.AspNet.Mvc -{ - public class AuthorizeAttribute : AuthorizationFilterAttribute - { - private string _roles; - private string[] _rolesSplit; - - public AuthorizeAttribute() { } - - public AuthorizeAttribute(string policy) - { - Policy = policy; - } - - public string Policy { get; set; } - - public string Roles - { - get { return _roles; } - set - { - _roles = value; - if (string.IsNullOrWhiteSpace(_roles)) - { - _rolesSplit = null; - } - else - { - _rolesSplit = _roles.Split(','); - } - } - } - - public override async Task OnAuthorizationAsync([NotNull] AuthorizationContext context) - { - var httpContext = context.HttpContext; - - // Allow Anonymous skips all authorization - if (HasAllowAnonymous(context)) - { - return; - } - - var authService = httpContext.RequestServices.GetRequiredService(); - - // Build a policy for the requested roles if specified - if (_rolesSplit != null) - { - var rolesPolicy = new AuthorizationPolicyBuilder(); - rolesPolicy.RequiresRole(_rolesSplit); - if (!await authService.AuthorizeAsync(rolesPolicy.Build(), httpContext, context)) - { - Fail(context); - return; - } - } - - var authorized = (Policy == null) - // [Authorize] with no policy just requires any authenticated user - ? await authService.AuthorizeAsync(BuildAnyAuthorizedUserPolicy(), httpContext, context) - : await authService.AuthorizeAsync(Policy, httpContext, context); - if (!authorized) - { - Fail(context); - } - } - - private static AuthorizationPolicy BuildAnyAuthorizedUserPolicy() - { - return new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build(); - } - - public sealed override void OnAuthorization([NotNull] AuthorizationContext context) - { - // The async filter will be called by the filter pipeline. - throw new NotImplementedException(Resources.AuthorizeAttribute_OnAuthorizationNotImplemented); - } - } -} diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/AuthorizeFilter.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/AuthorizeFilter.cs new file mode 100644 index 0000000000..9c2bd6e21a --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Filters/AuthorizeFilter.cs @@ -0,0 +1,62 @@ +// 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.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNet.Security; +using Microsoft.Framework.DependencyInjection; + +namespace Microsoft.AspNet.Mvc +{ + /// + /// An implementation of + /// + public class AuthorizeFilter : IAsyncAuthorizationFilter + { + /// + /// Authorize filter for a specific policy. + /// + /// + public AuthorizeFilter([NotNull] AuthorizationPolicy policy) + { + Policy = policy; + } + + /// + /// Authorization policy to be used. + /// + public AuthorizationPolicy Policy { get; private set; } + + /// + public virtual async Task OnAuthorizationAsync([NotNull] AuthorizationContext context) + { + // Build a ClaimsPrincipal with the Policy's required authentication types + if (Policy.ActiveAuthenticationTypes != null && Policy.ActiveAuthenticationTypes.Any()) + { + var results = await context.HttpContext.AuthenticateAsync(Policy.ActiveAuthenticationTypes); + if (results != null) + { + context.HttpContext.User = new ClaimsPrincipal(results.Where(r => r.Identity != null).Select(r => r.Identity)); + } + } + + // Allow Anonymous skips all authorization + if (context.Filters.Any(item => item is IAllowAnonymous)) + { + return; + } + + var httpContext = context.HttpContext; + var authService = httpContext.RequestServices.GetRequiredService(); + + // Note: Default Anonymous User is new ClaimsPrincipal(new ClaimsIdentity()) + if (httpContext.User == null || + !httpContext.User.Identities.Any(i => i.IsAuthenticated) || + !await authService.AuthorizeAsync(httpContext.User, context, Policy)) + { + context.Result = new ChallengeResult(Policy.ActiveAuthenticationTypes.ToArray()); + } + } + } +} diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/ActionModelTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/ActionModelTest.cs index 128c8d5ffc..799f662bc8 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/ActionModelTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/ActionModelTest.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Reflection; +using Microsoft.AspNet.Security; using Xunit; namespace Microsoft.AspNet.Mvc.ApplicationModels @@ -43,14 +44,14 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels { // Arrange var action = new ActionModel(typeof(TestController).GetMethod("Edit"), - new List() { new HttpGetAttribute() }); + new List() { new HttpGetAttribute(), new AuthorizeAttribute() }); action.ActionConstraints.Add(new HttpMethodConstraint(new string[] { "GET" })); action.ActionName = "Edit"; action.Controller = new ControllerModel(typeof(TestController).GetTypeInfo(), new List()); - action.Filters.Add(new AuthorizeAttribute()); + action.Filters.Add(new AuthorizeFilter(new AuthorizationPolicyBuilder().Build())); action.HttpMethods.Add("GET"); action.RouteConstraints.Add(new AreaAttribute("Admin")); action.Properties.Add(new KeyValuePair("test key", "test value")); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/ControllerModelTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/ControllerModelTest.cs index 87699e9d0a..c67f12d158 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/ControllerModelTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/ControllerModelTest.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Reflection; +using Microsoft.AspNet.Security; using Xunit; namespace Microsoft.AspNet.Mvc.ApplicationModels @@ -49,12 +50,12 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels { // Arrange var controller = new ControllerModel(typeof(TestController).GetTypeInfo(), - new List() { new HttpGetAttribute() }); + new List() { new HttpGetAttribute(), new AuthorizeAttribute() }); controller.ActionConstraints.Add(new HttpMethodConstraint(new string[] { "GET" })); controller.Application = new ApplicationModel(); controller.ControllerName = "cool"; - controller.Filters.Add(new AuthorizeAttribute()); + controller.Filters.Add(new AuthorizeFilter(new AuthorizationPolicyBuilder().Build())); controller.RouteConstraints.Add(new AreaAttribute("Admin")); controller.Properties.Add(new KeyValuePair("test key", "test value")); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/DefaultActionModelBuilderTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/DefaultActionModelBuilderTest.cs index 0223c7098b..df4e8c0cdf 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/DefaultActionModelBuilderTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/DefaultActionModelBuilderTest.cs @@ -5,6 +5,9 @@ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; +using Microsoft.AspNet.Security; +using Microsoft.Framework.OptionsModel; +using Moq; using Xunit; namespace Microsoft.AspNet.Mvc.ApplicationModels @@ -18,7 +21,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public void IsAction_WithInheritedMethods(string methodName, bool expected) { // Arrange - var builder = new AccessibleActionModelBuilder(); + var builder = CreateTestAccessibleActionModelBuilder(); var method = typeof(DerivedController).GetMethod(methodName); Assert.NotNull(method); @@ -33,7 +36,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public void IsAction_OverridenMethodControllerClass() { // Arrange - var builder = new AccessibleActionModelBuilder(); + var builder = CreateTestAccessibleActionModelBuilder(); var method = typeof(BaseController).GetMethod(nameof(BaseController.Redirect)); Assert.NotNull(method); @@ -48,7 +51,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public void IsAction_PrivateMethod_FromUserDefinedController() { // Arrange - var builder = new AccessibleActionModelBuilder(); + var builder = CreateTestAccessibleActionModelBuilder(); var method = typeof(DerivedController).GetMethod( "PrivateMethod", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); @@ -65,7 +68,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public void IsAction_OperatorOverloadingMethod_FromOperatorOverloadingController() { // Arrange - var builder = new AccessibleActionModelBuilder(); + var builder = CreateTestAccessibleActionModelBuilder(); var method = typeof(OperatorOverloadingController).GetMethod("op_Addition"); Assert.NotNull(method); Assert.True(method.IsSpecialName); @@ -81,7 +84,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public void IsAction_GenericMethod_FromUserDefinedController() { // Arrange - var builder = new AccessibleActionModelBuilder(); + var builder = CreateTestAccessibleActionModelBuilder(); var method = typeof(DerivedController).GetMethod("GenericMethod"); Assert.NotNull(method); @@ -96,7 +99,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public void IsAction_OverridenNonActionMethod() { // Arrange - var builder = new AccessibleActionModelBuilder(); + var builder = CreateTestAccessibleActionModelBuilder(); var method = typeof(DerivedController).GetMethod("OverridenNonActionMethod"); Assert.NotNull(method); @@ -115,7 +118,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public void IsAction_OverriddenMethodsFromObjectClass(string methodName) { // Arrange - var builder = new AccessibleActionModelBuilder(); + var builder = CreateTestAccessibleActionModelBuilder(); var method = typeof(DerivedController).GetMethod( methodName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); @@ -132,7 +135,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public void IsAction_DerivedControllerIDisposableDisposeMethod() { // Arrange - var builder = new AccessibleActionModelBuilder(); + var builder = CreateTestAccessibleActionModelBuilder(); var typeInfo = typeof(DerivedController).GetTypeInfo(); var methodInfo = typeInfo.GetRuntimeInterfaceMap(typeof(IDisposable)).TargetMethods[0]; @@ -150,7 +153,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public void IsAction_DerivedControllerDisposeMethod() { // Arrange - var builder = new AccessibleActionModelBuilder(); + var builder = CreateTestAccessibleActionModelBuilder(); var typeInfo = typeof(DerivedController).GetTypeInfo(); var methodInfo = typeInfo.GetRuntimeInterfaceMap(typeof(IDisposable)).TargetMethods[0]; @@ -172,7 +175,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public void IsAction_OverriddenDisposeMethod() { // Arrange - var builder = new AccessibleActionModelBuilder(); + var builder = CreateTestAccessibleActionModelBuilder(); var typeInfo = typeof(DerivedOverriddenDisposeController).GetTypeInfo(); var method = typeInfo.GetDeclaredMethods("Dispose").SingleOrDefault(); Assert.NotNull(method); @@ -188,7 +191,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public void IsAction_NewDisposeMethod() { // Arrange - var builder = new AccessibleActionModelBuilder(); + var builder = CreateTestAccessibleActionModelBuilder(); var typeInfo = typeof(DerivedNewDisposeController).GetTypeInfo(); var method = typeInfo.GetDeclaredMethods("Dispose").SingleOrDefault(); Assert.NotNull(method); @@ -204,7 +207,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public void IsAction_PocoControllerIDisposableDisposeMethod() { // Arrange - var builder = new AccessibleActionModelBuilder(); + var builder = CreateTestAccessibleActionModelBuilder(); var typeInfo = typeof(IDisposablePocoController).GetTypeInfo(); var methodInfo = typeInfo.GetRuntimeInterfaceMap(typeof(IDisposable)).TargetMethods[0]; @@ -222,7 +225,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public void IsAction_PocoControllerDisposeMethod() { // Arrange - var builder = new AccessibleActionModelBuilder(); + var builder = CreateTestAccessibleActionModelBuilder(); var typeInfo = typeof(IDisposablePocoController).GetTypeInfo(); var methodInfo = typeInfo.GetRuntimeInterfaceMap(typeof(IDisposable)).TargetMethods[0]; @@ -244,7 +247,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public void IsAction_SimplePocoControllerDisposeMethod() { // Arrange - var builder = new AccessibleActionModelBuilder(); + var builder = CreateTestAccessibleActionModelBuilder(); var typeInfo = typeof(SimplePocoController).GetTypeInfo(); var methods = typeInfo.GetMethods().Where(m => m.Name.Equals("Dispose")); @@ -267,7 +270,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public void IsAction_StaticMethods(string methodName) { // Arrange - var builder = new AccessibleActionModelBuilder(); + var builder = CreateTestAccessibleActionModelBuilder(); var method = typeof(DerivedController).GetMethod( methodName, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public); @@ -284,7 +287,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public void GetActions_ConventionallyRoutedAction_WithoutHttpConstraints() { // Arrange - var builder = new DefaultActionModelBuilder(); + var builder = CreateTestDefaultActionModelBuilder(); var typeInfo = typeof(ConventionallyRoutedController).GetTypeInfo(); var actionName = nameof(ConventionallyRoutedController.Edit); @@ -303,7 +306,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public void GetActions_ConventionallyRoutedAction_WithHttpConstraints() { // Arrange - var builder = new DefaultActionModelBuilder(); + var builder = CreateTestDefaultActionModelBuilder(); var typeInfo = typeof(ConventionallyRoutedController).GetTypeInfo(); var actionName = nameof(ConventionallyRoutedController.Update); @@ -320,11 +323,34 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels Assert.IsType(Assert.Single(action.Attributes)); } + [Fact] + public void GetActions_BaseAuthorizeFiltersAreStillValidWhenOverriden() + { + // Arrange + var options = new AuthorizationOptions(); + options.AddPolicy("Base", policy => policy.RequiresClaim("Basic").RequiresClaim("Basic2")); + options.AddPolicy("Derived", policy => policy.RequiresClaim("Derived")); + var builder = CreateTestDefaultActionModelBuilder(options); + var typeInfo = typeof(DerivedController).GetTypeInfo(); + var actionName = nameof(DerivedController.Authorize); + + // Act + var actions = builder.BuildActionModels(typeInfo, typeInfo.GetMethod(actionName)); + + // Assert + var action = Assert.Single(actions); + Assert.Equal("Authorize", action.ActionName); + Assert.Null(action.AttributeRouteModel); + var authorizeFilters = action.Filters.OfType(); + Assert.Single(authorizeFilters); + Assert.Equal(3, authorizeFilters.First().Policy.Requirements.Count); + } + [Fact] public void GetActions_ConventionallyRoutedActionWithHttpConstraints_AndInvalidRouteTemplateProvider() { // Arrange - var builder = new DefaultActionModelBuilder(); + var builder = CreateTestDefaultActionModelBuilder(); var typeInfo = typeof(ConventionallyRoutedController).GetTypeInfo(); var actionName = nameof(ConventionallyRoutedController.Delete); @@ -346,7 +372,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public void GetActions_ConventionallyRoutedAction_WithMultipleHttpConstraints() { // Arrange - var builder = new DefaultActionModelBuilder(); + var builder = CreateTestDefaultActionModelBuilder(); var typeInfo = typeof(ConventionallyRoutedController).GetTypeInfo(); var actionName = nameof(ConventionallyRoutedController.Details); @@ -365,7 +391,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public void GetActions_ConventionallyRoutedAction_WithMultipleOverlappingHttpConstraints() { // Arrange - var builder = new DefaultActionModelBuilder(); + var builder = CreateTestDefaultActionModelBuilder(); var typeInfo = typeof(ConventionallyRoutedController).GetTypeInfo(); var actionName = nameof(ConventionallyRoutedController.List); @@ -385,7 +411,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public void GetActions_AttributeRouteOnAction() { // Arrange - var builder = new DefaultActionModelBuilder(); + var builder = CreateTestDefaultActionModelBuilder(); var typeInfo = typeof(NoRouteAttributeOnControllerController).GetTypeInfo(); var actionName = nameof(NoRouteAttributeOnControllerController.Edit); @@ -410,7 +436,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public void GetActions_AttributeRouteOnAction_RouteAttribute() { // Arrange - var builder = new DefaultActionModelBuilder(); + var builder = CreateTestDefaultActionModelBuilder(); var typeInfo = typeof(NoRouteAttributeOnControllerController).GetTypeInfo(); var actionName = nameof(NoRouteAttributeOnControllerController.Update); @@ -434,7 +460,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public void GetActions_AttributeRouteOnAction_AcceptVerbsAttributeWithTemplate() { // Arrange - var builder = new DefaultActionModelBuilder(); + var builder = CreateTestDefaultActionModelBuilder(); var typeInfo = typeof(NoRouteAttributeOnControllerController).GetTypeInfo(); var actionName = nameof(NoRouteAttributeOnControllerController.List); @@ -458,7 +484,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public void GetActions_AttributeRouteOnAction_CreatesOneActionInforPerRouteTemplate() { // Arrange - var builder = new DefaultActionModelBuilder(); + var builder = CreateTestDefaultActionModelBuilder(); var typeInfo = typeof(NoRouteAttributeOnControllerController).GetTypeInfo(); var actionName = nameof(NoRouteAttributeOnControllerController.Index); @@ -489,7 +515,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public void GetActions_NoRouteOnController_AllowsConventionallyRoutedActions_OnTheSameController() { // Arrange - var builder = new DefaultActionModelBuilder(); + var builder = CreateTestDefaultActionModelBuilder(); var typeInfo = typeof(NoRouteAttributeOnControllerController).GetTypeInfo(); var actionName = nameof(NoRouteAttributeOnControllerController.Remove); @@ -514,7 +540,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public void GetActions_RouteAttributeOnController_CreatesAttributeRoute_ForNonAttributedActions(Type controller) { // Arrange - var builder = new DefaultActionModelBuilder(); + var builder = CreateTestDefaultActionModelBuilder(); var typeInfo = controller.GetTypeInfo(); // Act @@ -538,7 +564,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public void GetActions_RouteOnController_CreatesOneActionInforPerRouteTemplateOnAction(Type controller) { // Arrange - var builder = new DefaultActionModelBuilder(); + var builder = CreateTestDefaultActionModelBuilder(); var typeInfo = controller.GetTypeInfo(); // Act @@ -567,7 +593,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public void GetActions_MixedHttpVerbsAndRoutes_EmptyVerbWithRoute() { // Arrange - var builder = new DefaultActionModelBuilder(); + var builder = CreateTestDefaultActionModelBuilder(); var typeInfo = typeof(MixedHttpVerbsAndRouteAttributeController).GetTypeInfo(); var actionName = nameof(MixedHttpVerbsAndRouteAttributeController.VerbAndRoute); @@ -584,7 +610,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public void GetActions_MixedHttpVerbsAndRoutes_MultipleEmptyVerbsWithMultipleRoutes() { // Arrange - var builder = new DefaultActionModelBuilder(); + var builder = CreateTestDefaultActionModelBuilder(); var typeInfo = typeof(MixedHttpVerbsAndRouteAttributeController).GetTypeInfo(); var actionName = nameof(MixedHttpVerbsAndRouteAttributeController.MultipleVerbsAndRoutes); @@ -605,7 +631,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public void GetActions_MixedHttpVerbsAndRoutes_MultipleEmptyAndNonEmptyVerbsWithMultipleRoutes() { // Arrange - var builder = new DefaultActionModelBuilder(); + var builder = CreateTestDefaultActionModelBuilder(); var typeInfo = typeof(MixedHttpVerbsAndRouteAttributeController).GetTypeInfo(); var actionName = nameof(MixedHttpVerbsAndRouteAttributeController.MultipleVerbsWithAnyWithoutTemplateAndRoutes); @@ -629,7 +655,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public void GetActions_MixedHttpVerbsAndRoutes_MultipleEmptyAndNonEmptyVerbs() { // Arrange - var builder = new DefaultActionModelBuilder(); + var builder = CreateTestDefaultActionModelBuilder(); var typeInfo = typeof(MixedHttpVerbsAndRouteAttributeController).GetTypeInfo(); var actionName = nameof(MixedHttpVerbsAndRouteAttributeController.Invalid); @@ -646,8 +672,25 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels Assert.Equal(new string[] { "GET" }, action.HttpMethods); } + private static DefaultActionModelBuilder CreateTestDefaultActionModelBuilder( + AuthorizationOptions authOptions = null) + { + var options = new Mock>(); + options.Setup(o => o.Options).Returns(authOptions ?? new AuthorizationOptions()); + return new DefaultActionModelBuilder(options.Object); + } + + private static AccessibleActionModelBuilder CreateTestAccessibleActionModelBuilder() + { + var options = new Mock>(); + options.Setup(o => o.Options).Returns(new AuthorizationOptions()); + return new AccessibleActionModelBuilder(options.Object); + } + private class AccessibleActionModelBuilder : DefaultActionModelBuilder { + public AccessibleActionModelBuilder(IOptions options) : base(options) { } + public new bool IsAction([NotNull] TypeInfo typeInfo, [NotNull]MethodInfo methodInfo) { return base.IsAction(typeInfo, methodInfo); @@ -674,6 +717,12 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels { return base.Redirect(url + "#RedirectOverride"); } + + [Authorize(Policy = "Base")] + public virtual void Authorize() + { + } + } private class DerivedController : BaseController @@ -691,6 +740,12 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels { } + [Authorize(Policy = "Derived")] + public override void Authorize() + { + } + + public void GenericMethod() { } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/DefaultControllerModelBuilderTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/DefaultControllerModelBuilderTest.cs index 7505e2e021..3655d3f57b 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/DefaultControllerModelBuilderTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/DefaultControllerModelBuilderTest.cs @@ -2,9 +2,11 @@ // 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.Reflection; using System.Threading.Tasks; using Microsoft.AspNet.Mvc.Filters; +using Microsoft.AspNet.Security; using Xunit; namespace Microsoft.AspNet.Mvc.ApplicationModels @@ -15,8 +17,9 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public void BuildControllerModel_DerivedFromControllerClass_HasFilter() { // Arrange - var builder = new DefaultControllerModelBuilder(new DefaultActionModelBuilder(), - NullLoggerFactory.Instance); + var builder = new DefaultControllerModelBuilder(new DefaultActionModelBuilder(null), + NullLoggerFactory.Instance, + null); var typeInfo = typeof(StoreController).GetTypeInfo(); // Act @@ -27,14 +30,31 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels Assert.IsType(filter); } + [Fact] + public void BuildControllerModel_AuthorizeAttributeAddsAuthorizeFilter() + { + // Arrange + var builder = new DefaultControllerModelBuilder(new DefaultActionModelBuilder(null), + NullLoggerFactory.Instance, + null); + var typeInfo = typeof(AccountController).GetTypeInfo(); + + // Act + var model = builder.BuildControllerModel(typeInfo); + + // Assert + Assert.True(model.Filters.Any(f => f is AuthorizeFilter)); + } + // This class has a filter attribute, but doesn't implement any filter interfaces, // so ControllerFilter is not present. [Fact] public void BuildControllerModel_ClassWithoutFilterInterfaces_HasNoControllerFilter() { // Arrange - var builder = new DefaultControllerModelBuilder(new DefaultActionModelBuilder(), - NullLoggerFactory.Instance); + var builder = new DefaultControllerModelBuilder(new DefaultActionModelBuilder(null), + NullLoggerFactory.Instance, + null); var typeInfo = typeof(NoFiltersController).GetTypeInfo(); // Act @@ -49,8 +69,9 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public void BuildControllerModel_ClassWithFilterInterfaces_HasFilter() { // Arrange - var builder = new DefaultControllerModelBuilder(new DefaultActionModelBuilder(), - NullLoggerFactory.Instance); + var builder = new DefaultControllerModelBuilder(new DefaultActionModelBuilder(null), + NullLoggerFactory.Instance, + null); var typeInfo = typeof(SomeFiltersController).GetTypeInfo(); // Act @@ -65,8 +86,9 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public void BuildControllerModel_ClassWithFilterInterfaces_UnsupportedType() { // Arrange - var builder = new DefaultControllerModelBuilder(new DefaultActionModelBuilder(), - NullLoggerFactory.Instance); + var builder = new DefaultControllerModelBuilder(new DefaultActionModelBuilder(null), + NullLoggerFactory.Instance, + null); var typeInfo = typeof(UnsupportedFiltersController).GetTypeInfo(); // Act @@ -81,11 +103,16 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels } [Produces("application/json")] - private class NoFiltersController + public class NoFiltersController { } - private class SomeFiltersController : IAsyncActionFilter, IResultFilter + [Authorize] + public class AccountController + { + } + + public class SomeFiltersController : IAsyncActionFilter, IResultFilter { public Task OnActionExecutionAsync( [NotNull] ActionExecutingContext context, @@ -121,4 +148,4 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels } } } -} +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionDescriptorProviderTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionDescriptorProviderTests.cs index 859eb8123e..82917abd17 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionDescriptorProviderTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionDescriptorProviderTests.cs @@ -1363,8 +1363,9 @@ namespace Microsoft.AspNet.Mvc.Test IEnumerable filters = null) { var controllerTypeProvider = new FixedSetControllerTypeProvider(new[] { controllerTypeInfo }); - var controllerModelBuilder = new DefaultControllerModelBuilder(new DefaultActionModelBuilder(), - NullLoggerFactory.Instance); + var controllerModelBuilder = new DefaultControllerModelBuilder(new DefaultActionModelBuilder(null), + NullLoggerFactory.Instance, + null); var provider = new ControllerActionDescriptorProvider( controllerTypeProvider, @@ -1380,8 +1381,9 @@ namespace Microsoft.AspNet.Mvc.Test params TypeInfo[] controllerTypeInfo) { var controllerTypeProvider = new FixedSetControllerTypeProvider(controllerTypeInfo); - var controllerModelBuilder = new DefaultControllerModelBuilder(new DefaultActionModelBuilder(), - NullLoggerFactory.Instance); + var controllerModelBuilder = new DefaultControllerModelBuilder(new DefaultActionModelBuilder(null), + NullLoggerFactory.Instance, + null); var provider = new ControllerActionDescriptorProvider( controllerTypeProvider, @@ -1398,8 +1400,9 @@ namespace Microsoft.AspNet.Mvc.Test IApplicationModelConvention convention) { var controllerTypeProvider = new FixedSetControllerTypeProvider(new[] { type }); - var modelBuilder = new DefaultControllerModelBuilder(new DefaultActionModelBuilder(), - NullLoggerFactory.Instance); + var modelBuilder = new DefaultControllerModelBuilder(new DefaultActionModelBuilder(null), + NullLoggerFactory.Instance, + null); var options = new MockMvcOptionsAccessor(); options.Options.Conventions.Add(convention); @@ -1415,8 +1418,9 @@ namespace Microsoft.AspNet.Mvc.Test private IEnumerable GetDescriptors(params TypeInfo[] controllerTypeInfos) { var controllerTypeProvider = new FixedSetControllerTypeProvider(controllerTypeInfos); - var modelBuilder = new DefaultControllerModelBuilder(new DefaultActionModelBuilder(), - NullLoggerFactory.Instance); + var modelBuilder = new DefaultControllerModelBuilder(new DefaultActionModelBuilder(null), + NullLoggerFactory.Instance, + null); var provider = new ControllerActionDescriptorProvider( controllerTypeProvider, diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/DefaultActionDescriptorCollectionProviderLoggingTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/DefaultActionDescriptorCollectionProviderLoggingTest.cs index 5efdd49675..ead46a83e0 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/DefaultActionDescriptorCollectionProviderLoggingTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/DefaultActionDescriptorCollectionProviderLoggingTest.cs @@ -5,6 +5,7 @@ using System.ComponentModel.Design; using System.Linq; using System.Reflection; using Microsoft.AspNet.Mvc.ApplicationModels; +using Microsoft.AspNet.Security; using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.DependencyInjection.NestedProviders; using Microsoft.Framework.Logging; @@ -123,8 +124,9 @@ namespace Microsoft.AspNet.Mvc.Logging ILoggerFactory loggerFactory, params TypeInfo[] controllerTypeInfo) { var controllerTypeProvider = new FixedSetControllerTypeProvider(controllerTypeInfo); - var modelBuilder = new DefaultControllerModelBuilder(new DefaultActionModelBuilder(), - loggerFactory); + var modelBuilder = new DefaultControllerModelBuilder(new DefaultActionModelBuilder(null), + loggerFactory, + null); var provider = new ControllerActionDescriptorProvider( controllerTypeProvider, diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/DefaultActionSelectorTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/DefaultActionSelectorTests.cs index 2cd5e37b66..882b2efdc4 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/DefaultActionSelectorTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/DefaultActionSelectorTests.cs @@ -765,8 +765,9 @@ namespace Microsoft.AspNet.Mvc .ToList(); var controllerTypeProvider = new FixedSetControllerTypeProvider(controllerTypes); - var modelBuilder = new DefaultControllerModelBuilder(new DefaultActionModelBuilder(), - NullLoggerFactory.Instance); + var modelBuilder = new DefaultControllerModelBuilder(new DefaultActionModelBuilder(null), + NullLoggerFactory.Instance, + null); return new ControllerActionDescriptorProvider( controllerTypeProvider, diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Filters/AuthorizeAttributeTestsBase.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Filters/AuthorizeAttributeTestsBase.cs deleted file mode 100644 index c6a50844c4..0000000000 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Filters/AuthorizeAttributeTestsBase.cs +++ /dev/null @@ -1,65 +0,0 @@ -// 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.Security.Claims; -using Microsoft.AspNet.Http; -using Microsoft.AspNet.Routing; -using Microsoft.Framework.DependencyInjection; -using Microsoft.Framework.DependencyInjection.Fallback; -using Moq; - -namespace Microsoft.AspNet.Mvc.Core.Test -{ - public class AuthorizeAttributeTestsBase - { - protected AuthorizationContext GetAuthorizationContext(Action registerServices, bool anonymous = false) - { - var validUser = new ClaimsPrincipal( - new ClaimsIdentity( - new Claim[] { - new Claim("Permission", "CanViewPage"), - new Claim(ClaimTypes.Role, "Administrator"), - new Claim(ClaimTypes.Role, "User"), - new Claim(ClaimTypes.NameIdentifier, "John")}, - "Basic")); - - validUser.AddIdentity( - new ClaimsIdentity( - new Claim[] { - new Claim("Permission", "CupBearer"), - new Claim(ClaimTypes.Role, "Token"), - new Claim(ClaimTypes.NameIdentifier, "John Bear")}, - "Bearer")); - - // ServiceProvider - var serviceCollection = new ServiceCollection(); - if (registerServices != null) - { - registerServices(serviceCollection); - } - - var serviceProvider = serviceCollection.BuildServiceProvider(); - - // HttpContext - var httpContext = new Mock(); - httpContext.SetupGet(c => c.User).Returns(anonymous ? null : validUser); - httpContext.SetupGet(c => c.RequestServices).Returns(serviceProvider); - - // AuthorizationContext - var actionContext = new ActionContext( - httpContext: httpContext.Object, - routeData: new RouteData(), - actionDescriptor: null - ); - - var authorizationContext = new AuthorizationContext( - actionContext, - Enumerable.Empty().ToList() - ); - - return authorizationContext; - } - } -} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Filters/AuthorizeAttributeTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Filters/AuthorizeFilterTest.cs similarity index 52% rename from test/Microsoft.AspNet.Mvc.Core.Test/Filters/AuthorizeAttributeTests.cs rename to test/Microsoft.AspNet.Mvc.Core.Test/Filters/AuthorizeFilterTest.cs index ba77fc685a..135735b935 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Filters/AuthorizeAttributeTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Filters/AuthorizeFilterTest.cs @@ -1,33 +1,39 @@ // 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.Security.Claims; using System.Threading.Tasks; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.Routing; using Microsoft.AspNet.Security; using Microsoft.AspNet.WebUtilities; using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.DependencyInjection.Fallback; using Moq; using Xunit; -namespace Microsoft.AspNet.Mvc.Core.Test +namespace Microsoft.AspNet.Mvc.Test { - public class AuthorizeAttributeTests : AuthorizeAttributeTestsBase + public class AuthorizeFilterTest { + [Fact] + public void InvalidUser() + { + var authorizationContext = GetAuthorizationContext(services => services.AddAuthorization()); + Assert.True(authorizationContext.HttpContext.User.Identities.Any(i => i.IsAuthenticated)); + } + [Fact] public async Task Invoke_ValidClaimShouldNotFail() { // Arrange - var authorizeAttribute = new AuthorizeAttribute("CanViewPage"); - var authorizationContext = GetAuthorizationContext(services => - { - services.AddAuthorization(null, options => - { - var policy = new AuthorizationPolicyBuilder().RequiresClaim("Permission", "CanViewPage"); - options.AddPolicy("CanViewPage", policy.Build()); - }); - }); + var authorizeFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequiresClaim("Permission", "CanViewPage").Build()); + var authorizationContext = GetAuthorizationContext(services => services.AddAuthorization()); // Act - await authorizeAttribute.OnAuthorizationAsync(authorizationContext); + await authorizeFilter.OnAuthorizationAsync(authorizationContext); // Assert Assert.Null(authorizationContext.Result); @@ -38,13 +44,13 @@ namespace Microsoft.AspNet.Mvc.Core.Test { // Arrange var authorizationOptions = new AuthorizationOptions(); - var authorizeAttribute = new AuthorizeAttribute(); + var authorizeFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build()); var authorizationContext = GetAuthorizationContext(services => services.AddAuthorization(), anonymous: true); // Act - await authorizeAttribute.OnAuthorizationAsync(authorizationContext); + await authorizeFilter.OnAuthorizationAsync(authorizationContext); // Assert Assert.NotNull(authorizationContext.Result); @@ -54,7 +60,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test public async Task Invoke_EmptyClaimsWithAllowAnonymousAttributeShouldNotRejectAnonymousUser() { // Arrange - var authorizeAttribute = new AuthorizeAttribute(); + var authorizeFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build()); var authorizationContext = GetAuthorizationContext(services => { services.AddAuthorization(); @@ -65,7 +71,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test authorizationContext.Filters.Add(new AllowAnonymousAttribute()); // Act - await authorizeAttribute.OnAuthorizationAsync(authorizationContext); + await authorizeFilter.OnAuthorizationAsync(authorizationContext); // Assert Assert.Null(authorizationContext.Result); @@ -75,7 +81,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test public async Task Invoke_EmptyClaimsShouldAuthorizeAuthenticatedUser() { // Arrange - var authorizeAttribute = new AuthorizeAttribute(); + var authorizeFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build()); var authorizationContext = GetAuthorizationContext(services => { services.AddAuthorization(); @@ -83,7 +89,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test }); // Act - await authorizeAttribute.OnAuthorizationAsync(authorizationContext); + await authorizeFilter.OnAuthorizationAsync(authorizationContext); // Assert Assert.Null(authorizationContext.Result); @@ -93,19 +99,15 @@ namespace Microsoft.AspNet.Mvc.Core.Test public async Task Invoke_SingleValidClaimShouldSucceed() { // Arrange - var authorizeAttribute = new AuthorizeAttribute("CanViewCommentOrPage"); + var authorizeFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequiresClaim("Permission", "CanViewComment", "CanViewPage").Build()); var authorizationContext = GetAuthorizationContext(services => { - services.AddAuthorization(null, options => - { - var policy = new AuthorizationPolicyBuilder().RequiresClaim("Permission", "CanViewComment", "CanViewPage"); - options.AddPolicy("CanViewCommentOrPage", policy.Build()); - }); + services.AddAuthorization(); services.AddTransient(); }); // Act - await authorizeAttribute.OnAuthorizationAsync(authorizationContext); + await authorizeFilter.OnAuthorizationAsync(authorizationContext); // Assert Assert.Null(authorizationContext.Result); @@ -115,7 +117,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test public async Task Invoke_RequireAdminRoleShouldFailWithNoHandlers() { // Arrange - var authorizeAttribute = new AuthorizeAttribute { Roles = "Administrator" }; + var authorizeFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequiresRole("Administrator").Build()); var authorizationContext = GetAuthorizationContext(services => { services.AddOptions(); @@ -123,7 +125,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test }); // Act - await authorizeAttribute.OnAuthorizationAsync(authorizationContext); + await authorizeFilter.OnAuthorizationAsync(authorizationContext); // Assert Assert.NotNull(authorizationContext.Result); @@ -133,7 +135,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test public async Task Invoke_RequireAdminAndUserRoleWithNoPolicyShouldSucceed() { // Arrange - var authorizeAttribute = new AuthorizeAttribute { Roles = "Administrator,User" }; + var authorizeFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequiresRole("Administrator").Build()); var authorizationContext = GetAuthorizationContext(services => { services.AddAuthorization(); @@ -141,7 +143,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test }); // Act - await authorizeAttribute.OnAuthorizationAsync(authorizationContext); + await authorizeFilter.OnAuthorizationAsync(authorizationContext); // Assert Assert.Null(authorizationContext.Result); @@ -151,7 +153,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test public async Task Invoke_RequireUnknownRoleShouldFail() { // Arrange - var authorizeAttribute = new AuthorizeAttribute { Roles = "Wut" }; + var authorizeFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequiresRole("Wut").Build()); var authorizationContext = GetAuthorizationContext(services => { services.AddAuthorization(); @@ -159,7 +161,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test }); // Act - await authorizeAttribute.OnAuthorizationAsync(authorizationContext); + await authorizeFilter.OnAuthorizationAsync(authorizationContext); // Assert Assert.NotNull(authorizationContext.Result); @@ -169,19 +171,18 @@ namespace Microsoft.AspNet.Mvc.Core.Test public async Task Invoke_RequireAdminRoleButFailPolicyShouldFail() { // Arrange - var authorizeAttribute = new AuthorizeAttribute { Roles = "Administrator", Policy = "Basic" }; + var authorizeFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder() + .RequiresRole("Administrator") + .RequiresClaim("Permission", "CanViewComment") + .Build()); var authorizationContext = GetAuthorizationContext(services => { - services.AddAuthorization(null, options => - { - var policy = new AuthorizationPolicyBuilder().RequiresClaim("Permission", "CanViewComment"); - options.AddPolicy("CanViewComment", policy.Build()); - }); + services.AddAuthorization(); services.AddTransient(); }); // Act - await authorizeAttribute.OnAuthorizationAsync(authorizationContext); + await authorizeFilter.OnAuthorizationAsync(authorizationContext); // Assert Assert.NotNull(authorizationContext.Result); @@ -191,19 +192,17 @@ namespace Microsoft.AspNet.Mvc.Core.Test public async Task Invoke_InvalidClaimShouldFail() { // Arrange - var authorizeAttribute = new AuthorizeAttribute("CanViewComment"); + var authorizeFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder() + .RequiresClaim("Permission", "CanViewComment") + .Build()); var authorizationContext = GetAuthorizationContext(services => { - services.AddAuthorization(null, options => - { - var policy = new AuthorizationPolicyBuilder().RequiresClaim("Permission", "CanViewComment"); - options.AddPolicy("CanViewComment", policy.Build()); - }); + services.AddAuthorization(); services.AddTransient(); }); // Act - await authorizeAttribute.OnAuthorizationAsync(authorizationContext); + await authorizeFilter.OnAuthorizationAsync(authorizationContext); // Assert Assert.NotNull(authorizationContext.Result); @@ -216,14 +215,16 @@ namespace Microsoft.AspNet.Mvc.Core.Test bool authorizationServiceIsCalled = false; var authorizationService = new Mock(); authorizationService - .Setup(x => x.AuthorizeAsync("CanViewComment", null, null)) + .Setup(x => x.AuthorizeAsync(null, null, "CanViewComment")) .Returns(() => { authorizationServiceIsCalled = true; return Task.FromResult(true); }); - var authorizeAttribute = new AuthorizeAttribute("CanViewComment"); + var authorizeFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder() + .RequiresClaim("Permission", "CanViewComment") + .Build()); var authorizationContext = GetAuthorizationContext(services => services.AddInstance(authorizationService.Object) ); @@ -231,7 +232,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test authorizationContext.Result = new HttpStatusCodeResult(StatusCodes.Status401Unauthorized); // Act - await authorizeAttribute.OnAuthorizationAsync(authorizationContext); + await authorizeFilter.OnAuthorizationAsync(authorizationContext); // Assert Assert.False(authorizationServiceIsCalled); @@ -241,19 +242,17 @@ namespace Microsoft.AspNet.Mvc.Core.Test public async Task Invoke_FailWhenLookingForClaimInOtherIdentity() { // Arrange - var authorizeAttribute = new AuthorizeAttribute("CanViewComment"); + var authorizeFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder() + .RequiresClaim("Permission", "CanViewComment") + .Build()); var authorizationContext = GetAuthorizationContext(services => { - services.AddAuthorization(null, options => - { - var policy = new AuthorizationPolicyBuilder("Bearer").RequiresClaim("Permission", "CanViewComment"); - options.AddPolicy("CanViewComment", policy.Build()); - }); + services.AddAuthorization(); services.AddTransient(); }); // Act - await authorizeAttribute.OnAuthorizationAsync(authorizationContext); + await authorizeFilter.OnAuthorizationAsync(authorizationContext); // Assert Assert.NotNull(authorizationContext.Result); @@ -263,30 +262,10 @@ namespace Microsoft.AspNet.Mvc.Core.Test public async Task Invoke_CanLookingForClaimsInMultipleIdentities() { // Arrange - var authorizeAttribute = new AuthorizeAttribute("CanViewCommentCupBearer"); - var authorizationContext = GetAuthorizationContext(services => - { - services.AddAuthorization(null, options => - { - var policy = new AuthorizationPolicyBuilder("Basic", "Bearer") - .RequiresClaim("Permission", "CanViewComment") - .RequiresClaim("Permission", "CupBearer"); - options.AddPolicy("CanViewComment", policy.Build()); - }); - services.AddTransient(); - }); - - // Act - await authorizeAttribute.OnAuthorizationAsync(authorizationContext); - - // Assert - Assert.NotNull(authorizationContext.Result); - } - - public async Task Invoke_NoPoliciesShouldNotFail() - { - // Arrange - var authorizeAttribute = new AuthorizeAttribute("CanViewPage"); + var authorizeFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder("Basic", "Bearer") + .RequiresClaim("Permission", "CanViewComment") + .RequiresClaim("Permission", "CupBearer") + .Build()); var authorizationContext = GetAuthorizationContext(services => { services.AddAuthorization(); @@ -294,10 +273,80 @@ namespace Microsoft.AspNet.Mvc.Core.Test }); // Act - await authorizeAttribute.OnAuthorizationAsync(authorizationContext); + await authorizeFilter.OnAuthorizationAsync(authorizationContext); // Assert - Assert.Null(authorizationContext.Result); + Assert.NotNull(authorizationContext.Result); + } + + [Fact] + public async Task Invoke_EmptyPolicyWillFail() + { + // Arrange + var authorizeFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().Build()); + var authorizationContext = GetAuthorizationContext(services => + { + services.AddAuthorization(); + services.AddTransient(); + }); + + // Act + await authorizeFilter.OnAuthorizationAsync(authorizationContext); + + // Assert + Assert.NotNull(authorizationContext.Result); + } + + private AuthorizationContext GetAuthorizationContext(Action registerServices, bool anonymous = false) + { + var validUser = new ClaimsPrincipal( + new ClaimsIdentity( + new Claim[] { + new Claim("Permission", "CanViewPage"), + new Claim(ClaimTypes.Role, "Administrator"), + new Claim(ClaimTypes.Role, "User"), + new Claim(ClaimTypes.NameIdentifier, "John")}, + "Basic")); + + validUser.AddIdentity( + new ClaimsIdentity( + new Claim[] { + new Claim("Permission", "CupBearer"), + new Claim(ClaimTypes.Role, "Token"), + new Claim(ClaimTypes.NameIdentifier, "John Bear")}, + "Bearer")); + + // ServiceProvider + var serviceCollection = new ServiceCollection(); + if (registerServices != null) + { + registerServices(serviceCollection); + } + + var serviceProvider = serviceCollection.BuildServiceProvider(); + + // HttpContext + var httpContext = new Mock(); + httpContext.SetupProperty(c => c.User); + if (!anonymous) + { + httpContext.Object.User = validUser; + } + httpContext.SetupGet(c => c.RequestServices).Returns(serviceProvider); + + // AuthorizationContext + var actionContext = new ActionContext( + httpContext: httpContext.Object, + routeData: new RouteData(), + actionDescriptor: null + ); + + var authorizationContext = new AuthorizationContext( + actionContext, + Enumerable.Empty().ToList() + ); + + return authorizationContext; } } } diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/FiltersTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/FiltersTest.cs index fa3c8bdff4..d2d7008427 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/FiltersTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/FiltersTest.cs @@ -106,8 +106,12 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests Assert.Equal("4", await response.Content.ReadAsStringAsync()); } - [Fact] - public async Task CanAuthorizeParticularUsers() + + [Theory] + [InlineData("AdminRole")] + [InlineData("InteractiveUsers")] + [InlineData("ApiManagers")] + public async Task CanAuthorize(string testAction) { // Arrange var server = TestServer.Create(_services, _app); @@ -115,7 +119,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests // Act var response = await client.GetAsync( - "http://localhost/AuthorizeUser/ReturnHelloWorldOnlyForAuthorizedUser"); + "http://localhost/AuthorizeUser/"+testAction); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); diff --git a/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/ApiControllerActionDiscoveryTest.cs b/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/ApiControllerActionDiscoveryTest.cs index 4836a1c585..f84ef77be8 100644 --- a/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/ApiControllerActionDiscoveryTest.cs +++ b/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/ApiControllerActionDiscoveryTest.cs @@ -372,8 +372,9 @@ namespace System.Web.Http var assemblyProvider = new FixedSetAssemblyProvider(); assemblyProvider.CandidateAssemblies.Add(GetType().GetTypeInfo().Assembly); var controllerTypeProvider = new NamespaceFilteredControllerTypeProvider(assemblyProvider); - var modelBuilder = new DefaultControllerModelBuilder(new DefaultActionModelBuilder(), - NullLoggerFactory.Instance); + var modelBuilder = new DefaultControllerModelBuilder(new DefaultActionModelBuilder(null), + NullLoggerFactory.Instance, + null); var filterProvider = new Mock(); filterProvider diff --git a/test/WebSites/FiltersWebSite/AuthorizeBasicMiddleware.cs b/test/WebSites/FiltersWebSite/AuthorizeBasicMiddleware.cs index 8f17b77b03..5eb2decb73 100644 --- a/test/WebSites/FiltersWebSite/AuthorizeBasicMiddleware.cs +++ b/test/WebSites/FiltersWebSite/AuthorizeBasicMiddleware.cs @@ -3,6 +3,7 @@ using System; using System.Security.Claims; +using System.Threading.Tasks; using Microsoft.AspNet.Builder; using Microsoft.AspNet.Http.Security; using Microsoft.AspNet.Security; @@ -11,51 +12,21 @@ using Microsoft.Framework.OptionsModel; namespace FiltersWebSite { - public class BasicOptions : AuthenticationOptions - { - public BasicOptions() - { - AuthenticationType = "Basic"; - AuthenticationMode = AuthenticationMode.Passive; - } - - } - public class AuthorizeBasicMiddleware : AuthenticationMiddleware { public AuthorizeBasicMiddleware( RequestDelegate next, IServiceProvider services, - IOptions options) : - base(next, services, options, null) - { } + IOptions options, + string authType) : + base(next, services, options, + new ConfigureOptions(o => o.AuthenticationType = authType) { Name = authType }) + { + } protected override AuthenticationHandler CreateHandler() { return new BasicAuthenticationHandler(); } } - - public class BasicAuthenticationHandler : AuthenticationHandler - { - protected override void ApplyResponseChallenge() - { - } - - protected override void ApplyResponseGrant() - { - } - - protected override AuthenticationTicket AuthenticateCore() - { - var id = new ClaimsIdentity( - new Claim[] { - new Claim("Permission", "CanViewPage"), - new Claim(ClaimTypes.Role, "Administrator"), - new Claim(ClaimTypes.NameIdentifier, "John")}, - "Basic"); - - return new AuthenticationTicket(id, new AuthenticationProperties()); - } - } } \ No newline at end of file diff --git a/test/WebSites/FiltersWebSite/BasicAuthenticationHandler.cs b/test/WebSites/FiltersWebSite/BasicAuthenticationHandler.cs new file mode 100644 index 0000000000..3b7dd5221a --- /dev/null +++ b/test/WebSites/FiltersWebSite/BasicAuthenticationHandler.cs @@ -0,0 +1,38 @@ +// 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.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.Http.Security; +using Microsoft.AspNet.Security; +using Microsoft.AspNet.Security.Infrastructure; +using Microsoft.Framework.OptionsModel; + +namespace FiltersWebSite +{ + public class BasicAuthenticationHandler : AuthenticationHandler + { + protected override void ApplyResponseChallenge() + { + } + + protected override void ApplyResponseGrant() + { + } + + protected override AuthenticationTicket AuthenticateCore() + { + var id = new ClaimsIdentity( + new Claim[] { + new Claim("Permission", "CanViewPage"), + new Claim("Manager", "yes"), + new Claim(ClaimTypes.Role, "Administrator"), + new Claim(ClaimTypes.NameIdentifier, "John") + }, + Options.AuthenticationType); + return new AuthenticationTicket(id, new AuthenticationProperties()); + } + } +} \ No newline at end of file diff --git a/test/WebSites/FiltersWebSite/BasicOptions.cs b/test/WebSites/FiltersWebSite/BasicOptions.cs new file mode 100644 index 0000000000..a76d086bad --- /dev/null +++ b/test/WebSites/FiltersWebSite/BasicOptions.cs @@ -0,0 +1,22 @@ +// 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.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.Http.Security; +using Microsoft.AspNet.Security; +using Microsoft.AspNet.Security.Infrastructure; +using Microsoft.Framework.OptionsModel; + +namespace FiltersWebSite +{ + public class BasicOptions : AuthenticationOptions + { + public BasicOptions() + { + AuthenticationMode = AuthenticationMode.Passive; + } + } +} \ No newline at end of file diff --git a/test/WebSites/FiltersWebSite/Controllers/AuthorizeUserController.cs b/test/WebSites/FiltersWebSite/Controllers/AuthorizeUserController.cs index 27c8ba2a7c..314f1ec450 100644 --- a/test/WebSites/FiltersWebSite/Controllers/AuthorizeUserController.cs +++ b/test/WebSites/FiltersWebSite/Controllers/AuthorizeUserController.cs @@ -2,15 +2,27 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNet.Mvc; +using Microsoft.AspNet.Security; namespace FiltersWebSite { - [AuthorizeUser] - [Authorize("RequireBasic")] + [Authorize("Api")] public class AuthorizeUserController : Controller { - [Authorize("CanViewPage")] - public string ReturnHelloWorldOnlyForAuthorizedUser() + [Authorize("Api-Manager")] + public string ApiManagers() + { + return "Hello World!"; + } + + [Authorize(Roles = "Administrator")] + public string AdminRole() + { + return "Hello World!"; + } + + [Authorize("Interactive")] + public string InteractiveUsers() { return "Hello World!"; } @@ -27,6 +39,5 @@ namespace FiltersWebSite { return "Hello World!"; } - } } \ No newline at end of file diff --git a/test/WebSites/FiltersWebSite/ManagerHandler.cs b/test/WebSites/FiltersWebSite/ManagerHandler.cs new file mode 100644 index 0000000000..02170821e2 --- /dev/null +++ b/test/WebSites/FiltersWebSite/ManagerHandler.cs @@ -0,0 +1,21 @@ +// 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.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNet.Security; +using Microsoft.Framework.DependencyInjection; + +namespace FiltersWebSite +{ + public class ManagerHandler : AuthorizationHandler + { + public override void Handle(Microsoft.AspNet.Security.AuthorizationContext context, OperationAuthorizationRequirement requirement) + { + if (context.User.HasClaim("Manager", "yes")) + { + context.Succeed(requirement); + } + } + } +} diff --git a/test/WebSites/FiltersWebSite/Operations.cs b/test/WebSites/FiltersWebSite/Operations.cs new file mode 100644 index 0000000000..faffd4036b --- /dev/null +++ b/test/WebSites/FiltersWebSite/Operations.cs @@ -0,0 +1,14 @@ +// 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 Microsoft.AspNet.Security; + +namespace FiltersWebSite +{ + public static class Operations + { + public static OperationAuthorizationRequirement Edit = new OperationAuthorizationRequirement { Name = "Edit" }; + public static OperationAuthorizationRequirement Create = new OperationAuthorizationRequirement { Name = "Create" }; + public static OperationAuthorizationRequirement Delete = new OperationAuthorizationRequirement { Name = "Delete" }; + } +} diff --git a/test/WebSites/FiltersWebSite/Startup.cs b/test/WebSites/FiltersWebSite/Startup.cs index 848e9ed791..df9065760c 100644 --- a/test/WebSites/FiltersWebSite/Startup.cs +++ b/test/WebSites/FiltersWebSite/Startup.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.Security.Claims; +using System.Threading.Tasks; using Microsoft.AspNet.Builder; using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Security; @@ -18,23 +19,32 @@ namespace FiltersWebSite app.UseServices(services => { services.AddMvc(configuration); - services.Configure(options => + services.ConfigureAuthorization(options => { // This policy cannot succeed since it has no requirements - options.AddPolicy("Impossible", - new AuthorizationPolicyBuilder() - .Build()); - options.AddPolicy("RequireBasic", - new AuthorizationPolicyBuilder("Basic") - .RequiresClaim(ClaimTypes.NameIdentifier) - .Build()); - options.AddPolicy("CanViewPage", - new AuthorizationPolicyBuilder() - .RequiresClaim("Permission", "CanViewPage") - .Build()); + options.AddPolicy("Impossible", policy => { }); + options.AddPolicy("Api", policy => + { + policy.ActiveAuthenticationTypes.Add("Api"); + policy.RequiresClaim(ClaimTypes.NameIdentifier); + }); + options.AddPolicy("Api-Manager", policy => + { + policy.ActiveAuthenticationTypes.Add("Api"); + policy.Requirements.Add(Operations.Edit); + }); + options.AddPolicy("Interactive", policy => + { + policy.ActiveAuthenticationTypes.Add("Interactive"); + policy.RequiresClaim(ClaimTypes.NameIdentifier) + .RequiresClaim("Permission", "CanViewPage"); + }); }); services.AddSingleton(); services.AddSingleton(); + services.AddTransient(); + services.Configure(o => o.AuthenticationType = "Api", "Api"); + services.Configure(o => o.AuthenticationType = "Interactive", "Interactive"); services.Configure(options => { @@ -48,7 +58,8 @@ namespace FiltersWebSite app.UseErrorReporter(); - app.UseMiddleware(); + app.UseMiddleware("Interactive"); + app.UseMiddleware("Api"); app.UseMvc(routes => {