Adding Base interfaces for Cors.Core

This commit is contained in:
Harsh Gupta 2015-02-24 17:08:37 -08:00
parent dd870750ec
commit 8e399cd83d
23 changed files with 2513 additions and 6 deletions

View File

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.22604.0
VisualStudioVersion = 14.0.22529.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{84FE6872-A610-4CEC-855F-A84CBF1F40FC}"
EndProject
@ -12,6 +12,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Cors.Core", "src\Microsoft.AspNet.Cors.Core\Microsoft.AspNet.Cors.Core.kproj", "{C573AEE1-8D54-4A83-8D6B-61C85E8F713E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{F32074C7-087C-46CC-A913-422BFD2D6E0A}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Cors.Core.Test", "test\Microsoft.AspNet.Cors.Core.Test\Microsoft.AspNet.Cors.Core.Test.kproj", "{B4F83A06-EB8E-4186-84C4-C6DAF7EB03D4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -22,11 +26,16 @@ Global
{C573AEE1-8D54-4A83-8D6B-61C85E8F713E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C573AEE1-8D54-4A83-8D6B-61C85E8F713E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C573AEE1-8D54-4A83-8D6B-61C85E8F713E}.Release|Any CPU.Build.0 = Release|Any CPU
{B4F83A06-EB8E-4186-84C4-C6DAF7EB03D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B4F83A06-EB8E-4186-84C4-C6DAF7EB03D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B4F83A06-EB8E-4186-84C4-C6DAF7EB03D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B4F83A06-EB8E-4186-84C4-C6DAF7EB03D4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{C573AEE1-8D54-4A83-8D6B-61C85E8F713E} = {84FE6872-A610-4CEC-855F-A84CBF1F40FC}
{B4F83A06-EB8E-4186-84C4-C6DAF7EB03D4} = {F32074C7-087C-46CC-A913-422BFD2D6E0A}
EndGlobalSection
EndGlobal

View File

@ -1,3 +1,3 @@
{
"sources": ["src"]
"sources": ["src", "test"]
}

View File

@ -0,0 +1,91 @@
// 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.Cors.Core
{
/// <summary>
/// CORS-related constants.
/// </summary>
public static class CorsConstants
{
/// <summary>
/// The HTTP method for the CORS preflight request.
/// </summary>
public static readonly string PreflightHttpMethod = "OPTIONS";
/// <summary>
/// The Origin request header.
/// </summary>
public static readonly string Origin = "Origin";
/// <summary>
/// The value for the Access-Control-Allow-Origin response header to allow all origins.
/// </summary>
public static readonly string AnyOrigin = "*";
/// <summary>
/// The Access-Control-Request-Method request header.
/// </summary>
public static readonly string AccessControlRequestMethod = "Access-Control-Request-Method";
/// <summary>
/// The Access-Control-Request-Headers request header.
/// </summary>
public static readonly string AccessControlRequestHeaders = "Access-Control-Request-Headers";
/// <summary>
/// The Access-Control-Allow-Origin response header.
/// </summary>
public static readonly string AccessControlAllowOrigin = "Access-Control-Allow-Origin";
/// <summary>
/// The Access-Control-Allow-Headers response header.
/// </summary>
public static readonly string AccessControlAllowHeaders = "Access-Control-Allow-Headers";
/// <summary>
/// The Access-Control-Expose-Headers response header.
/// </summary>
public static readonly string AccessControlExposeHeaders = "Access-Control-Expose-Headers";
/// <summary>
/// The Access-Control-Allow-Methods response header.
/// </summary>
public static readonly string AccessControlAllowMethods = "Access-Control-Allow-Methods";
/// <summary>
/// The Access-Control-Allow-Credentials response header.
/// </summary>
public static readonly string AccessControlAllowCredentials = "Access-Control-Allow-Credentials";
/// <summary>
/// The Access-Control-Max-Age response header.
/// </summary>
public static readonly string AccessControlMaxAge = "Access-Control-Max-Age";
internal static readonly string[] SimpleRequestHeaders =
{
"Origin",
"Accept",
"Accept-Language",
"Content-Language",
};
internal static readonly string[] SimpleResponseHeaders =
{
"Cache-Control",
"Content-Language",
"Content-Type",
"Expires",
"Last-Modified",
"Pragma"
};
internal static readonly string[] SimpleMethods =
{
"GET",
"HEAD",
"POST"
};
}
}

View File

@ -0,0 +1,50 @@
// 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 Microsoft.AspNet.Cors.Core;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Cors.Core
{
/// <summary>
/// Provides programmatic configuration for Cors.
/// </summary>
public class CorsOptions
{
private IDictionary<string, CorsPolicy> PolicyMap { get; } = new Dictionary<string, CorsPolicy>();
/// <summary>
/// Adds a new policy.
/// </summary>
/// <param name="name">The name of the policy.</param>
/// <param name="policy">The <see cref="CorsPolicy"/> policy to be added.</param>
public void AddPolicy([NotNull] string name, [NotNull] CorsPolicy policy)
{
PolicyMap[name] = policy;
}
/// <summary>
/// Adds a new policy.
/// </summary>
/// <param name="name">The name of the policy.</param>
/// <param name="configurePolicy">A delegate which can use a policy builder to build a policy.</param>
public void AddPolicy([NotNull] string name, [NotNull] Action<CorsPolicyBuilder> configurePolicy)
{
var policyBuilder = new CorsPolicyBuilder();
configurePolicy(policyBuilder);
PolicyMap[name] = policyBuilder.Build();
}
/// <summary>
/// Gets the policy based on the <paramref name="name"/>
/// </summary>
/// <param name="name">The name of the policy to lookup.</param>
/// <returns>The <see cref="CorsPolicy"/> if the policy was added.<c>null</c> otherwise.</returns>
public CorsPolicy GetPolicy([NotNull] string name)
{
return PolicyMap.ContainsKey(name) ? PolicyMap[name] : null;
}
}
}

View File

@ -0,0 +1,146 @@
// 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.Globalization;
using System.Text;
namespace Microsoft.AspNet.Cors.Core
{
/// <summary>
/// Defines the policy for Cross-Origin requests based on the CORS specifications.
/// </summary>
public class CorsPolicy
{
private TimeSpan? _preflightMaxAge;
/// <summary>
/// Gets a value indicating if all headers are allowed.
/// </summary>
public bool AllowAnyHeader
{
get
{
if (Headers == null || Headers.Count != 1 || Headers.Count == 1 && Headers[0] != "*")
{
return false;
}
return true;
}
}
/// <summary>
/// Gets a value indicating if all methods are allowed.
/// </summary>
public bool AllowAnyMethod
{
get
{
if (Methods == null || Methods.Count != 1 || Methods.Count == 1 && Methods[0] != "*")
{
return false;
}
return true;
}
}
/// <summary>
/// Gets a value indicating if all origins are allowed.
/// </summary>
public bool AllowAnyOrigin
{
get
{
if (Origins == null || Origins.Count != 1 || Origins.Count == 1 && Origins[0] != "*")
{
return false;
}
return true;
}
}
/// <summary>
/// Gets the headers that the resource might use and can be exposed.
/// </summary>
public IList<string> ExposedHeaders { get; } = new List<string>();
/// <summary>
/// Gets the headers that are supported by the resource.
/// </summary>
public IList<string> Headers { get; } = new List<string>();
/// <summary>
/// Gets the methods that are supported by the resource.
/// </summary>
public IList<string> Methods { get; } = new List<string>();
/// <summary>
/// Gets the origins that are allowed to access the resource.
/// </summary>
public IList<string> Origins { get; } = new List<string>();
/// <summary>
/// Gets or sets the <see cref="TimeSpan"/> for which the results of a preflight request can be cached.
/// </summary>
public TimeSpan? PreflightMaxAge
{
get
{
return _preflightMaxAge;
}
set
{
if (value < TimeSpan.Zero)
{
throw new ArgumentOutOfRangeException("value", Resources.PreflightMaxAgeOutOfRange);
}
_preflightMaxAge = value;
}
}
/// <summary>
/// Gets or sets a value indicating whether the resource supports user credentials in the request.
/// </summary>
public bool SupportsCredentials { get; set; }
/// <summary>
/// Returns a <see cref="System.String" /> that represents this instance.
/// </summary>
/// <returns>
/// A <see cref="System.String" /> that represents this instance.
/// </returns>
public override string ToString()
{
var builder = new StringBuilder();
builder.Append("AllowAnyHeader: ");
builder.Append(AllowAnyHeader);
builder.Append(", AllowAnyMethod: ");
builder.Append(AllowAnyMethod);
builder.Append(", AllowAnyOrigin: ");
builder.Append(AllowAnyOrigin);
builder.Append(", PreflightMaxAge: ");
builder.Append(PreflightMaxAge.HasValue ?
PreflightMaxAge.Value.TotalSeconds.ToString() : "null");
builder.Append(", SupportsCredentials: ");
builder.Append(SupportsCredentials);
builder.Append(", Origins: {");
builder.Append(string.Join(",", Origins));
builder.Append("}");
builder.Append(", Methods: {");
builder.Append(string.Join(",", Methods));
builder.Append("}");
builder.Append(", Headers: {");
builder.Append(string.Join(",", Headers));
builder.Append("}");
builder.Append(", ExposedHeaders: {");
builder.Append(string.Join(",", ExposedHeaders));
builder.Append("}");
return builder.ToString();
}
}
}

View File

@ -0,0 +1,194 @@
// 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 Microsoft.AspNet.Cors.Core;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Cors
{
/// <summary>
/// Exposes methods to build a policy.
/// </summary>
public class CorsPolicyBuilder
{
private readonly CorsPolicy _policy = new CorsPolicy();
/// <summary>
/// Creates a new instance of the <see cref="CorsPolicyBuilder"/>.
/// </summary>
/// <param name="origins">list of origins which can be added.</param>
public CorsPolicyBuilder(params string[] origins)
{
AddOrigins(origins);
}
/// <summary>
/// Creates a new instance of the <see cref="CorsPolicyBuilder"/>.
/// </summary>
/// <param name="policy">The policy which will be used to intialize the builder.</param>
public CorsPolicyBuilder(CorsPolicy policy)
{
Combine(policy);
}
/// <summary>
/// Adds the specified <paramref name="origins"/> to the policy.
/// </summary>
/// <param name="origins">The origins that are allowed.</param>
/// <returns>The current policy builder</returns>
public CorsPolicyBuilder AddOrigins(params string[] origins)
{
foreach (var req in origins)
{
_policy.Origins.Add(req);
}
return this;
}
/// <summary>
/// Adds the specified <paramref name="headers"/> to the policy.
/// </summary>
/// <param name="headers">The headers which need to be allowed in the request.</param>
/// <returns>The current policy builder</returns>
public CorsPolicyBuilder AddHeaders(params string[] headers)
{
foreach (var req in headers)
{
_policy.Headers.Add(req);
}
return this;
}
/// <summary>
/// Adds the specified <paramref name="exposedHeaders"/> to the policy.
/// </summary>
/// <param name="exposedHeaders">The headers which need to be exposed to the client.</param>
/// <returns>The current policy builder</returns>
public CorsPolicyBuilder AddExposedHeaders(params string[] exposedHeaders)
{
foreach (var req in exposedHeaders)
{
_policy.ExposedHeaders.Add(req);
}
return this;
}
/// <summary>
/// Adds the specified <paramref name="methods"/> to the policy.
/// </summary>
/// <param name="methods">The methods which need to be added to the policy.</param>
/// <returns>The current policy builder</returns>
public CorsPolicyBuilder AddMethods(params string[] methods)
{
foreach (var req in methods)
{
_policy.Methods.Add(req);
}
return this;
}
/// <summary>
/// Sets the policy to allow credentials.
/// </summary>
/// <returns>The current policy builder</returns>
public CorsPolicyBuilder AllowCredentials()
{
_policy.SupportsCredentials = true;
return this;
}
/// <summary>
/// Sets the policy to not allow credentials.
/// </summary>
/// <returns>The current policy builder</returns>
public CorsPolicyBuilder DisallowCredentials()
{
_policy.SupportsCredentials = false;
return this;
}
/// <summary>
/// Ensures that the policy allows any origin.
/// </summary>
/// <returns>The current policy builder</returns>
public CorsPolicyBuilder AllowAnyOrigin()
{
_policy.Origins.Clear();
_policy.Origins.Add(CorsConstants.AnyOrigin);
return this;
}
/// <summary>
/// Ensures that the policy allows any method.
/// </summary>
/// <returns>The current policy builder</returns>
public CorsPolicyBuilder AllowAnyMethod()
{
_policy.Methods.Clear();
_policy.Methods.Add("*");
return this;
}
/// <summary>
/// Ensures that the policy allows any header.
/// </summary>
/// <returns>The current policy builder</returns>
public CorsPolicyBuilder AllowAnyHeader()
{
_policy.Headers.Clear();
_policy.Headers.Add("*");
return this;
}
/// <summary>
/// Sets the preflightMaxAge for the underlying policy.
/// </summary>
/// <param name="preflightMaxAge">A positive <see cref="TimeSpan"/> indicating the time a preflight
/// request can be cached.</param>
/// <returns></returns>
public CorsPolicyBuilder SetPreflightMaxAge(TimeSpan preflightMaxAge)
{
_policy.PreflightMaxAge = preflightMaxAge;
return this;
}
/// <summary>
/// Builds a new <see cref="CorsPolicy"/> using the entries added.
/// </summary>
/// <returns>The constructed <see cref="CorsPolicy"/>.</returns>
public CorsPolicy Build()
{
return _policy;
}
/// <summary>
/// Combines the given <paramref name="policy"/> to the existing properties in the builder.
/// </summary>
/// <param name="policy">The policy which needs to be combined.</param>
/// <returns>The current policy builder</returns>
private CorsPolicyBuilder Combine([NotNull] CorsPolicy policy)
{
AddOrigins(policy.Origins.ToArray());
AddHeaders(policy.Headers.ToArray());
AddExposedHeaders(policy.ExposedHeaders.ToArray());
AddMethods(policy.Methods.ToArray());
SetPreflightMaxAge(policy.PreflightMaxAge.Value);
if (policy.SupportsCredentials)
{
AllowCredentials();
}
else
{
DisallowCredentials();
}
return this;
}
}
}

View File

@ -0,0 +1,96 @@
// 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.Globalization;
using System.Linq;
using System.Text;
namespace Microsoft.AspNet.Cors.Core
{
/// <summary>
/// Results returned by <see cref="ICorsService"/>.
/// </summary>
public class CorsResult
{
private TimeSpan? _preflightMaxAge;
/// <summary>
/// Gets or sets the allowed origin.
/// </summary>
public string AllowedOrigin { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the resource supports user credentials.
/// </summary>
public bool SupportsCredentials { get; set; }
/// <summary>
/// Gets the allowed methods.
/// </summary>
public IList<string> AllowedMethods { get; } = new List<string>();
/// <summary>
/// Gets the allowed headers.
/// </summary>
public IList<string> AllowedHeaders { get; } = new List<string>();
/// <summary>
/// Gets the allowed headers that can be exposed on the response.
/// </summary>
public IList<string> AllowedExposedHeaders { get; } = new List<string>();
/// <summary>
/// Gets or sets a value indicating if a 'Vary' header with the value 'Origin' is required.
/// </summary>
public bool VaryByOrigin { get; set; }
/// <summary>
/// Gets or sets the <see cref="TimeSpan"/> for which the results of a preflight request can be cached.
/// </summary>
public TimeSpan? PreflightMaxAge
{
get
{
return _preflightMaxAge;
}
set
{
if (value < TimeSpan.Zero)
{
throw new ArgumentOutOfRangeException("value", Resources.PreflightMaxAgeOutOfRange);
}
_preflightMaxAge = value;
}
}
/// <summary>
/// Returns a <see cref="System.String" /> that represents this instance.
/// </summary>
/// <returns>
/// A <see cref="System.String" /> that represents this instance.
/// </returns>
public override string ToString()
{
var builder = new StringBuilder();
builder.Append("AllowCredentials: ");
builder.Append(SupportsCredentials);
builder.Append(", PreflightMaxAge: ");
builder.Append(PreflightMaxAge.HasValue ?
PreflightMaxAge.Value.TotalSeconds.ToString() : "null");
builder.Append(", AllowOrigin: ");
builder.Append(AllowedOrigin);
builder.Append(", AllowExposedHeaders: {");
builder.Append(string.Join(",", AllowedExposedHeaders));
builder.Append("}");
builder.Append(", AllowHeaders: {");
builder.Append(string.Join(",", AllowedHeaders));
builder.Append("}");
builder.Append(", AllowMethods: {");
builder.Append(string.Join(",", AllowedMethods));
builder.Append("}");
return builder.ToString();
}
}
}

View File

@ -0,0 +1,212 @@
// 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 Microsoft.AspNet.Http;
using Microsoft.Framework.Internal;
using Microsoft.Framework.OptionsModel;
namespace Microsoft.AspNet.Cors.Core
{
/// <summary>
/// Default implementation of <see cref="ICorsService"/>.
/// </summary>
public class CorsService : ICorsService
{
private readonly CorsOptions _options;
/// <summary>
/// Creates a new instance of the <see cref="CorsService"/>.
/// </summary>
/// <param name="options">The option model representing <see cref="CorsOptions"/>.</param>
public CorsService([NotNull] IOptions<CorsOptions> options)
{
_options = options.Options;
}
/// <summary>
/// Looks up a policy using the <paramref name="policyName"/> and then evaluates the policy using the passed in
/// <paramref name="context"/>.
/// </summary>
/// <param name="requestContext"></param>
/// <param name="policyName"></param>
/// <returns>A <see cref="CorsResult"/> which contains the result of policy evaluation and can be
/// used by the caller to set apporpriate response headers.</returns>
public CorsResult EvaluatePolicy([NotNull] HttpContext context, string policyName)
{
var policy = _options.GetPolicy(policyName);
return EvaluatePolicy(context, policy);
}
/// <inheritdoc />
public CorsResult EvaluatePolicy([NotNull] HttpContext context, [NotNull] CorsPolicy policy)
{
var corsResult = new CorsResult();
var accessControlRequestMethod = context.Request.Headers.Get(CorsConstants.AccessControlRequestMethod);
if (string.Equals(context.Request.Method, CorsConstants.PreflightHttpMethod, StringComparison.Ordinal) &&
accessControlRequestMethod != null)
{
EvaluatePreflightRequest(context, policy, corsResult);
}
else
{
EvaluateRequest(context, policy, corsResult);
}
return corsResult;
}
public virtual void EvaluateRequest(HttpContext context, CorsPolicy policy, CorsResult result)
{
var origin = context.Request.Headers.Get(CorsConstants.Origin);
if (origin == null || !policy.AllowAnyOrigin && !policy.Origins.Contains(origin))
{
return;
}
AddOriginToResult(origin, policy, result);
result.SupportsCredentials = policy.SupportsCredentials;
AddHeaderValues(result.AllowedExposedHeaders, policy.ExposedHeaders);
}
public virtual void EvaluatePreflightRequest(HttpContext context, CorsPolicy policy, CorsResult result)
{
var origin = context.Request.Headers.Get(CorsConstants.Origin);
if (origin == null || !policy.AllowAnyOrigin && !policy.Origins.Contains(origin))
{
return;
}
var accessControlRequestMethod = context.Request.Headers.Get(CorsConstants.AccessControlRequestMethod);
if (accessControlRequestMethod == null)
{
return;
}
var requestHeaders =
context.Request.Headers.GetCommaSeparatedValues(CorsConstants.AccessControlRequestHeaders);
if (!policy.AllowAnyMethod && !policy.Methods.Contains(accessControlRequestMethod))
{
return;
}
if (!policy.AllowAnyHeader &&
requestHeaders != null &&
!requestHeaders.All(header => policy.Headers.Contains(header, StringComparer.Ordinal)))
{
return;
}
AddOriginToResult(origin, policy, result);
result.SupportsCredentials = policy.SupportsCredentials;
result.PreflightMaxAge = policy.PreflightMaxAge;
result.AllowedMethods.Add(accessControlRequestMethod);
AddHeaderValues(result.AllowedHeaders, requestHeaders);
}
/// <inheritdoc />
public virtual void ApplyResult(CorsResult result, HttpResponse response)
{
var headers = response.Headers;
if (result.AllowedOrigin != null)
{
headers.Add(CorsConstants.AccessControlAllowOrigin, new[] { result.AllowedOrigin });
}
if (result.VaryByOrigin)
{
headers.Set("Vary", "Origin");
}
if (result.SupportsCredentials)
{
headers.Add(CorsConstants.AccessControlAllowCredentials, new[] { "true" });
}
if (result.AllowedMethods.Count > 0)
{
// Filter out simple methods
var nonSimpleAllowMethods = result.AllowedMethods
.Where(m =>
!CorsConstants.SimpleMethods.Contains(m, StringComparer.OrdinalIgnoreCase))
.ToArray();
if (nonSimpleAllowMethods.Length > 0)
{
headers.Add(CorsConstants.AccessControlAllowMethods, nonSimpleAllowMethods);
}
}
if (result.AllowedHeaders.Count > 0)
{
// Filter out simple request headers
var nonSimpleAllowRequestHeaders = result.AllowedHeaders
.Where(header =>
!CorsConstants.SimpleRequestHeaders.Contains(header, StringComparer.OrdinalIgnoreCase))
.ToArray();
if (nonSimpleAllowRequestHeaders.Length > 0)
{
headers.Add(CorsConstants.AccessControlAllowHeaders, nonSimpleAllowRequestHeaders);
}
}
if (result.AllowedExposedHeaders.Count > 0)
{
// Filter out simple response headers
var nonSimpleAllowResponseHeaders = result.AllowedExposedHeaders
.Where(header =>
!CorsConstants.SimpleResponseHeaders.Contains(header, StringComparer.OrdinalIgnoreCase))
.ToArray();
if (nonSimpleAllowResponseHeaders.Length > 0)
{
headers.Add(CorsConstants.AccessControlExposeHeaders, nonSimpleAllowResponseHeaders.ToArray());
}
}
if (result.PreflightMaxAge.HasValue)
{
headers.Set(
CorsConstants.AccessControlMaxAge,
result.PreflightMaxAge.Value.TotalSeconds.ToString());
}
}
private void AddOriginToResult(string origin, CorsPolicy policy, CorsResult result)
{
if (policy.AllowAnyOrigin)
{
if (policy.SupportsCredentials)
{
result.AllowedOrigin = origin;
result.VaryByOrigin = true;
}
else
{
result.AllowedOrigin = CorsConstants.AnyOrigin;
}
}
else if (policy.Origins.Contains(origin))
{
result.AllowedOrigin = origin;
}
}
private static void AddHeaderValues(IList<string> target, IEnumerable<string> headerValues)
{
if (headerValues == null)
{
return;
}
foreach (var current in headerValues)
{
target.Add(current);
}
}
}
}

View File

@ -0,0 +1,42 @@
// 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 Microsoft.AspNet.Cors;
using Microsoft.AspNet.Cors.Core;
using Microsoft.Framework.ConfigurationModel;
using Microsoft.Framework.Internal;
namespace Microsoft.Framework.DependencyInjection
{
/// <summary>
/// The <see cref="IServiceCollection"/> extensions for enabling CORS support.
/// </summary>
public static class CorsServiceCollectionExtensions
{
/// <summary>
/// Can be used to configure services in the <paramref name="serviceCollection"/>.
/// </summary>
/// <param name="serviceCollection">The service collection which needs to be configured.</param>
/// <param name="configure">A delegate which is run to configure the services.</param>
/// <returns></returns>
public static IServiceCollection ConfigureCors(
[NotNull] this IServiceCollection serviceCollection,
[NotNull] Action<CorsOptions> configure)
{
return serviceCollection.Configure(configure);
}
/// <summary>
/// Add services needed to support CORS to the given <paramref name="serviceCollection"/>.
/// </summary>
/// <param name="serviceCollection">The service collection to which CORS services are added.</param>
/// <returns>The updated <see cref="IServiceCollection"/>.</returns>
public static IServiceCollection AddCors(this IServiceCollection serviceCollection)
{
serviceCollection.AddOptions();
serviceCollection.AddTransient<ICorsService, CorsService>();
return serviceCollection;
}
}
}

View File

@ -0,0 +1,13 @@
// 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;
namespace Microsoft.AspNet.Cors.Core
{
/// <inheritdoc />
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public class DisableCorsAttribute : Attribute, IDisableCorsMetadata
{
}
}

View File

@ -0,0 +1,24 @@
// 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;
namespace Microsoft.AspNet.Cors.Core
{
/// <inheritdoc />
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class EnableCorsAttribute : Attribute, IEnableCorsMetadata
{
/// <summary>
/// Creates a new instance of the <see cref="EnableCorsAttribute"/>.
/// </summary>
/// <param name="policyName">The name of the policy to be applied.</param>
public EnableCorsAttribute(string policyName)
{
PolicyName = policyName;
}
/// <inheritdoc />
public string PolicyName { get; set; }
}
}

View File

@ -0,0 +1,32 @@
// 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.Http;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Cors.Core
{
/// <summary>
/// A type which can evaluate a policy for a particular <see cref="HttpContext"/>.
/// </summary>
public interface ICorsService
{
/// <summary>
/// Evaluates the given <paramref name="policy"/> using the passed in <paramref name="context"/>.
/// </summary>
/// <param name="context">The <see cref="HttpContext"/> associated with the call.</param>
/// <param name="policy">The <see cref="CorsPolicy"/> which needs to be evaluated.</param>
/// <returns>A <see cref="CorsResult"/> which contains the result of policy evaluation and can be
/// used by the caller to set apporpriate response headers.</returns>
CorsResult EvaluatePolicy([NotNull] HttpContext context, [NotNull] CorsPolicy policy);
/// <summary>
/// Adds CORS-specific response headers to the given <paramref name="response"/>.
/// </summary>
/// <param name="result">The <see cref="CorsResult"/> used to read the allowed values.</param>
/// <param name="response">The <see cref="HttpResponse"/> associated with the current call.</param>
void ApplyResult([NotNull] CorsResult result, [NotNull] HttpResponse response);
}
}

View File

@ -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.Cors.Core
{
/// <summary>
/// An interface which can be used to identify a type which provides metdata to disable cors for a resource.
/// </summary>
public interface IDisableCorsMetadata
{
}
}

View File

@ -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.
namespace Microsoft.AspNet.Cors.Core
{
/// <summary>
/// An interface which can be used to identify a type which provides metadata needed for enabling CORS support.
/// </summary>
public interface IEnableCorsMetadata
{
/// <summary>
/// The name of the policy which needs to be applied.
/// </summary>
string PolicyName { get; set; }
}
}

View File

@ -0,0 +1,110 @@
// <auto-generated />
namespace Microsoft.AspNet.Cors.Core
{
using System.Globalization;
using System.Reflection;
using System.Resources;
internal static class Resources
{
private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.AspNet.Cors.Core.Resources", typeof(Resources).GetTypeInfo().Assembly);
/// <summary>
/// The collection of headers '{0}' is not allowed.
/// </summary>
internal static string HeadersNotAllowed
{
get { return GetString("HeadersNotAllowed"); }
}
/// <summary>
/// The collection of headers '{0}' is not allowed.
/// </summary>
internal static string FormatHeadersNotAllowed(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("HeadersNotAllowed"), p0);
}
/// <summary>
/// The method '{0}' is not allowed.
/// </summary>
internal static string MethodNotAllowed
{
get { return GetString("MethodNotAllowed"); }
}
/// <summary>
/// The method '{0}' is not allowed.
/// </summary>
internal static string FormatMethodNotAllowed(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("MethodNotAllowed"), p0);
}
/// <summary>
/// The request does not contain the Origin header.
/// </summary>
internal static string NoOriginHeader
{
get { return GetString("NoOriginHeader"); }
}
/// <summary>
/// The request does not contain the Origin header.
/// </summary>
internal static string FormatNoOriginHeader()
{
return GetString("NoOriginHeader");
}
/// <summary>
/// The origin '{0}' is not allowed.
/// </summary>
internal static string OriginNotAllowed
{
get { return GetString("OriginNotAllowed"); }
}
/// <summary>
/// The origin '{0}' is not allowed.
/// </summary>
internal static string FormatOriginNotAllowed(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("OriginNotAllowed"), p0);
}
/// <summary>
/// PreflightMaxAge must be greater than or equal to 0.
/// </summary>
internal static string PreflightMaxAgeOutOfRange
{
get { return GetString("PreflightMaxAgeOutOfRange"); }
}
/// <summary>
/// PreflightMaxAge must be greater than or equal to 0.
/// </summary>
internal static string FormatPreflightMaxAgeOutOfRange()
{
return GetString("PreflightMaxAgeOutOfRange");
}
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
System.Diagnostics.Debug.Assert(value != null);
if (formatterNames != null)
{
for (var i = 0; i < formatterNames.Length; i++)
{
value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
}
}
return value;
}
}
}

