Cors Support in MVC.
This commit is contained in:
parent
ee4ffea294
commit
015edefa96
15
Mvc.sln
15
Mvc.sln
|
|
@ -152,6 +152,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "TempDataWebSite", "test\Web
|
|||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.TestCommon", "test\Microsoft.AspNet.Mvc.TestCommon\Microsoft.AspNet.Mvc.TestCommon.xproj", "{F504357E-C2E1-4818-BA5C-9A2EAC25FEE5}"
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CorsWebSite", "test\WebSites\CorsWebSite\CorsWebSite.xproj", "{94BA134D-04B3-48AA-BA55-5A4DB8640F2D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -894,6 +896,18 @@ Global
|
|||
{F504357E-C2E1-4818-BA5C-9A2EAC25FEE5}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{F504357E-C2E1-4818-BA5C-9A2EAC25FEE5}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{F504357E-C2E1-4818-BA5C-9A2EAC25FEE5}.Release|x86.Build.0 = Release|Any CPU
|
||||
{94BA134D-04B3-48AA-BA55-5A4DB8640F2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{94BA134D-04B3-48AA-BA55-5A4DB8640F2D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{94BA134D-04B3-48AA-BA55-5A4DB8640F2D}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{94BA134D-04B3-48AA-BA55-5A4DB8640F2D}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{94BA134D-04B3-48AA-BA55-5A4DB8640F2D}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{94BA134D-04B3-48AA-BA55-5A4DB8640F2D}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{94BA134D-04B3-48AA-BA55-5A4DB8640F2D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{94BA134D-04B3-48AA-BA55-5A4DB8640F2D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{94BA134D-04B3-48AA-BA55-5A4DB8640F2D}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{94BA134D-04B3-48AA-BA55-5A4DB8640F2D}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{94BA134D-04B3-48AA-BA55-5A4DB8640F2D}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{94BA134D-04B3-48AA-BA55-5A4DB8640F2D}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -968,5 +982,6 @@ Global
|
|||
{BCDB13A6-7D6E-485E-8424-A156432B71AC} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||
{8AEB631E-AB74-4D2E-83FB-8931EE10D9D3} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||
{F504357E-C2E1-4818-BA5C-9A2EAC25FEE5} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
|
||||
{94BA134D-04B3-48AA-BA55-5A4DB8640F2D} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNet.Authorization;
|
||||
using Microsoft.AspNet.Cors;
|
||||
using Microsoft.AspNet.Cors.Core;
|
||||
using Microsoft.AspNet.Mvc.Description;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
using Microsoft.AspNet.Mvc.Routing;
|
||||
|
|
@ -21,9 +23,9 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
|
|||
{
|
||||
private readonly AuthorizationOptions _authorizationOptions;
|
||||
|
||||
public DefaultActionModelBuilder(IOptions<AuthorizationOptions> options)
|
||||
public DefaultActionModelBuilder(IOptions<AuthorizationOptions> authorizationOptions)
|
||||
{
|
||||
_authorizationOptions = options?.Options ?? new AuthorizationOptions();
|
||||
_authorizationOptions = authorizationOptions?.Options ?? new AuthorizationOptions();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -266,6 +268,18 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
|
|||
AddRange(actionModel.ActionConstraints, attributes.OfType<IActionConstraintMetadata>());
|
||||
AddRange(actionModel.Filters, attributes.OfType<IFilter>());
|
||||
|
||||
var enableCors = attributes.OfType<IEnableCorsAttribute>().SingleOrDefault();
|
||||
if (enableCors != null)
|
||||
{
|
||||
actionModel.Filters.Add(new CorsAuthorizationFilterFactory(enableCors.PolicyName));
|
||||
}
|
||||
|
||||
var disableCors = attributes.OfType<IDisableCorsAttribute>().SingleOrDefault();
|
||||
if (disableCors != null)
|
||||
{
|
||||
actionModel.Filters.Add(new DisableCorsAuthorizationFilter());
|
||||
}
|
||||
|
||||
var policy = AuthorizationPolicy.Combine(_authorizationOptions, attributes.OfType<AuthorizeAttribute>());
|
||||
if (policy != null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNet.Authorization;
|
||||
using Microsoft.AspNet.Cors;
|
||||
using Microsoft.AspNet.Cors.Core;
|
||||
using Microsoft.AspNet.Mvc.Description;
|
||||
using Microsoft.AspNet.Mvc.Filters;
|
||||
using Microsoft.AspNet.Mvc.Routing;
|
||||
|
|
@ -31,11 +33,11 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
|
|||
public DefaultControllerModelBuilder(
|
||||
IActionModelBuilder actionModelBuilder,
|
||||
ILoggerFactory loggerFactory,
|
||||
IOptions<AuthorizationOptions> options)
|
||||
IOptions<AuthorizationOptions> authorizationOptions)
|
||||
{
|
||||
_actionModelBuilder = actionModelBuilder;
|
||||
_logger = loggerFactory.CreateLogger<DefaultControllerModelBuilder>();
|
||||
_authorizationOptions = options?.Options ?? new AuthorizationOptions();
|
||||
_authorizationOptions = authorizationOptions?.Options ?? new AuthorizationOptions();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -80,6 +82,18 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
|
|||
AddRange(controllerModel.Filters, attributes.OfType<IFilter>());
|
||||
AddRange(controllerModel.RouteConstraints, attributes.OfType<IRouteConstraintProvider>());
|
||||
|
||||
var enableCors = attributes.OfType<IEnableCorsAttribute>().SingleOrDefault();
|
||||
if (enableCors != null)
|
||||
{
|
||||
controllerModel.Filters.Add(new CorsAuthorizationFilterFactory(enableCors.PolicyName));
|
||||
}
|
||||
|
||||
var disableCors = attributes.OfType<IDisableCorsAttribute>().SingleOrDefault();
|
||||
if (disableCors != null)
|
||||
{
|
||||
controllerModel.Filters.Add(new DisableCorsAuthorizationFilter());
|
||||
}
|
||||
|
||||
var policy = AuthorizationPolicy.Combine(_authorizationOptions, attributes.OfType<AuthorizeAttribute>());
|
||||
if (policy != null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,5 +11,11 @@ namespace Microsoft.AspNet.Mvc
|
|||
/// User code should order at bigger than 0 or smaller than -2000.
|
||||
/// </summary>
|
||||
public static readonly int DefaultFrameworkSortOrder = -1000;
|
||||
|
||||
/// <summary>
|
||||
/// The default order for <see cref="CorsAuthorizationFilter"/>, <see cref="CorsAuthorizationFilterFactory"/>
|
||||
/// and <see cref="DisableCorsAuthorizationFilter"/>.
|
||||
/// </summary>
|
||||
public static readonly int DefaultCorsSortOrder = int.MaxValue - 100;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
// 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.Cors.Core;
|
||||
using Microsoft.AspNet.WebUtilities;
|
||||
using Microsoft.Framework.Internal;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="ICorsAuthorizationFilter"/> which ensures that an action does not run for a pre-flight request.
|
||||
/// </summary>
|
||||
public class DisableCorsAuthorizationFilter : ICorsAuthorizationFilter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public int Order
|
||||
{
|
||||
get
|
||||
{
|
||||
return DefaultOrder.DefaultCorsSortOrder;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task OnAuthorizationAsync([NotNull] AuthorizationContext context)
|
||||
{
|
||||
var accessControlRequestMethod =
|
||||
context.HttpContext.Request.Headers.Get(CorsConstants.AccessControlRequestMethod);
|
||||
if (string.Equals(
|
||||
context.HttpContext.Request.Method,
|
||||
CorsConstants.PreflightHttpMethod,
|
||||
StringComparison.Ordinal) &&
|
||||
accessControlRequestMethod != null)
|
||||
{
|
||||
// Short circuit if the request is preflight as that should not result in action execution.
|
||||
context.Result = new HttpStatusCodeResult(StatusCodes.Status200OK);
|
||||
}
|
||||
|
||||
// Let the action be executed.
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Cors.Core;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.WebUtilities;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Microsoft.Framework.Internal;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
/// <summary>
|
||||
/// A filter which applies the given <see cref="CorsPolicy"/> and adds appropriate response headers.
|
||||
/// </summary>
|
||||
public class CorsAuthorizationFilter : ICorsAuthorizationFilter
|
||||
{
|
||||
private ICorsService _corsService;
|
||||
private ICorsPolicyProvider _corsPolicyProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instace of <see cref="CorsAuthorizationFilter"/>.
|
||||
/// </summary>
|
||||
/// <param name="corsService">The <see cref="ICorsService"/>.</param>
|
||||
/// <param name="policyProvider">The <see cref="ICorsPolicyProvider"/>.</param>
|
||||
public CorsAuthorizationFilter(ICorsService corsService, ICorsPolicyProvider policyProvider)
|
||||
{
|
||||
_corsService = corsService;
|
||||
_corsPolicyProvider = policyProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The policy name used to fetch a <see cref="CorsPolicy"/>.
|
||||
/// </summary>
|
||||
public string PolicyName { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Order
|
||||
{
|
||||
get
|
||||
{
|
||||
return DefaultOrder.DefaultCorsSortOrder;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task OnAuthorizationAsync([NotNull] AuthorizationContext context)
|
||||
{
|
||||
// If this filter is not closest to the action, it is not applicable.
|
||||
if (!IsClosestToAction(context.Filters))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var httpContext = context.HttpContext;
|
||||
var request = httpContext.Request;
|
||||
if (request.Headers.ContainsKey(CorsConstants.Origin))
|
||||
{
|
||||
var policy = await _corsPolicyProvider.GetPolicyAsync(httpContext, PolicyName);
|
||||
var result = _corsService.EvaluatePolicy(context.HttpContext, policy);
|
||||
_corsService.ApplyResult(result, context.HttpContext.Response);
|
||||
|
||||
var accessControlRequestMethod =
|
||||
httpContext.Request.Headers.Get(CorsConstants.AccessControlRequestMethod);
|
||||
if (string.Equals(
|
||||
request.Method,
|
||||
CorsConstants.PreflightHttpMethod,
|
||||
StringComparison.Ordinal) &&
|
||||
accessControlRequestMethod != null)
|
||||
{
|
||||
// If this was a preflight, there is no need to run anything else.
|
||||
// Also the response is always 200 so that anyone after mvc can handle the pre flight request.
|
||||
context.Result = new HttpStatusCodeResult(StatusCodes.Status200OK);
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
// Continue with other filters and action.
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsClosestToAction(IEnumerable<IFilter> filters)
|
||||
{
|
||||
// If there are multiple ICorsAuthorizationFilter which are defined at the class and
|
||||
// at the action level, the one closest to the action overrides the others.
|
||||
// Since filterdescriptor collection is ordered (the last filter is the one closest to the action),
|
||||
// we apply this constraint only if there is no ICorsAuthorizationFilter after this.
|
||||
return filters.Last(filter => filter is ICorsAuthorizationFilter) == this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Cors.Core;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.WebUtilities;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Microsoft.Framework.Internal;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
/// <summary>
|
||||
/// A filter factory which creates a new instance of <see cref="CorsAuthorizationFilter"/>.
|
||||
/// </summary>
|
||||
public class CorsAuthorizationFilterFactory : IFilterFactory, IOrderedFilter
|
||||
{
|
||||
private readonly string _policyName;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new insntace of <see cref="CorsAuthorizationFilterFactory"/>.
|
||||
/// </summary>
|
||||
/// <param name="policyName"></param>
|
||||
public CorsAuthorizationFilterFactory(string policyName)
|
||||
{
|
||||
_policyName = policyName;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Order
|
||||
{
|
||||
get
|
||||
{
|
||||
return DefaultOrder.DefaultCorsSortOrder;
|
||||
}
|
||||
}
|
||||
|
||||
public IFilter CreateInstance([NotNull] IServiceProvider serviceProvider)
|
||||
{
|
||||
var filter = serviceProvider.GetRequiredService<CorsAuthorizationFilter>();
|
||||
filter.PolicyName = _policyName;
|
||||
return filter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.Cors.Core;
|
||||
using Microsoft.Framework.Internal;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
|
|
@ -54,8 +55,22 @@ namespace Microsoft.AspNet.Mvc
|
|||
}
|
||||
|
||||
var request = context.RouteContext.HttpContext.Request;
|
||||
var method = request.Method;
|
||||
if (request.Headers.ContainsKey(CorsConstants.Origin))
|
||||
{
|
||||
// Update the http method if it is preflight request.
|
||||
var accessControlRequestMethod = request.Headers.Get(CorsConstants.AccessControlRequestMethod);
|
||||
if (string.Equals(
|
||||
request.Method,
|
||||
CorsConstants.PreflightHttpMethod,
|
||||
StringComparison.Ordinal) &&
|
||||
accessControlRequestMethod != null)
|
||||
{
|
||||
method = accessControlRequestMethod;
|
||||
}
|
||||
}
|
||||
|
||||
return (HttpMethods.Any(m => m.Equals(request.Method, StringComparison.Ordinal)));
|
||||
return (HttpMethods.Any(m => m.Equals(method, StringComparison.Ordinal)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
/// <summary>
|
||||
/// A filter which can be used to enable/disable cors support for a resource.
|
||||
/// </summary>
|
||||
public interface ICorsAuthorizationFilter : IAsyncAuthorizationFilter, IOrderedFilter
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
"dependencies": {
|
||||
"Microsoft.AspNet.Authentication": "1.0.0-*",
|
||||
"Microsoft.AspNet.Authorization": "1.0.0-*",
|
||||
"Microsoft.AspNet.Cors.Core": "1.0.0-*",
|
||||
"Microsoft.AspNet.DataProtection": "1.0.0-*",
|
||||
"Microsoft.AspNet.Diagnostics.Interfaces": "1.0.0-*",
|
||||
"Microsoft.AspNet.FileProviders": "1.0.0-*",
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.Mvc;
|
||||
using Microsoft.AspNet.Routing;
|
||||
using Microsoft.Framework.Internal;
|
||||
|
|
@ -87,6 +88,7 @@ namespace Microsoft.Framework.DependencyInjection
|
|||
services.AddOptions();
|
||||
services.AddDataProtection();
|
||||
services.AddRouting();
|
||||
services.AddCors();
|
||||
services.AddAuthorization();
|
||||
services.AddWebEncoders();
|
||||
services.Configure<RouteOptions>(
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
services.AddTransient<IFilterProvider, DefaultFilterProvider>();
|
||||
|
||||
services.AddTransient<FormatFilter, FormatFilter>();
|
||||
services.AddTransient<CorsAuthorizationFilter, CorsAuthorizationFilter>();
|
||||
|
||||
// Dataflow - ModelBinding, Validation and Formatting
|
||||
//
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"Microsoft.AspNet.Authorization": "1.0.0-*",
|
||||
"Microsoft.AspNet.Cors": "1.0.0-*",
|
||||
"Microsoft.AspNet.Mvc.Common": { "version": "6.0.0-*", "type": "build" },
|
||||
"Microsoft.AspNet.Mvc.Razor": "6.0.0-*",
|
||||
"Microsoft.Framework.Caching.Memory": "1.0.0-*",
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNet.Authorization;
|
||||
using Microsoft.AspNet.Cors.Core;
|
||||
using Microsoft.Framework.Internal;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
using Moq;
|
||||
|
|
@ -284,6 +285,38 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
|
|||
Assert.False(isValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildActionModel_EnableCorsAttributeAddsCorsAuthorizationFilterFactory()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new DefaultActionModelBuilder(authorizationOptions: null);
|
||||
var typeInfo = typeof(EnableCorsController).GetTypeInfo();
|
||||
var method = typeInfo.GetMethod("Action");
|
||||
|
||||
// Act
|
||||
var actions = builder.BuildActionModels(typeInfo, method);
|
||||
|
||||
// Assert
|
||||
var action = Assert.Single(actions);
|
||||
Assert.Single(action.Filters, f => f is CorsAuthorizationFilterFactory);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildActionModel_DisableCorsAttributeAddsDisableCorsAuthorizationFilter()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new DefaultActionModelBuilder(authorizationOptions: null);
|
||||
var typeInfo = typeof(DisableCorsController).GetTypeInfo();
|
||||
var method = typeInfo.GetMethod("Action");
|
||||
|
||||
// Act
|
||||
var actions = builder.BuildActionModels(typeInfo, method);
|
||||
|
||||
// Assert
|
||||
var action = Assert.Single(actions);
|
||||
Assert.True(action.Filters.Any(f => f is DisableCorsAuthorizationFilter));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetActions_ConventionallyRoutedAction_WithoutHttpConstraints()
|
||||
{
|
||||
|
|
@ -927,6 +960,22 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
|
|||
public void Invalid() { }
|
||||
}
|
||||
|
||||
private class EnableCorsController
|
||||
{
|
||||
[EnableCors("policy")]
|
||||
public void Action()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private class DisableCorsController
|
||||
{
|
||||
[DisableCors]
|
||||
public void Action()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// Here the constraints on the methods are acting as an IActionHttpMethodProvider and
|
||||
// not as an IRouteTemplateProvider given that there is no RouteAttribute
|
||||
// on the controller and the template for all the constraints on a method is null.
|
||||
|
|
|
|||
|
|
@ -6,8 +6,11 @@ using System.Linq;
|
|||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Authorization;
|
||||
using Microsoft.AspNet.Cors.Core;
|
||||
using Microsoft.AspNet.Mvc.Filters;
|
||||
using Microsoft.Framework.Internal;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ApplicationModels
|
||||
|
|
@ -47,6 +50,48 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
|
|||
Assert.True(model.Filters.Any(f => f is AuthorizeFilter));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildControllerModel_EnableCorsAttributeAddsCorsAuthorizationFilterFactory()
|
||||
{
|
||||
// Arrange
|
||||
var corsOptions = new CorsOptions();
|
||||
corsOptions.AddPolicy("policy", new CorsPolicy());
|
||||
var mockOptions = new Mock<IOptions<CorsOptions>>();
|
||||
mockOptions.SetupGet(o => o.Options)
|
||||
.Returns(corsOptions);
|
||||
var builder = new DefaultControllerModelBuilder(new DefaultActionModelBuilder(null),
|
||||
NullLoggerFactory.Instance,
|
||||
authorizationOptions: null);
|
||||
var typeInfo = typeof(CorsController).GetTypeInfo();
|
||||
|
||||
// Act
|
||||
var model = builder.BuildControllerModel(typeInfo);
|
||||
|
||||
// Assert
|
||||
Assert.Single(model.Filters, f => f is CorsAuthorizationFilterFactory);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildControllerModel_DisableCorsAttributeAddsDisableCorsAuthorizationFilter()
|
||||
{
|
||||
// Arrange
|
||||
var corsOptions = new CorsOptions();
|
||||
corsOptions.AddPolicy("policy", new CorsPolicy());
|
||||
var mockOptions = new Mock<IOptions<CorsOptions>>();
|
||||
mockOptions.SetupGet(o => o.Options)
|
||||
.Returns(corsOptions);
|
||||
var builder = new DefaultControllerModelBuilder(new DefaultActionModelBuilder(null),
|
||||
NullLoggerFactory.Instance,
|
||||
authorizationOptions: null);
|
||||
var typeInfo = typeof(DisableCorsController).GetTypeInfo();
|
||||
|
||||
// Act
|
||||
var model = builder.BuildControllerModel(typeInfo);
|
||||
|
||||
// Assert
|
||||
Assert.True(model.Filters.Any(f => f is DisableCorsAuthorizationFilter));
|
||||
}
|
||||
|
||||
// This class has a filter attribute, but doesn't implement any filter interfaces,
|
||||
// so ControllerFilter is not present.
|
||||
[Fact]
|
||||
|
|
@ -113,6 +158,16 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
|
|||
{
|
||||
}
|
||||
|
||||
[EnableCors("policy")]
|
||||
public class CorsController
|
||||
{
|
||||
}
|
||||
|
||||
[DisableCors]
|
||||
public class DisableCorsController
|
||||
{
|
||||
}
|
||||
|
||||
public class SomeFiltersController : IAsyncActionFilter, IResultFilter
|
||||
{
|
||||
public Task OnActionExecutionAsync(
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ using System.Reflection;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Core;
|
||||
using Microsoft.AspNet.Http.Core.Collections;
|
||||
using Microsoft.AspNet.Mvc.ActionConstraints;
|
||||
using Microsoft.AspNet.Mvc.ApplicationModels;
|
||||
using Microsoft.AspNet.Mvc.Core;
|
||||
|
|
@ -867,7 +868,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
var request = new Mock<HttpRequest>(MockBehavior.Strict);
|
||||
request.SetupGet(r => r.Method).Returns(httpMethod);
|
||||
request.SetupGet(r => r.Path).Returns(new PathString());
|
||||
|
||||
request.SetupGet(r => r.Headers).Returns(new HeaderDictionary());
|
||||
httpContext.SetupGet(c => c.Request).Returns(request.Object);
|
||||
httpContext.SetupGet(c => c.RequestServices).Returns(serviceContainer);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,281 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Cors.Core;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Core;
|
||||
using Microsoft.AspNet.Routing;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Moq;
|
||||
using Newtonsoft.Json.Utilities;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Test
|
||||
{
|
||||
public class CorsAuthorizationFilterTest
|
||||
{
|
||||
[Fact]
|
||||
public async Task PreFlightRequest_SuccessfulMatch_WritesHeaders()
|
||||
{
|
||||
// Arrange
|
||||
var mockEngine = GetPassingEngine(supportsCredentials:true);
|
||||
var filter = GetFilter(mockEngine);
|
||||
|
||||
var authorizationContext = GetAuthorizationContext(
|
||||
new[] { new FilterDescriptor(filter, FilterScope.Action) },
|
||||
GetRequestHeaders(true),
|
||||
isPreflight: true);
|
||||
|
||||
// Act
|
||||
await filter.OnAuthorizationAsync(authorizationContext);
|
||||
await authorizationContext.Result.ExecuteResultAsync(authorizationContext);
|
||||
|
||||
// Assert
|
||||
var response = authorizationContext.HttpContext.Response;
|
||||
Assert.Equal(200, response.StatusCode);
|
||||
Assert.Equal("http://example.com", response.Headers[CorsConstants.AccessControlAllowOrigin]);
|
||||
Assert.Equal("header1,header2", response.Headers[CorsConstants.AccessControlAllowHeaders]);
|
||||
|
||||
// Notice: GET header gets filtered because it is a simple header.
|
||||
Assert.Equal("PUT", response.Headers[CorsConstants.AccessControlAllowMethods]);
|
||||
Assert.Equal("exposed1,exposed2", response.Headers[CorsConstants.AccessControlExposeHeaders]);
|
||||
Assert.Equal("123", response.Headers[CorsConstants.AccessControlMaxAge]);
|
||||
Assert.Equal("true", response.Headers[CorsConstants.AccessControlAllowCredentials]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PreFlight_FailedMatch_Writes200()
|
||||
{
|
||||
// Arrange
|
||||
var mockEngine = GetFailingEngine();
|
||||
var filter = GetFilter(mockEngine);
|
||||
|
||||
var authorizationContext = GetAuthorizationContext(
|
||||
new[] { new FilterDescriptor(filter, FilterScope.Action) },
|
||||
GetRequestHeaders(),
|
||||
isPreflight: true);
|
||||
|
||||
// Act
|
||||
await filter.OnAuthorizationAsync(authorizationContext);
|
||||
await authorizationContext.Result.ExecuteResultAsync(authorizationContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(200, authorizationContext.HttpContext.Response.StatusCode);
|
||||
Assert.Empty(authorizationContext.HttpContext.Response.Headers);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CorsRequest_SuccessfulMatch_WritesHeaders()
|
||||
{
|
||||
// Arrange
|
||||
var mockEngine = GetPassingEngine(supportsCredentials: true);
|
||||
var filter = GetFilter(mockEngine);
|
||||
|
||||
var authorizationContext = GetAuthorizationContext(
|
||||
new[] { new FilterDescriptor(filter, FilterScope.Action) },
|
||||
GetRequestHeaders(true),
|
||||
isPreflight: true);
|
||||
|
||||
// Act
|
||||
await filter.OnAuthorizationAsync(authorizationContext);
|
||||
await authorizationContext.Result.ExecuteResultAsync(authorizationContext);
|
||||
|
||||
// Assert
|
||||
var response = authorizationContext.HttpContext.Response;
|
||||
Assert.Equal(200, response.StatusCode);
|
||||
Assert.Equal("http://example.com", response.Headers[CorsConstants.AccessControlAllowOrigin]);
|
||||
Assert.Equal("exposed1,exposed2", response.Headers[CorsConstants.AccessControlExposeHeaders]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CorsRequest_FailedMatch_Writes200()
|
||||
{
|
||||
// Arrange
|
||||
var mockEngine = GetFailingEngine();
|
||||
var filter = GetFilter(mockEngine);
|
||||
|
||||
var authorizationContext = GetAuthorizationContext(
|
||||
new[] { new FilterDescriptor(filter, FilterScope.Action) },
|
||||
GetRequestHeaders(),
|
||||
isPreflight: false);
|
||||
|
||||
// Act
|
||||
await filter.OnAuthorizationAsync(authorizationContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(200, authorizationContext.HttpContext.Response.StatusCode);
|
||||
Assert.Empty(authorizationContext.HttpContext.Response.Headers);
|
||||
}
|
||||
|
||||
private CorsAuthorizationFilter GetFilter(ICorsService corsService)
|
||||
{
|
||||
var policyProvider = new Mock<ICorsPolicyProvider>();
|
||||
policyProvider
|
||||
.Setup(o => o.GetPolicyAsync(It.IsAny<HttpContext>(), It.IsAny<string>()))
|
||||
.Returns(Task.FromResult(new CorsPolicy()));
|
||||
|
||||
return new CorsAuthorizationFilter(corsService, policyProvider.Object)
|
||||
{
|
||||
PolicyName = string.Empty
|
||||
};
|
||||
}
|
||||
|
||||
private AuthorizationContext GetAuthorizationContext(
|
||||
FilterDescriptor[] filterDescriptors,
|
||||
RequestHeaders headers = null,
|
||||
bool isPreflight = false)
|
||||
{
|
||||
|
||||
// HttpContext
|
||||
var httpContext = new DefaultHttpContext();
|
||||
if (headers != null)
|
||||
{
|
||||
httpContext.Request.Headers.Add(CorsConstants.AccessControlRequestHeaders, headers.Headers.Split(','));
|
||||
httpContext.Request.Headers.Add(CorsConstants.AccessControlRequestMethod, new[] { headers.Method });
|
||||
httpContext.Request.Headers.Add(CorsConstants.AccessControlExposeHeaders, headers.ExposedHeaders.Split(','));
|
||||
httpContext.Request.Headers.Add(CorsConstants.Origin, new[] { headers.Origin });
|
||||
}
|
||||
|
||||
var method = isPreflight ? CorsConstants.PreflightHttpMethod : "GET";
|
||||
httpContext.Request.Method = method;
|
||||
|
||||
// AuthorizationContext
|
||||
var actionContext = new ActionContext(
|
||||
httpContext: httpContext,
|
||||
routeData: new RouteData(),
|
||||
actionDescriptor: new ActionDescriptor() { FilterDescriptors = filterDescriptors });
|
||||
|
||||
var authorizationContext = new AuthorizationContext(
|
||||
actionContext,
|
||||
filterDescriptors.Select(filter => filter.Filter).ToList()
|
||||
);
|
||||
|
||||
return authorizationContext;
|
||||
}
|
||||
|
||||
private ICorsService GetFailingEngine()
|
||||
{
|
||||
var mockEngine = new Mock<ICorsService>();
|
||||
var result = GetCorsResult("http://example.com");
|
||||
|
||||
mockEngine
|
||||
.Setup(o => o.EvaluatePolicy(It.IsAny<HttpContext>(), It.IsAny<CorsPolicy>()))
|
||||
.Returns(result);
|
||||
return mockEngine.Object;
|
||||
}
|
||||
|
||||
private ICorsService GetPassingEngine(bool supportsCredentials = false)
|
||||
{
|
||||
var mockEngine = new Mock<ICorsService>();
|
||||
var result = GetCorsResult(
|
||||
"http://example.com",
|
||||
new List<string> { "header1", "header2" },
|
||||
new List<string> { "PUT" },
|
||||
new List<string> { "exposed1", "exposed2" },
|
||||
123,
|
||||
supportsCredentials);
|
||||
|
||||
mockEngine
|
||||
.Setup(o => o.EvaluatePolicy(It.IsAny<HttpContext>(), It.IsAny<CorsPolicy>()))
|
||||
.Returns(result);
|
||||
|
||||
mockEngine
|
||||
.Setup(o => o.ApplyResult(It.IsAny<CorsResult>(), It.IsAny<HttpResponse>()))
|
||||
.Callback<CorsResult, HttpResponse>((result1, response1) =>
|
||||
{
|
||||
var headers = response1.Headers;
|
||||
headers.Set(
|
||||
CorsConstants.AccessControlMaxAge,
|
||||
result1.PreflightMaxAge.Value.TotalSeconds.ToString());
|
||||
headers.Add(CorsConstants.AccessControlAllowOrigin, new[] { result1.AllowedOrigin });
|
||||
if (result1.SupportsCredentials)
|
||||
{
|
||||
headers.Add(CorsConstants.AccessControlAllowCredentials, new[] { "true" });
|
||||
}
|
||||
|
||||
headers.Add(CorsConstants.AccessControlAllowHeaders, result1.AllowedHeaders.ToArray());
|
||||
headers.Add(CorsConstants.AccessControlAllowMethods, result1.AllowedMethods.ToArray());
|
||||
headers.Add(CorsConstants.AccessControlExposeHeaders, result1.AllowedExposedHeaders.ToArray());
|
||||
});
|
||||
|
||||
return mockEngine.Object;
|
||||
}
|
||||
|
||||
private RequestHeaders GetRequestHeaders(bool supportsCredentials = false)
|
||||
{
|
||||
return new RequestHeaders
|
||||
{
|
||||
Origin = "http://example.com",
|
||||
Headers = "header1,header2",
|
||||
Method = "GET",
|
||||
ExposedHeaders = "exposed1,exposed2",
|
||||
};
|
||||
}
|
||||
|
||||
private CorsResult GetCorsResult(
|
||||
string origin = null,
|
||||
IList<string> headers = null,
|
||||
IList<string> methods = null,
|
||||
IList<string> exposedHeaders = null,
|
||||
long? preFlightMaxAge = null,
|
||||
bool? supportsCredentials = null)
|
||||
{
|
||||
var result = new CorsResult();
|
||||
|
||||
if (origin != null)
|
||||
{
|
||||
result.AllowedOrigin = origin;
|
||||
}
|
||||
|
||||
if (headers != null)
|
||||
{
|
||||
AddRange(result.AllowedHeaders, headers);
|
||||
}
|
||||
|
||||
if (methods != null)
|
||||
{
|
||||
AddRange(result.AllowedMethods, methods);
|
||||
}
|
||||
|
||||
if (exposedHeaders != null)
|
||||
{
|
||||
AddRange(result.AllowedExposedHeaders, exposedHeaders);
|
||||
}
|
||||
|
||||
if (preFlightMaxAge != null)
|
||||
{
|
||||
result.PreflightMaxAge = TimeSpan.FromSeconds(preFlightMaxAge.Value);
|
||||
}
|
||||
|
||||
if (supportsCredentials != null)
|
||||
{
|
||||
result.SupportsCredentials = supportsCredentials.Value;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void AddRange(IList<string> target, IList<string> source)
|
||||
{
|
||||
foreach (var item in source)
|
||||
{
|
||||
target.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
private class RequestHeaders
|
||||
{
|
||||
public string Origin { get; set; }
|
||||
|
||||
public string Headers { get; set; }
|
||||
|
||||
public string ExposedHeaders { get; set; }
|
||||
|
||||
public string Method { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
// 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.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.Cors.Core;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||
{
|
||||
public class CorsMiddlewareTests
|
||||
{
|
||||
private const string SiteName = nameof(CorsMiddlewareWebSite);
|
||||
private readonly Action<IApplicationBuilder> _app = new CorsMiddlewareWebSite.Startup().Configure;
|
||||
|
||||
[Theory]
|
||||
[InlineData("GET")]
|
||||
[InlineData("HEAD")]
|
||||
[InlineData("POST")]
|
||||
public async Task ResourceWithSimpleRequestPolicy_Allows_SimpleRequests(string method)
|
||||
{
|
||||
// Arrange
|
||||
var server = TestHelper.CreateServer(_app, SiteName);
|
||||
var client = server.CreateClient();
|
||||
var origin = "http://example.com";
|
||||
|
||||
var requestBuilder = server
|
||||
.CreateRequest("http://localhost/CorsMiddleware/GetExclusiveContent")
|
||||
.AddHeader(CorsConstants.Origin, origin);
|
||||
|
||||
// Act
|
||||
var response = await requestBuilder.SendAsync(method);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
Assert.Equal("exclusive", content);
|
||||
var responseHeaders = response.Headers;
|
||||
var header = Assert.Single(response.Headers);
|
||||
Assert.Equal(CorsConstants.AccessControlAllowOrigin, header.Key);
|
||||
Assert.Equal(new[] { "http://example.com" }, header.Value.ToArray());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("GET")]
|
||||
[InlineData("HEAD")]
|
||||
[InlineData("POST")]
|
||||
[InlineData("PUT")]
|
||||
public async Task PolicyFailed_Disallows_PreFlightRequest(string method)
|
||||
{
|
||||
// Arrange
|
||||
var server = TestHelper.CreateServer(_app, SiteName);
|
||||
var client = server.CreateClient();
|
||||
|
||||
// Adding a custom header makes it a non simple request.
|
||||
var requestBuilder = server
|
||||
.CreateRequest("http://localhost/CorsMiddleware/GetExclusiveContent")
|
||||
.AddHeader(CorsConstants.Origin, "http://example.com")
|
||||
.AddHeader(CorsConstants.AccessControlRequestMethod, method)
|
||||
.AddHeader(CorsConstants.AccessControlRequestHeaders, "Custom");
|
||||
|
||||
// Act
|
||||
var response = await requestBuilder.SendAsync(CorsConstants.PreflightHttpMethod);
|
||||
|
||||
// Assert
|
||||
// Middleware applied the policy and since that did not pass, there were no access control headers.
|
||||
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
|
||||
Assert.Empty(response.Headers);
|
||||
|
||||
// It should short circuit and hence no result.
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
Assert.Equal(string.Empty, content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PolicyFailed_Allows_ActualRequest_WithMissingResponseHeaders()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestHelper.CreateServer(_app, SiteName);
|
||||
var client = server.CreateClient();
|
||||
|
||||
// Adding a custom header makes it a non simple request.
|
||||
var requestBuilder = server
|
||||
.CreateRequest("http://localhost/CorsMiddleware/GetExclusiveContent")
|
||||
.AddHeader(CorsConstants.Origin, "http://example2.com");
|
||||
|
||||
// Act
|
||||
var response = await requestBuilder.SendAsync("PUT");
|
||||
|
||||
// Assert
|
||||
// Middleware applied the policy and since that did not pass, there were no access control headers.
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Empty(response.Headers);
|
||||
|
||||
// It still has executed the action.
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
Assert.Equal("exclusive", content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,231 @@
|
|||
// 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.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.Cors.Core;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||
{
|
||||
public class CorsTests
|
||||
{
|
||||
private const string SiteName = nameof(CorsWebSite);
|
||||
private readonly Action<IApplicationBuilder> _app = new CorsWebSite.Startup().Configure;
|
||||
|
||||
[Theory]
|
||||
[InlineData("GET")]
|
||||
[InlineData("HEAD")]
|
||||
[InlineData("POST")]
|
||||
public async Task ResourceWithSimpleRequestPolicy_Allows_SimpleRequests(string method)
|
||||
{
|
||||
// Arrange
|
||||
var server = TestHelper.CreateServer(_app, SiteName);
|
||||
var client = server.CreateClient();
|
||||
var origin = "http://example.com";
|
||||
|
||||
var requestBuilder = server
|
||||
.CreateRequest("http://localhost/Cors/GetBlogComments")
|
||||
.AddHeader(CorsConstants.Origin, origin);
|
||||
|
||||
// Act
|
||||
var response = await requestBuilder.SendAsync(method);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
Assert.Equal("[\"comment1\",\"comment2\",\"comment3\"]", content);
|
||||
var responseHeaders = response.Headers;
|
||||
var header = Assert.Single(response.Headers);
|
||||
Assert.Equal(CorsConstants.AccessControlAllowOrigin, header.Key);
|
||||
Assert.Equal(new[] { "*" }, header.Value.ToArray());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("GET")]
|
||||
[InlineData("HEAD")]
|
||||
[InlineData("POST")]
|
||||
[InlineData("PUT")]
|
||||
public async Task PolicyFailed_Disallows_PreFlightRequest(string method)
|
||||
{
|
||||
// Arrange
|
||||
var server = TestHelper.CreateServer(_app, SiteName);
|
||||
var client = server.CreateClient();
|
||||
|
||||
// Adding a custom header makes it a non simple request.
|
||||
var requestBuilder = server
|
||||
.CreateRequest("http://localhost/Cors/GetBlogComments")
|
||||
.AddHeader(CorsConstants.Origin, "http://example.com")
|
||||
.AddHeader(CorsConstants.AccessControlRequestMethod, method)
|
||||
.AddHeader(CorsConstants.AccessControlRequestHeaders, "Custom");
|
||||
|
||||
// Act
|
||||
var response = await requestBuilder.SendAsync(CorsConstants.PreflightHttpMethod);
|
||||
|
||||
// Assert
|
||||
// MVC applied the policy and since that did not pass, there were no access control headers.
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Empty(response.Headers);
|
||||
|
||||
// It should short circuit and hence no result.
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
Assert.Equal(string.Empty, content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SuccessfulCorsRequest_AllowsCredentials_IfThePolicyAllowsCredentials()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestHelper.CreateServer(_app, SiteName);
|
||||
var client = server.CreateClient();
|
||||
|
||||
// Adding a custom header makes it a non simple request.
|
||||
var requestBuilder = server
|
||||
.CreateRequest("http://localhost/Cors/EditUserComment?userComment=abcd")
|
||||
.AddHeader(CorsConstants.Origin, "http://example.com")
|
||||
.AddHeader(CorsConstants.AccessControlExposeHeaders, "exposed1,exposed2");
|
||||
|
||||
// Act
|
||||
var response = await requestBuilder.SendAsync("PUT");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var responseHeaders = response.Headers;
|
||||
Assert.Equal(
|
||||
new[] { "http://example.com" },
|
||||
responseHeaders.GetValues(CorsConstants.AccessControlAllowOrigin).ToArray());
|
||||
Assert.Equal(
|
||||
new[] { "true" },
|
||||
responseHeaders.GetValues(CorsConstants.AccessControlAllowCredentials).ToArray());
|
||||
Assert.Equal(
|
||||
new[] { "exposed1", "exposed2" },
|
||||
responseHeaders.GetValues(CorsConstants.AccessControlExposeHeaders).ToArray());
|
||||
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
Assert.Equal("abcd", content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SuccessfulPreflightRequest_AllowsCredentials_IfThePolicyAllowsCredentials()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestHelper.CreateServer(_app, SiteName);
|
||||
var client = server.CreateClient();
|
||||
|
||||
// Adding a custom header makes it a non simple request.
|
||||
var requestBuilder = server
|
||||
.CreateRequest("http://localhost/Cors/EditUserComment?userComment=abcd")
|
||||
.AddHeader(CorsConstants.Origin, "http://example.com")
|
||||
.AddHeader(CorsConstants.AccessControlRequestMethod, "PUT")
|
||||
.AddHeader(CorsConstants.AccessControlRequestHeaders, "header1,header2");
|
||||
|
||||
// Act
|
||||
var response = await requestBuilder.SendAsync(CorsConstants.PreflightHttpMethod);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var responseHeaders = response.Headers;
|
||||
Assert.Equal(
|
||||
new[] { "http://example.com" },
|
||||
responseHeaders.GetValues(CorsConstants.AccessControlAllowOrigin).ToArray());
|
||||
Assert.Equal(
|
||||
new[] { "true" },
|
||||
responseHeaders.GetValues(CorsConstants.AccessControlAllowCredentials).ToArray());
|
||||
Assert.Equal(
|
||||
new[] { "header1", "header2" },
|
||||
responseHeaders.GetValues(CorsConstants.AccessControlAllowHeaders).ToArray());
|
||||
Assert.Equal(
|
||||
new[] { "PUT" },
|
||||
responseHeaders.GetValues(CorsConstants.AccessControlAllowMethods).ToArray());
|
||||
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
Assert.Empty(content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PolicyFailed_Allows_ActualRequest_WithMissingResponseHeaders()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestHelper.CreateServer(_app, SiteName);
|
||||
var client = server.CreateClient();
|
||||
|
||||
// Adding a custom header makes it a non simple request.
|
||||
var requestBuilder = server
|
||||
.CreateRequest("http://localhost/Cors/GetUserComments")
|
||||
.AddHeader(CorsConstants.Origin, "http://example2.com");
|
||||
|
||||
// Act
|
||||
var response = await requestBuilder.SendAsync("PUT");
|
||||
|
||||
// Assert
|
||||
// MVC applied the policy and since that did not pass, there were no access control headers.
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Empty(response.Headers);
|
||||
|
||||
// It still have executed the action.
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
Assert.Equal("[\"usercomment1\",\"usercomment2\",\"usercomment3\"]", content);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("GET")]
|
||||
[InlineData("HEAD")]
|
||||
[InlineData("POST")]
|
||||
public async Task DisableCors_ActionsCanOverride_ControllerLevel(string method)
|
||||
{
|
||||
// Arrange
|
||||
var server = TestHelper.CreateServer(_app, SiteName);
|
||||
var client = server.CreateClient();
|
||||
|
||||
// Exclusive content is not available on other sites.
|
||||
var requestBuilder = server
|
||||
.CreateRequest("http://localhost/Cors/GetExclusiveContent")
|
||||
.AddHeader(CorsConstants.Origin, "http://example.com");
|
||||
|
||||
// Act
|
||||
var response = await requestBuilder.SendAsync(method);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
// Since there are no response headers, the client should step in to block the content.
|
||||
Assert.Empty(response.Headers);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
Assert.Equal("exclusive", content);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("GET")]
|
||||
[InlineData("HEAD")]
|
||||
[InlineData("POST")]
|
||||
public async Task DisableCors_PreFlight_ActionsCanOverride_ControllerLevel(string method)
|
||||
{
|
||||
// Arrange
|
||||
var server = TestHelper.CreateServer(_app, SiteName);
|
||||
var client = server.CreateClient();
|
||||
|
||||
// Exclusive content is not available on other sites.
|
||||
var requestBuilder = server
|
||||
.CreateRequest("http://localhost/Cors/GetExclusiveContent")
|
||||
.AddHeader(CorsConstants.Origin, "http://example.com")
|
||||
.AddHeader(CorsConstants.AccessControlRequestMethod, method)
|
||||
.AddHeader(CorsConstants.AccessControlRequestHeaders, "Custom");
|
||||
|
||||
// Act
|
||||
var response = await requestBuilder.SendAsync(CorsConstants.PreflightHttpMethod);
|
||||
|
||||
// Assert
|
||||
// Since there are no response headers, the client should step in to block the content.
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Empty(response.Headers);
|
||||
|
||||
// Nothing gets executed for a pre-flight request.
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
Assert.Empty(content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,6 +20,8 @@
|
|||
"ContentNegotiationWebSite": "1.0.0",
|
||||
"ControllerDiscoveryConventionsWebSite": "1.0.0",
|
||||
"ControllersFromServicesWebSite": "1.0.0",
|
||||
"CorsWebSite": "1.0.0",
|
||||
"CorsMiddlewareWebSite": "1.0.0",
|
||||
"CustomRouteWebSite": "1.0.0",
|
||||
"ErrorPageMiddlewareWebSite": "1.0.0",
|
||||
"FilesWebSite": "1.0.0",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
// 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.Mvc;
|
||||
|
||||
namespace CorsMiddlewareWebSite
|
||||
{
|
||||
[Route("CorsMiddleWare/[action]")]
|
||||
public class BlogController : Controller
|
||||
{
|
||||
public string GetExclusiveContent()
|
||||
{
|
||||
return "exclusive";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>94ba134d-04b3-48aa-ba55-5a4db8640f2d</ProjectGuid>
|
||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
|
||||
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<DevelopmentServerPort>41642</DevelopmentServerPort>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
// 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.Builder;
|
||||
using Microsoft.AspNet.Cors;
|
||||
using Microsoft.AspNet.Mvc;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
|
||||
namespace CorsMiddlewareWebSite
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
var configuration = app.GetTestConfiguration();
|
||||
|
||||
app.UseServices(services =>
|
||||
{
|
||||
services.AddMvc();
|
||||
});
|
||||
|
||||
app.UseCors(policy => policy.WithOrigins("http://example.com"));
|
||||
app.UseMvc();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"commands": {
|
||||
"web": "Microsoft.AspNet.Hosting server=Microsoft.AspNet.Server.WebListener server.urls=http://localhost:5001",
|
||||
"kestrel": "Microsoft.AspNet.Hosting --server Kestrel --server.urls http://localhost:5000"
|
||||
},
|
||||
"dependencies": {
|
||||
"Kestrel": "1.0.0-*",
|
||||
"Microsoft.AspNet.Cors": "1.0.0-*",
|
||||
"Microsoft.AspNet.Cors.Core": "1.0.0-*",
|
||||
"Microsoft.AspNet.Mvc": "6.0.0-*",
|
||||
"Microsoft.AspNet.Mvc.Xml": "6.0.0-*",
|
||||
"Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0",
|
||||
"Microsoft.AspNet.Server.IIS": "1.0.0-*",
|
||||
"Microsoft.AspNet.Server.WebListener": "1.0.0-*",
|
||||
"Microsoft.AspNet.StaticFiles": "1.0.0-*"
|
||||
},
|
||||
"frameworks": {
|
||||
"dnx451": { },
|
||||
"dnx50": { }
|
||||
},
|
||||
"webroot": "wwwroot"
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
CorsMiddlewareWebSite
|
||||
===
|
||||
|
||||
This web site illustrates how to use CorsMiddleware to apply a policy for entire application.
|
||||
|
|
@ -0,0 +1 @@
|
|||
HelloWorld
|
||||
|
|
@ -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.Collections.Generic;
|
||||
using Microsoft.AspNet.Cors.Core;
|
||||
using Microsoft.AspNet.Mvc;
|
||||
|
||||
namespace CorsWebSite
|
||||
{
|
||||
[Route("Cors/[action]")]
|
||||
[EnableCors("AllowAnySimpleRequest")]
|
||||
public class BlogController : Controller
|
||||
{
|
||||
public IEnumerable<string> GetBlogComments(int id)
|
||||
{
|
||||
return new[] { "comment1", "comment2", "comment3" };
|
||||
}
|
||||
|
||||
[EnableCors("AllowSpecificOrigin")]
|
||||
public IEnumerable<string> GetUserComments(int id)
|
||||
{
|
||||
return new[] { "usercomment1", "usercomment2", "usercomment3" };
|
||||
}
|
||||
|
||||
[DisableCors]
|
||||
[AcceptVerbs("HEAD", "GET", "POST")]
|
||||
public string GetExclusiveContent()
|
||||
{
|
||||
return "exclusive";
|
||||
}
|
||||
|
||||
[EnableCors("WithCredentialsAnyOrigin")]
|
||||
public string EditUserComment(int id, string userComment)
|
||||
{
|
||||
return userComment;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>94ba134d-04b3-48aa-ba55-5a4db8640f2d</ProjectGuid>
|
||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
|
||||
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<DevelopmentServerPort>41642</DevelopmentServerPort>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
// 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.Builder;
|
||||
using Microsoft.AspNet.Mvc;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
|
||||
namespace CorsWebSite
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
var configuration = app.GetTestConfiguration();
|
||||
|
||||
app.UseServices(services =>
|
||||
{
|
||||
services.AddMvc();
|
||||
services.ConfigureCors(options =>
|
||||
{
|
||||
options.AddPolicy(
|
||||
"AllowAnySimpleRequest",
|
||||
builder =>
|
||||
{
|
||||
builder.AllowAnyOrigin()
|
||||
.WithMethods("GET", "POST", "HEAD");
|
||||
});
|
||||
|
||||
options.AddPolicy(
|
||||
"AllowSpecificOrigin",
|
||||
builder =>
|
||||
{
|
||||
builder.WithOrigins("http://example.com");
|
||||
});
|
||||
|
||||
options.AddPolicy(
|
||||
"WithCredentials",
|
||||
builder =>
|
||||
{
|
||||
builder.AllowCredentials()
|
||||
.WithOrigins("http://example.com");
|
||||
});
|
||||
|
||||
options.AddPolicy(
|
||||
"WithCredentialsAnyOrigin",
|
||||
builder =>
|
||||
{
|
||||
builder.AllowCredentials()
|
||||
.AllowAnyOrigin()
|
||||
.AllowAnyHeader()
|
||||
.WithMethods("PUT", "POST")
|
||||
.WithExposedHeaders("exposed1", "exposed2");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
app.UseMvc();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"commands": {
|
||||
"web": "Microsoft.AspNet.Hosting server=Microsoft.AspNet.Server.WebListener server.urls=http://localhost:5001",
|
||||
"kestrel": "Microsoft.AspNet.Hosting --server Kestrel --server.urls http://localhost:5000"
|
||||
},
|
||||
"dependencies": {
|
||||
"Kestrel": "1.0.0-*",
|
||||
"Microsoft.AspNet.Cors": "1.0.0-*",
|
||||
"Microsoft.AspNet.Cors.Core": "1.0.0-*",
|
||||
"Microsoft.AspNet.Mvc": "6.0.0-*",
|
||||
"Microsoft.AspNet.Mvc.Xml": "6.0.0-*",
|
||||
"Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0",
|
||||
"Microsoft.AspNet.Server.IIS": "1.0.0-*",
|
||||
"Microsoft.AspNet.Server.WebListener": "1.0.0-*",
|
||||
"Microsoft.AspNet.StaticFiles": "1.0.0-*"
|
||||
},
|
||||
"frameworks": {
|
||||
"dnx451": { },
|
||||
"dnx50": { }
|
||||
},
|
||||
"webroot": "wwwroot"
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
CorsWebSite
|
||||
===
|
||||
|
||||
This web site illustrates how to configure actions to allow/disallow cross origin requests.
|
||||
|
|
@ -0,0 +1 @@
|
|||
HelloWorld
|
||||
Loading…
Reference in New Issue