View File

@ -0,0 +1,135 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="HeadersNotAllowed" xml:space="preserve">
<value>The collection of headers '{0}' is not allowed.</value>
</data>
<data name="MethodNotAllowed" xml:space="preserve">
<value>The method '{0}' is not allowed.</value>
</data>
<data name="NoOriginHeader" xml:space="preserve">
<value>The request does not contain the Origin header.</value>
</data>
<data name="OriginNotAllowed" xml:space="preserve">
<value>The origin '{0}' is not allowed.</value>
</data>
<data name="PreflightMaxAgeOutOfRange" xml:space="preserve">
<value>PreflightMaxAge must be greater than or equal to 0.</value>
</data>
</root>

View File

@ -1,7 +1,12 @@
{
"version": "1.0.0-*",
"dependencies": {
},
"version": "1.0.0-*",
"dependencies": {
"Microsoft.Framework.ConfigurationModel.Interfaces": "1.0.0-*",
"Microsoft.Framework.DependencyInjection.Interfaces": "1.0.0-*",
"Microsoft.Framework.OptionsModel": "1.0.0-*",
"Microsoft.AspNet.Http": "1.0.0-*",
"Microsoft.Framework.NotNullAttribute.Internal": { "version": "1.0.0-*", "type": "build" }
},
"frameworks" : {
"dnx451" : {
@ -14,4 +19,4 @@
}
}
}
}
}

View File

@ -0,0 +1,233 @@
// 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 Xunit;
namespace Microsoft.AspNet.Cors.Core.Test
{
public class CorsPolicyBuilderTests
{
[Fact]
public void Constructor_WithPolicy_AddsTheGivenPolicy()
{
// Arrange
var policy = new CorsPolicy();
policy.Origins.Add("http://existing.com");
policy.Headers.Add("Existing");
policy.Methods.Add("GET");
policy.ExposedHeaders.Add("ExistingExposed");
policy.SupportsCredentials = true;
policy.PreflightMaxAge = TimeSpan.FromSeconds(12);
// Act
var builder = new CorsPolicyBuilder(policy);
// Assert
var corsPolicy = builder.Build();
Assert.False(corsPolicy.AllowAnyHeader);
Assert.False(corsPolicy.AllowAnyMethod);
Assert.False(corsPolicy.AllowAnyOrigin);
Assert.True(corsPolicy.SupportsCredentials);
Assert.Equal(policy.Headers, corsPolicy.Headers);
Assert.Equal(policy.Methods, corsPolicy.Methods);
Assert.Equal(policy.Origins, corsPolicy.Origins);
Assert.Equal(policy.ExposedHeaders, corsPolicy.ExposedHeaders);
Assert.Equal(TimeSpan.FromSeconds(12), corsPolicy.PreflightMaxAge);
}
[Fact]
public void Constructor_WithNoOrigin()
{
// Arrange & Act
var builder = new CorsPolicyBuilder();
// Assert
var corsPolicy = builder.Build();
Assert.False(corsPolicy.AllowAnyHeader);
Assert.False(corsPolicy.AllowAnyMethod);
Assert.False(corsPolicy.AllowAnyOrigin);
Assert.False(corsPolicy.SupportsCredentials);
Assert.Empty(corsPolicy.ExposedHeaders);
Assert.Empty(corsPolicy.Headers);
Assert.Empty(corsPolicy.Methods);
Assert.Empty(corsPolicy.Origins);
Assert.Null(corsPolicy.PreflightMaxAge);
}
[Theory]
[InlineData("")]
[InlineData("http://example.com,http://example2.com")]
public void Constructor_WithParamsOrigin_InitializesOrigin(string origin)
{
// Arrange
var origins = origin.Split(',');
// Act
var builder = new CorsPolicyBuilder(origins);
// Assert
var corsPolicy = builder.Build();
Assert.False(corsPolicy.AllowAnyHeader);
Assert.False(corsPolicy.AllowAnyMethod);
Assert.False(corsPolicy.AllowAnyOrigin);
Assert.False(corsPolicy.SupportsCredentials);
Assert.Empty(corsPolicy.ExposedHeaders);
Assert.Empty(corsPolicy.Headers);
Assert.Empty(corsPolicy.Methods);
Assert.Equal(origins.ToList(), corsPolicy.Origins);
Assert.Null(corsPolicy.PreflightMaxAge);
}
[Fact]
public void AddOrigins_AddsOrigins()
{
// Arrange
var builder = new CorsPolicyBuilder();
// Act
builder.AddOrigins("http://example.com", "http://example2.com");
// Assert
var corsPolicy = builder.Build();
Assert.False(corsPolicy.AllowAnyOrigin);
Assert.Equal(new List<string>() { "http://example.com", "http://example2.com" }, corsPolicy.Origins);
}
[Fact]
public void AllowAnyOrigin_AllowsAny()
{
// Arrange
var builder = new CorsPolicyBuilder();
// Act
builder.AllowAnyOrigin();
// Assert
var corsPolicy = builder.Build();
Assert.True(corsPolicy.AllowAnyOrigin);
Assert.Equal(new List<string>() { "*" }, corsPolicy.Origins);
}
[Fact]
public void AddMethods_AddsMethods()
{
// Arrange
var builder = new CorsPolicyBuilder();
// Act
builder.AddMethods("PUT", "GET");
// Assert
var corsPolicy = builder.Build();
Assert.False(corsPolicy.AllowAnyOrigin);
Assert.Equal(new List<string>() { "PUT", "GET" }, corsPolicy.Methods);
}
[Fact]
public void AllowAnyMethod_AllowsAny()
{
// Arrange
var builder = new CorsPolicyBuilder();
// Act
builder.AllowAnyMethod();
// Assert
var corsPolicy = builder.Build();
Assert.True(corsPolicy.AllowAnyMethod);
Assert.Equal(new List<string>() { "*" }, corsPolicy.Methods);
}
[Fact]
public void AddHeaders_AddsHeaders()
{
// Arrange
var builder = new CorsPolicyBuilder();
// Act
builder.AddHeaders("example1", "example2");
// Assert
var corsPolicy = builder.Build();
Assert.False(corsPolicy.AllowAnyHeader);
Assert.Equal(new List<string>() { "example1", "example2" }, corsPolicy.Headers);
}
[Fact]
public void AllowAnyHeaders_AllowsAny()
{
// Arrange
var builder = new CorsPolicyBuilder();
// Act
builder.AllowAnyHeader();
// Assert
var corsPolicy = builder.Build();
Assert.True(corsPolicy.AllowAnyHeader);
Assert.Equal(new List<string>() { "*" }, corsPolicy.Headers);
}
[Fact]
public void AddExposedHeaders_AddsExposedHeaders()
{
// Arrange
var builder = new CorsPolicyBuilder();
// Act
builder.AddExposedHeaders("exposed1", "exposed2");
// Assert
var corsPolicy = builder.Build();
Assert.Equal(new List<string>() { "exposed1", "exposed2" }, corsPolicy.ExposedHeaders);
}
[Fact]
public void SetPreFlightMaxAge_SetsThePreFlightAge()
{
// Arrange
var builder = new CorsPolicyBuilder();
// Act
builder.SetPreflightMaxAge(TimeSpan.FromSeconds(12));
// Assert
var corsPolicy = builder.Build();
Assert.Equal(TimeSpan.FromSeconds(12), corsPolicy.PreflightMaxAge);
}
[Fact]
public void AllowCredential_SetsSupportsCredentials_ToTrue()
{
// Arrange
var builder = new CorsPolicyBuilder();
// Act
builder.AllowCredentials();
// Assert
var corsPolicy = builder.Build();
Assert.True(corsPolicy.SupportsCredentials);
}
[Fact]
public void DisallowCredential_SetsSupportsCredentials_ToFalse()
{
// Arrange
var builder = new CorsPolicyBuilder();
// Act
builder.DisallowCredentials();
// Assert
var corsPolicy = builder.Build();
Assert.False(corsPolicy.SupportsCredentials);
}
}
}

View File

@ -0,0 +1,73 @@
// 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 Xunit;
namespace Microsoft.AspNet.Cors.Core.Test
{
public class CorsPolicyTest
{
[Fact]
public void Default_Constructor()
{
// Arrange & Act
var corsPolicy = new CorsPolicy();
// Assert
Assert.False(corsPolicy.AllowAnyHeader);
Assert.False(corsPolicy.AllowAnyMethod);
Assert.False(corsPolicy.AllowAnyOrigin);
Assert.False(corsPolicy.SupportsCredentials);
Assert.Empty(corsPolicy.ExposedHeaders);
Assert.Empty(corsPolicy.Headers);
Assert.Empty(corsPolicy.Methods);
Assert.Empty(corsPolicy.Origins);
Assert.Null(corsPolicy.PreflightMaxAge);
}
[Fact]
public void SettingNegativePreflightMaxAge_Throws()
{
// Arrange
var policy = new CorsPolicy();
// Act
var exception = Assert.Throws<ArgumentOutOfRangeException>(() =>
{
policy.PreflightMaxAge = TimeSpan.FromSeconds(-12);
});
// Assert
Assert.Equal(
"PreflightMaxAge must be greater than or equal to 0.\r\nParameter name: value",
exception.Message);
}
[Fact]
public void ToString_ReturnsThePropertyValues()
{
// Arrange
var corsPolicy = new CorsPolicy
{
PreflightMaxAge = TimeSpan.FromSeconds(12),
SupportsCredentials = true
};
corsPolicy.Headers.Add("foo");
corsPolicy.Headers.Add("bar");
corsPolicy.Origins.Add("http://example.com");
corsPolicy.Origins.Add("http://example.org");
corsPolicy.Methods.Add("GET");
// Act
var policyString = corsPolicy.ToString();
// Assert
Assert.Equal(
@"AllowAnyHeader: False, AllowAnyMethod: False, AllowAnyOrigin: False, PreflightMaxAge: 12,"+
" SupportsCredentials: True, Origins: {http://example.com,http://example.org}, Methods: {GET},"+
" Headers: {foo,bar}, ExposedHeaders: {}",
policyString);
}
}
}

View File

@ -0,0 +1,69 @@
// 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 Xunit;
namespace Microsoft.AspNet.Cors.Core.Test
{
public class CorsResultTest
{
[Fact]
public void Default_Constructor()
{
// Arrange & Act
var result = new CorsResult();
// Assert
Assert.Empty(result.AllowedHeaders);
Assert.Empty(result.AllowedExposedHeaders);
Assert.Empty(result.AllowedMethods);
Assert.False(result.SupportsCredentials);
Assert.Null(result.AllowedOrigin);
Assert.Null(result.PreflightMaxAge);
}
[Fact]
public void SettingNegativePreflightMaxAge_Throws()
{
// Arrange
var result = new CorsResult();
// Act
var exception = Assert.Throws<ArgumentOutOfRangeException>(() =>
{
result.PreflightMaxAge = TimeSpan.FromSeconds(-1);
});
// Assert
Assert.Equal(
"PreflightMaxAge must be greater than or equal to 0.\r\nParameter name: value",
exception.Message);
}
[Fact]
public void ToString_ReturnsThePropertyValues()
{
// Arrange
var corsResult = new CorsResult
{
SupportsCredentials = true,
PreflightMaxAge = TimeSpan.FromSeconds(30),
AllowedOrigin = "*"
};
corsResult.AllowedExposedHeaders.Add("foo");
corsResult.AllowedHeaders.Add("bar");
corsResult.AllowedHeaders.Add("baz");
corsResult.AllowedMethods.Add("GET");
// Act
var result = corsResult.ToString();
// Assert
Assert.Equal(
@"AllowCredentials: True, PreflightMaxAge: 30, AllowOrigin: *," +
" AllowExposedHeaders: {foo}, AllowHeaders: {bar,baz}, AllowMethods: {GET}",
result);
}
}
}

View File

@ -0,0 +1,904 @@
// 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 Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Core;
using Microsoft.Framework.OptionsModel;
using Moq;
using Xunit;
namespace Microsoft.AspNet.Cors.Core.Test
{
public class CorsServiceTests
{
[Fact]
public void EvaluatePolicy_NoOrigin_ReturnsInvalidResult()
{
// Arrange
var corsService = new CorsService(Mock.Of<IOptions<CorsOptions>>());
var requestContext = GetHttpContext("GET", origin: null);
// Act
var result = corsService.EvaluatePolicy(requestContext, new CorsPolicy());
// Assert
Assert.Null(result.AllowedOrigin);
Assert.False(result.VaryByOrigin);
}
[Fact]
public void EvaluatePolicy_NoMatchingOrigin_ReturnsInvalidResult()
{
// Arrange
var corsService = new CorsService(Mock.Of<IOptions<CorsOptions>>());
var requestContext = GetHttpContext(origin: "http://example.com");
var policy = new CorsPolicy();
policy.Origins.Add("bar");
// Act
var result = corsService.EvaluatePolicy(requestContext, policy);
// Assert
Assert.Null(result.AllowedOrigin);
Assert.False(result.VaryByOrigin);
}
[Fact]
public void EvaluatePolicy_EmptyOriginsPolicy_ReturnsInvalidResult()
{
// Arrange
var corsService = new CorsService(Mock.Of<IOptions<CorsOptions>>());
var requestContext = GetHttpContext(origin: "http://example.com");
var policy = new CorsPolicy();
// Act
var result = corsService.EvaluatePolicy(requestContext, policy);
// Assert
Assert.Null(result.AllowedOrigin);
Assert.False(result.VaryByOrigin);
}
[Fact]
public void EvaluatePolicy_AllowAnyOrigin_DoesNotSupportCredentials_EmitsWildcardForOrigin()
{
// Arrange
var corsService = new CorsService(Mock.Of<IOptions<CorsOptions>>());
var requestContext = GetHttpContext(origin: "http://example.com");
var policy = new CorsPolicy
{
SupportsCredentials = false
};
policy.Origins.Add(CorsConstants.AnyOrigin);
// Act
var result = corsService.EvaluatePolicy(requestContext, policy);
// Assert
Assert.Equal("*", result.AllowedOrigin);
}
[Fact]
public void EvaluatePolicy_AllowAnyOrigin_SupportsCredentials_AddsSpecificOrigin()
{
// Arrange
var corsService = new CorsService(Mock.Of<IOptions<CorsOptions>>());
var requestContext = GetHttpContext(origin: "http://example.com");
var policy = new CorsPolicy
{
SupportsCredentials = true
};
policy.Origins.Add(CorsConstants.AnyOrigin);
// Act
var result = corsService.EvaluatePolicy(requestContext, policy);
// Assert
Assert.Equal("http://example.com", result.AllowedOrigin);
Assert.True(result.VaryByOrigin);
}
[Fact]
public void EvaluatePolicy_DoesNotSupportCredentials_AllowCredentialsReturnsFalse()
{
// Arrange
var corsService = new CorsService(Mock.Of<IOptions<CorsOptions>>());
var requestContext = GetHttpContext(origin: "http://example.com");
var policy = new CorsPolicy
{
SupportsCredentials = false
};
policy.Origins.Add(CorsConstants.AnyOrigin);
// Act
var result = corsService.EvaluatePolicy(requestContext, policy);
// Assert
Assert.False(result.SupportsCredentials);
}
[Fact]
public void EvaluatePolicy_SupportsCredentials_AllowCredentialsReturnsTrue()
{
// Arrange
var corsService = new CorsService(Mock.Of<IOptions<CorsOptions>>());
var requestContext = GetHttpContext(origin: "http://example.com");
var policy = new CorsPolicy
{
SupportsCredentials = true
};
policy.Origins.Add(CorsConstants.AnyOrigin);
// Act
var result = corsService.EvaluatePolicy(requestContext, policy);
// Assert
Assert.True(result.SupportsCredentials);
}
[Fact]
public void EvaluatePolicy_NoExposedHeaders_NoAllowExposedHeaders()
{
// Arrange
var corsService = new CorsService(Mock.Of<IOptions<CorsOptions>>());
var requestContext = GetHttpContext(origin: "http://example.com");
var policy = new CorsPolicy();
policy.Origins.Add(CorsConstants.AnyOrigin);
// Act
var result = corsService.EvaluatePolicy(requestContext, policy);
// Assert
Assert.Empty(result.AllowedExposedHeaders);
}
[Fact]
public void EvaluatePolicy_OneExposedHeaders_HeadersAllowed()
{
// Arrange
var corsService = new CorsService(Mock.Of<IOptions<CorsOptions>>());
var requestContext = GetHttpContext(origin: "http://example.com");
var policy = new CorsPolicy();
policy.Origins.Add(CorsConstants.AnyOrigin);
policy.ExposedHeaders.Add("foo");
// Act
var result = corsService.EvaluatePolicy(requestContext, policy);
// Assert
Assert.Equal(1, result.AllowedExposedHeaders.Count);
Assert.Contains("foo", result.AllowedExposedHeaders);
}
[Fact]
public void EvaluatePolicy_ManyExposedHeaders_HeadersAllowed()
{
// Arrange
var corsService = new CorsService(Mock.Of<IOptions<CorsOptions>>());
var requestContext = GetHttpContext(origin: "http://example.com");
var policy = new CorsPolicy();
policy.Origins.Add(CorsConstants.AnyOrigin);
policy.ExposedHeaders.Add("foo");
policy.ExposedHeaders.Add("bar");
policy.ExposedHeaders.Add("baz");
// Act
var result = corsService.EvaluatePolicy(requestContext, policy);
// Assert
Assert.Equal(3, result.AllowedExposedHeaders.Count);
Assert.Contains("foo", result.AllowedExposedHeaders);
Assert.Contains("bar", result.AllowedExposedHeaders);
Assert.Contains("baz", result.AllowedExposedHeaders);
}
[Fact]
public void EvaluatePolicy_PreflightRequest_MethodNotAllowed_ReturnsInvalidResult()
{
// Arrange
var corsService = new CorsService(Mock.Of<IOptions<CorsOptions>>());
var requestContext = GetHttpContext(method: "OPTIONS", origin: "http://example.com", accessControlRequestMethod: "PUT");
var policy = new CorsPolicy();
policy.Origins.Add(CorsConstants.AnyOrigin);
policy.Methods.Add("GET");
// Act
var result = corsService.EvaluatePolicy(requestContext, policy);
// Assert
Assert.Empty(result.AllowedMethods);
}
[Fact]
public void EvaluatePolicy_PreflightRequest_MethodAllowed_ReturnsAllowMethods()
{
// Arrange
var corsService = new CorsService(Mock.Of<IOptions<CorsOptions>>());
var requestContext = GetHttpContext(method: "OPTIONS", origin: "http://example.com", accessControlRequestMethod: "PUT");
var policy = new CorsPolicy();
policy.Origins.Add(CorsConstants.AnyOrigin);
policy.Methods.Add("PUT");
// Act
var result = corsService.EvaluatePolicy(requestContext, policy);
// Assert
Assert.NotNull(result);
Assert.Contains("PUT", result.AllowedMethods);
}
[Fact]
public void EvaluatePolicy_PreflightRequest_OriginAllowed_ReturnsOrigin()
{
// Arrange
var corsService = new CorsService(Mock.Of<IOptions<CorsOptions>>());
var requestContext = GetHttpContext(method: "OPTIONS", origin: "http://example.com", accessControlRequestMethod: "PUT");
var policy = new CorsPolicy();
policy.Origins.Add(CorsConstants.AnyOrigin);
policy.Origins.Add("http://example.com");
policy.Methods.Add("*");
// Act
var result = corsService.EvaluatePolicy(requestContext, policy);
// Assert
Assert.Equal("http://example.com", result.AllowedOrigin);
}
[Fact]
public void EvaluatePolicy_PreflightRequest_SupportsCredentials_AllowCredentialsReturnsTrue()
{
// Arrange
var corsService = new CorsService(Mock.Of<IOptions<CorsOptions>>());
var requestContext = GetHttpContext(method: "OPTIONS", origin: "http://example.com", accessControlRequestMethod: "PUT");
var policy = new CorsPolicy
{
SupportsCredentials = true
};
policy.Origins.Add(CorsConstants.AnyOrigin);
policy.Methods.Add("*");
// Act
var result = corsService.EvaluatePolicy(requestContext, policy);
// Assert
Assert.True(result.SupportsCredentials);
}
[Fact]
public void EvaluatePolicy_PreflightRequest_NoPreflightMaxAge_NoPreflightMaxAgeSet()
{
// Arrange
var corsService = new CorsService(Mock.Of<IOptions<CorsOptions>>());
var requestContext = GetHttpContext(method: "OPTIONS", origin: "http://example.com", accessControlRequestMethod: "PUT");
var policy = new CorsPolicy
{
PreflightMaxAge = null
};
policy.Origins.Add(CorsConstants.AnyOrigin);
policy.Methods.Add("*");
// Act
var result = corsService.EvaluatePolicy(requestContext, policy);
// Assert
Assert.Null(result.PreflightMaxAge);
}
[Fact]
public void EvaluatePolicy_PreflightRequest_PreflightMaxAge_PreflightMaxAgeSet()
{
// Arrange
var corsService = new CorsService(Mock.Of<IOptions<CorsOptions>>());
var requestContext = GetHttpContext(method: "OPTIONS", origin: "http://example.com", accessControlRequestMethod: "PUT");
var policy = new CorsPolicy
{
PreflightMaxAge = TimeSpan.FromSeconds(10)
};
policy.Origins.Add(CorsConstants.AnyOrigin);
policy.Methods.Add("*");
// Act
var result = corsService.EvaluatePolicy(requestContext, policy);
// Assert
Assert.Equal(TimeSpan.FromSeconds(10), result.PreflightMaxAge);
}
[Fact]
public void EvaluatePolicy_PreflightRequest_AnyMethod_ReturnsRequestMethod()
{
// Arrange
var corsService = new CorsService(Mock.Of<IOptions<CorsOptions>>());
var requestContext = GetHttpContext(method: "OPTIONS", origin: "http://example.com", accessControlRequestMethod: "GET");
var policy = new CorsPolicy();
policy.Origins.Add(CorsConstants.AnyOrigin);
policy.Methods.Add("*");
// Act
var result = corsService.EvaluatePolicy(requestContext, policy);
// Assert
Assert.Equal(1, result.AllowedMethods.Count);
Assert.Contains("GET", result.AllowedMethods);
}
[Fact]
public void EvaluatePolicy_PreflightRequest_ListedMethod_ReturnsSubsetOfListedMethods()
{
// Arrange
var corsService = new CorsService(Mock.Of<IOptions<CorsOptions>>());
var requestContext = GetHttpContext(method: "OPTIONS", origin: "http://example.com", accessControlRequestMethod: "PUT");
var policy = new CorsPolicy();
policy.Origins.Add(CorsConstants.AnyOrigin);
policy.Methods.Add("PUT");
policy.Methods.Add("DELETE");
// Act
var result = corsService.EvaluatePolicy(requestContext, policy);
// Assert
Assert.Equal(1, result.AllowedMethods.Count);
Assert.Contains("PUT", result.AllowedMethods);
}
[Fact]
public void EvaluatePolicy_PreflightRequest_NoHeadersRequested_AllowedAllHeaders_ReturnsEmptyHeaders()
{
// Arrange
var corsService = new CorsService(Mock.Of<IOptions<CorsOptions>>());
var requestContext = GetHttpContext(method: "OPTIONS", origin: "http://example.com", accessControlRequestMethod: "PUT");
var policy = new CorsPolicy();
policy.Origins.Add(CorsConstants.AnyOrigin);
policy.Methods.Add("*");
policy.Headers.Add("*");
// Act
var result = corsService.EvaluatePolicy(requestContext, policy);
// Assert
Assert.Empty(result.AllowedHeaders);
}
[Fact]
public void EvaluatePolicy_PreflightRequest_HeadersRequested_AllowAllHeaders_ReturnsRequestedHeaders()
{
// Arrange
var corsService = new CorsService(Mock.Of<IOptions<CorsOptions>>());
var requestContext = GetHttpContext(
method: "OPTIONS",
origin: "http://example.com",
accessControlRequestMethod: "PUT",
accessControlRequestHeaders: new[] { "foo", "bar" });
var policy = new CorsPolicy();
policy.Origins.Add(CorsConstants.AnyOrigin);
policy.Methods.Add("*");
policy.Headers.Add("*");
// Act
var result = corsService.EvaluatePolicy(requestContext, policy);
// Assert
Assert.Equal(2, result.AllowedHeaders.Count);
Assert.Contains("foo", result.AllowedHeaders);
Assert.Contains("bar", result.AllowedHeaders);
}
[Fact]
public void EvaluatePolicy_PreflightRequest_HeadersRequested_AllowSomeHeaders_ReturnsSubsetOfListedHeaders()
{
// Arrange
var corsService = new CorsService(Mock.Of<IOptions<CorsOptions>>());
var requestContext = GetHttpContext(
method: "OPTIONS",
origin: "http://example.com",
accessControlRequestMethod: "PUT",
accessControlRequestHeaders: new[] { "Content-Type" });
var policy = new CorsPolicy();
policy.Origins.Add(CorsConstants.AnyOrigin);
policy.Methods.Add("*");
policy.Headers.Add("foo");
policy.Headers.Add("bar");
policy.Headers.Add("Content-Type");
// Act
var result = corsService.EvaluatePolicy(requestContext, policy);
// Assert
Assert.Equal(1, result.AllowedHeaders.Count);
Assert.Contains("Content-Type", result.AllowedHeaders);
}
[Fact]
public void EvaluatePolicy_PreflightRequest_HeadersRequested_NotAllHeaderMatches_ReturnsInvalidResult()
{
// Arrange
var corsService = new CorsService(Mock.Of<IOptions<CorsOptions>>());
var requestContext = GetHttpContext(
method: "OPTIONS",
origin: "http://example.com",
accessControlRequestMethod: "PUT",
accessControlRequestHeaders: new[] { "match", "noMatch" });
var policy = new CorsPolicy();
policy.Origins.Add(CorsConstants.AnyOrigin);
policy.Methods.Add("*");
policy.Headers.Add("match");
policy.Headers.Add("foo");
// Act
var result = corsService.EvaluatePolicy(requestContext, policy);
// Assert
Assert.Empty(result.AllowedHeaders);
Assert.Empty(result.AllowedMethods);
Assert.Empty(result.AllowedExposedHeaders);
Assert.Null(result.AllowedOrigin);
}
[Fact]
public void EaluatePolicy_DoesCaseSensitiveComparison()
{
// Arrange
var corsService = new CorsService(Mock.Of<IOptions<CorsOptions>>());
var policy = new CorsPolicy();
policy.Methods.Add("POST");
var httpContext = GetHttpContext(origin: null, accessControlRequestMethod: "post");
// Act
var result = corsService.EvaluatePolicy(httpContext, policy);
// Assert
Assert.Empty(result.AllowedHeaders);
Assert.Empty(result.AllowedMethods);
Assert.Empty(result.AllowedExposedHeaders);
Assert.Null(result.AllowedOrigin);
}
[Fact]
public void TryValidateOrigin_DoesCaseSensitiveComparison()
{
// Arrange
var corsService = new CorsService(Mock.Of<IOptions<CorsOptions>>());
var policy = new CorsPolicy();
policy.Origins.Add("http://Example.com");
var httpContext = GetHttpContext(origin: "http://example.com");
// Act
var result = corsService.EvaluatePolicy(httpContext, policy);
// Assert
Assert.Empty(result.AllowedHeaders);
Assert.Empty(result.AllowedMethods);
Assert.Empty(result.AllowedExposedHeaders);
Assert.Null(result.AllowedOrigin);
}
[Fact]
public void ApplyResult_ReturnsNoHeaders_ByDefault()
{
// Arrange
var result = new CorsResult();
var httpContext = new DefaultHttpContext();
var service = new CorsService(Mock.Of<IOptions<CorsOptions>>());
// Act
service.ApplyResult(result, httpContext.Response);
// Assert
Assert.Empty(httpContext.Response.Headers);
}
[Fact]
public void ApplyResult_AllowOrigin_AllowOriginHeaderAdded()
{
// Arrange
var result = new CorsResult
{
AllowedOrigin = "http://example.com"
};
var httpContext = new DefaultHttpContext();
var service = new CorsService(Mock.Of<IOptions<CorsOptions>>());
// Act
service.ApplyResult(result, httpContext.Response);
// Assert
Assert.Equal("http://example.com", httpContext.Response.Headers["Access-Control-Allow-Origin"]);
}
[Fact]
public void ApplyResult_NoAllowOrigin_AllowOriginHeaderNotAdded()
{
// Arrange
var result = new CorsResult
{
AllowedOrigin = null
};
var httpContext = new DefaultHttpContext();
var service = new CorsService(Mock.Of<IOptions<CorsOptions>>());
// Act
service.ApplyResult(result, httpContext.Response);
// Assert
Assert.DoesNotContain("Access-Control-Allow-Origin", httpContext.Response.Headers.Keys);
}
[Fact]
public void ApplyResult_AllowCredentials_AllowCredentialsHeaderAdded()
{
// Arrange
var result = new CorsResult
{
SupportsCredentials = true
};
var service = new CorsService(Mock.Of<IOptions<CorsOptions>>());
// Act
var httpContext = new DefaultHttpContext();
service.ApplyResult(result, httpContext.Response);
// Assert
Assert.Equal("true", httpContext.Response.Headers["Access-Control-Allow-Credentials"]);
}
[Fact]
public void ApplyResult_AddVaryHeader_VaryHeaderAdded()
{
// Arrange
var result = new CorsResult
{
VaryByOrigin = true
};
var httpContext = new DefaultHttpContext();
var service = new CorsService(Mock.Of<IOptions<CorsOptions>>());
// Act
service.ApplyResult(result, httpContext.Response);
// Assert
Assert.Equal("Origin", httpContext.Response.Headers["Vary"]);
}
[Fact]
public void ApplyResult_NoAllowCredentials_AllowCredentialsHeaderNotAdded()
{
// Arrange
var result = new CorsResult
{
SupportsCredentials = false
};
var httpContext = new DefaultHttpContext();
var service = new CorsService(Mock.Of<IOptions<CorsOptions>>());
// Act
service.ApplyResult(result, httpContext.Response);
// Assert
Assert.DoesNotContain("Access-Control-Allow-Credentials", httpContext.Response.Headers.Keys);
}
[Fact]
public void ApplyResult_NoAllowMethods_AllowMethodsHeaderNotAdded()
{
// Arrange
var result = new CorsResult
{
// AllowMethods is empty by default
};
var httpContext = new DefaultHttpContext();
var service = new CorsService(Mock.Of<IOptions<CorsOptions>>());
// Act
service.ApplyResult(result, httpContext.Response);
// Assert
Assert.DoesNotContain("Access-Control-Allow-Methods", httpContext.Response.Headers.Keys);
}
[Fact]
public void ApplyResult_OneAllowMethods_AllowMethodsHeaderAdded()
{
// Arrange
var result = new CorsResult();
result.AllowedMethods.Add("PUT");
var httpContext = new DefaultHttpContext();
var service = new CorsService(Mock.Of<IOptions<CorsOptions>>());
// Act
service.ApplyResult(result, httpContext.Response);
// Assert
Assert.Equal("PUT", httpContext.Response.Headers["Access-Control-Allow-Methods"]);
}
[Fact]
public void ApplyResult_SomeSimpleAllowMethods_AllowMethodsHeaderAddedForNonSimpleMethods()
{
// Arrange
var result = new CorsResult();
result.AllowedMethods.Add("PUT");
result.AllowedMethods.Add("get");
result.AllowedMethods.Add("DELETE");
result.AllowedMethods.Add("POST");
var httpContext = new DefaultHttpContext();
var service = new CorsService(Mock.Of<IOptions<CorsOptions>>());
// Act
service.ApplyResult(result, httpContext.Response);
// Assert
Assert.Contains("Access-Control-Allow-Methods", httpContext.Response.Headers.Keys);
var methods = httpContext.Response.Headers["Access-Control-Allow-Methods"].Split(',');
Assert.Equal(2, methods.Length);
Assert.Contains("PUT", methods);
Assert.Contains("DELETE", methods);
}
[Fact]
public void ApplyResult_SimpleAllowMethods_AllowMethodsHeaderNotAdded()
{
// Arrange
var result = new CorsResult();
result.AllowedMethods.Add("GET");
result.AllowedMethods.Add("HEAD");
result.AllowedMethods.Add("POST");
var httpContext = new DefaultHttpContext();
var service = new CorsService(Mock.Of<IOptions<CorsOptions>>());
// Act
service.ApplyResult(result, httpContext.Response);
// Assert
Assert.DoesNotContain("Access-Control-Allow-Methods", httpContext.Response.Headers.Keys);
}
[Fact]
public void ApplyResult_NoAllowHeaders_AllowHeadersHeaderNotAdded()
{
// Arrange
var result = new CorsResult
{
// AllowHeaders is empty by default
};
var httpContext = new DefaultHttpContext();
var service = new CorsService(Mock.Of<IOptions<CorsOptions>>());
// Act
service.ApplyResult(result, httpContext.Response);
// Assert
Assert.DoesNotContain("Access-Control-Allow-Headers", httpContext.Response.Headers.Keys);
}
[Fact]
public void ApplyResult_OneAllowHeaders_AllowHeadersHeaderAdded()
{
// Arrange
var result = new CorsResult();
result.AllowedHeaders.Add("foo");
var httpContext = new DefaultHttpContext();
var service = new CorsService(Mock.Of<IOptions<CorsOptions>>());
// Act
service.ApplyResult(result, httpContext.Response);
// Assert
Assert.Equal("foo", httpContext.Response.Headers["Access-Control-Allow-Headers"]);
}
[Fact]
public void ApplyResult_ManyAllowHeaders_AllowHeadersHeaderAdded()
{
// Arrange
var result = new CorsResult();
result.AllowedHeaders.Add("foo");
result.AllowedHeaders.Add("bar");
result.AllowedHeaders.Add("baz");
var httpContext = new DefaultHttpContext();
var service = new CorsService(Mock.Of<IOptions<CorsOptions>>());
// Act
service.ApplyResult(result, httpContext.Response);
// Assert
Assert.Contains("Access-Control-Allow-Headers", httpContext.Response.Headers.Keys);
string[] headerValues = httpContext.Response.Headers["Access-Control-Allow-Headers"].Split(',');
Assert.Equal(3, headerValues.Length);
Assert.Contains("foo", headerValues);
Assert.Contains("bar", headerValues);
Assert.Contains("baz", headerValues);
}
[Fact]
public void ApplyResult_SomeSimpleAllowHeaders_AllowHeadersHeaderAddedForNonSimpleHeaders()
{
// Arrange
var result = new CorsResult();
result.AllowedHeaders.Add("Content-Language");
result.AllowedHeaders.Add("foo");
result.AllowedHeaders.Add("bar");
result.AllowedHeaders.Add("Accept");
var httpContext = new DefaultHttpContext();
var service = new CorsService(Mock.Of<IOptions<CorsOptions>>());
// Act
service.ApplyResult(result, httpContext.Response);
// Assert
Assert.Contains("Access-Control-Allow-Headers", httpContext.Response.Headers.Keys);
string[] headerValues = httpContext.Response.Headers["Access-Control-Allow-Headers"].Split(',');
Assert.Equal(2, headerValues.Length);
Assert.Contains("foo", headerValues);
Assert.Contains("bar", headerValues);
}
[Fact]
public void ApplyResult_SimpleAllowHeaders_AllowHeadersHeaderNotAdded()
{
// Arrange
var result = new CorsResult();
result.AllowedHeaders.Add("Accept");
result.AllowedHeaders.Add("Accept-Language");
result.AllowedHeaders.Add("Content-Language");
var httpContext = new DefaultHttpContext();
var service = new CorsService(Mock.Of<IOptions<CorsOptions>>());
// Act
service.ApplyResult(result, httpContext.Response);
// Assert
Assert.DoesNotContain("Access-Control-Allow-Headers", httpContext.Response.Headers.Keys);
}
[Fact]
public void ApplyResult_NoAllowExposedHeaders_ExposedHeadersHeaderNotAdded()
{
// Arrange
var result = new CorsResult
{
// AllowExposedHeaders is empty by default
};
var httpContext = new DefaultHttpContext();
var service = new CorsService(Mock.Of<IOptions<CorsOptions>>());
// Act
service.ApplyResult(result, httpContext.Response);
// Assert
Assert.DoesNotContain("Access-Control-Expose-Headers", httpContext.Response.Headers.Keys);
}
[Fact]
public void ApplyResult_OneAllowExposedHeaders_ExposedHeadersHeaderAdded()
{
// Arrange
var result = new CorsResult();
result.AllowedExposedHeaders.Add("foo");
var httpContext = new DefaultHttpContext();
var service = new CorsService(Mock.Of<IOptions<CorsOptions>>());
// Act
service.ApplyResult(result, httpContext.Response);
// Assert
Assert.Equal("foo", httpContext.Response.Headers["Access-Control-Expose-Headers"]);
}
[Fact]
public void ApplyResult_ManyAllowExposedHeaders_ExposedHeadersHeaderAdded()
{
// Arrange
var result = new CorsResult();
result.AllowedExposedHeaders.Add("foo");
result.AllowedExposedHeaders.Add("bar");
result.AllowedExposedHeaders.Add("baz");
var httpContext = new DefaultHttpContext();
var service = new CorsService(Mock.Of<IOptions<CorsOptions>>());
// Act
service.ApplyResult(result, httpContext.Response);
// Assert
Assert.Contains("Access-Control-Expose-Headers", httpContext.Response.Headers.Keys);
string[] exposedHeaderValues = httpContext.Response.Headers["Access-Control-Expose-Headers"].Split(',');
Assert.Equal(3, exposedHeaderValues.Length);
Assert.Contains("foo", exposedHeaderValues);
Assert.Contains("bar", exposedHeaderValues);
Assert.Contains("baz", exposedHeaderValues);
}
[Fact]
public void ApplyResult_NoPreflightMaxAge_MaxAgeHeaderNotAdded()
{
// Arrange
var result = new CorsResult
{
PreflightMaxAge = null
};
var httpContext = new DefaultHttpContext();
var service = new CorsService(Mock.Of<IOptions<CorsOptions>>());
// Act
service.ApplyResult(result, httpContext.Response);
// Assert
Assert.DoesNotContain("Access-Control-Max-Age", httpContext.Response.Headers.Keys);
}
[Fact]
public void ApplyResult_PreflightMaxAge_MaxAgeHeaderAdded()
{
// Arrange
var result = new CorsResult
{
PreflightMaxAge = TimeSpan.FromSeconds(30)
};
var httpContext = new DefaultHttpContext();
var service = new CorsService(Mock.Of<IOptions<CorsOptions>>());
// Act
service.ApplyResult(result, httpContext.Response);
// Assert
Assert.Equal("30", httpContext.Response.Headers["Access-Control-Max-Age"]);
}
private static HttpContext GetHttpContext(
string method = null,
string origin = null,
string accessControlRequestMethod = null,
string[] accessControlRequestHeaders = null)
{
var context = new DefaultHttpContext();
if (method != null)
{
context.Request.Method = method;
}
if (origin != null)
{
context.Request.Headers.Add(CorsConstants.Origin, new[] { origin });
}
if (accessControlRequestMethod != null)
{
context.Request.Headers.Add(CorsConstants.AccessControlRequestMethod, new[] { accessControlRequestMethod });
}
if (accessControlRequestHeaders != null)
{
context.Request.Headers.Add(CorsConstants.AccessControlRequestHeaders, accessControlRequestHeaders);
}
return context;
}
}
}

View File

@ -0,0 +1,20 @@
<?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>b4f83a06-eb8e-4186-84c4-c6daf7eb03d4</ProjectGuid>
<RootNamespace>Microsoft.AspNet.Cors.Core.Test</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -0,0 +1,21 @@
{
"version": "1.0.0-*",
"dependencies": {
"Microsoft.AspNet.Cors.Core": "1.0.0-*",
"Microsoft.AspNet.Http.Core": "1.0.0-*",
"Moq": "4.2.1312.1622",
"xunit.runner.kre": "1.0.0-*"
},
"commands": {
"test": "xunit.runner.kre"
},
"frameworks" : {
"dnx451" : {
"dependencies": {
}
}
}
}