diff --git a/CORS.sln b/CORS.sln index 62bed30..f3d81e9 100644 --- a/CORS.sln +++ b/CORS.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.22529.0 +VisualStudioVersion = 14.0.22711.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{84FE6872-A610-4CEC-855F-A84CBF1F40FC}" EndProject @@ -16,6 +16,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{F32074C7-0 EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Cors.Core.Test", "test\Microsoft.AspNet.Cors.Core.Test\Microsoft.AspNet.Cors.Core.Test.xproj", "{B4F83A06-EB8E-4186-84C4-C6DAF7EB03D4}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Cors", "src\Microsoft.AspNet.Cors\Microsoft.AspNet.Cors.xproj", "{41349FCD-D1C4-47A6-82D0-D16D00A8D59D}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Cors.Test", "test\Microsoft.AspNet.Cors.Test\Microsoft.AspNet.Cors.Test.xproj", "{F05BE96F-F869-4408-A480-96935B4835EE}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{EEF80A8E-F334-4C66-9537-8D24D002149D}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "UseOptions", "samples\UseOptions\UseOptions.xproj", "{8DC90D0F-9660-42AD-BE08-4A7643A8F46E}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "UsePolicyBuilder", "samples\UsePolicyBuilder\UsePolicyBuilder.xproj", "{6916DB8A-0246-45F8-9C64-9B05556C1A5D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -30,6 +40,22 @@ Global {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 + {41349FCD-D1C4-47A6-82D0-D16D00A8D59D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {41349FCD-D1C4-47A6-82D0-D16D00A8D59D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {41349FCD-D1C4-47A6-82D0-D16D00A8D59D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {41349FCD-D1C4-47A6-82D0-D16D00A8D59D}.Release|Any CPU.Build.0 = Release|Any CPU + {F05BE96F-F869-4408-A480-96935B4835EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F05BE96F-F869-4408-A480-96935B4835EE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F05BE96F-F869-4408-A480-96935B4835EE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F05BE96F-F869-4408-A480-96935B4835EE}.Release|Any CPU.Build.0 = Release|Any CPU + {8DC90D0F-9660-42AD-BE08-4A7643A8F46E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8DC90D0F-9660-42AD-BE08-4A7643A8F46E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8DC90D0F-9660-42AD-BE08-4A7643A8F46E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8DC90D0F-9660-42AD-BE08-4A7643A8F46E}.Release|Any CPU.Build.0 = Release|Any CPU + {6916DB8A-0246-45F8-9C64-9B05556C1A5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6916DB8A-0246-45F8-9C64-9B05556C1A5D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6916DB8A-0246-45F8-9C64-9B05556C1A5D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6916DB8A-0246-45F8-9C64-9B05556C1A5D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -37,5 +63,9 @@ Global GlobalSection(NestedProjects) = preSolution {C573AEE1-8D54-4A83-8D6B-61C85E8F713E} = {84FE6872-A610-4CEC-855F-A84CBF1F40FC} {B4F83A06-EB8E-4186-84C4-C6DAF7EB03D4} = {F32074C7-087C-46CC-A913-422BFD2D6E0A} + {41349FCD-D1C4-47A6-82D0-D16D00A8D59D} = {84FE6872-A610-4CEC-855F-A84CBF1F40FC} + {F05BE96F-F869-4408-A480-96935B4835EE} = {F32074C7-087C-46CC-A913-422BFD2D6E0A} + {8DC90D0F-9660-42AD-BE08-4A7643A8F46E} = {EEF80A8E-F334-4C66-9537-8D24D002149D} + {6916DB8A-0246-45F8-9C64-9B05556C1A5D} = {EEF80A8E-F334-4C66-9537-8D24D002149D} EndGlobalSection EndGlobal diff --git a/samples/UseOptions/Project_Readme.html b/samples/UseOptions/Project_Readme.html new file mode 100644 index 0000000..b693be4 --- /dev/null +++ b/samples/UseOptions/Project_Readme.html @@ -0,0 +1,204 @@ + + + + + Welcome to ASP.NET 5 + + + + + + +
+
+

This application consists of:

+ +
+ +
+

New concepts

+ +
+ +
+

Customize app

+ +
+ +
+

Deploy

+ +
+ + +
+ + + \ No newline at end of file diff --git a/samples/UseOptions/Startup.cs b/samples/UseOptions/Startup.cs new file mode 100644 index 0000000..5c29d7f --- /dev/null +++ b/samples/UseOptions/Startup.cs @@ -0,0 +1,23 @@ +using System; +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.Http; +using Microsoft.Framework.DependencyInjection; + +namespace UseOptions +{ + public class Startup + { + public void ConfigureServices(IServiceCollection services) + { + services.AddCors(); + services.ConfigureCors( + options => + options.AddPolicy("allowSingleOrigin", builder => builder.WithOrigins("http://example.com"))); + } + + public void Configure(IApplicationBuilder app) + { + app.UseCors("allowSingleOrigin"); + } + } +} diff --git a/samples/UseOptions/UseOptions.xproj b/samples/UseOptions/UseOptions.xproj new file mode 100644 index 0000000..0ad1f53 --- /dev/null +++ b/samples/UseOptions/UseOptions.xproj @@ -0,0 +1,19 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 8dc90d0f-9660-42ad-be08-4a7643a8f46e + UseOptions + ..\artifacts\obj\$(MSBuildProjectName) + ..\artifacts\bin\$(MSBuildProjectName)\ + + + 2.0 + 5442 + + + \ No newline at end of file diff --git a/samples/UseOptions/project.json b/samples/UseOptions/project.json new file mode 100644 index 0000000..6bcdc93 --- /dev/null +++ b/samples/UseOptions/project.json @@ -0,0 +1,30 @@ +{ + "webroot": "wwwroot", + "version": "1.0.0-*", + "dependencies": { + "Microsoft.AspNet.Cors": "1.0.0-*", + "Microsoft.AspNet.Diagnostics": "1.0.0-*", + "Microsoft.AspNet.Server.IIS": "1.0.0-*", + "Microsoft.AspNet.Server.WebListener": "1.0.0-*", + "Microsoft.Framework.Logging.Console": "1.0.0-*" + }, + "commands": { + "web": "Microsoft.AspNet.Hosting --server Microsoft.AspNet.Server.WebListener --server.urls http://localhost:5000" + }, + "frameworks": { + "dnx451": {}, + "dnxcore50": {} + }, + "bundleExclude": [ + "node_modules", + "bower_components", + "**.kproj", + "**.user", + "**.vspscc" + ], + "exclude": [ + "wwwroot", + "node_modules", + "bower_components" + ] +} \ No newline at end of file diff --git a/samples/UsePolicyBuilder/Project_Readme.html b/samples/UsePolicyBuilder/Project_Readme.html new file mode 100644 index 0000000..b693be4 --- /dev/null +++ b/samples/UsePolicyBuilder/Project_Readme.html @@ -0,0 +1,204 @@ + + + + + Welcome to ASP.NET 5 + + + + + + +
+
+

This application consists of:

+ +
+ +
+

New concepts

+ +
+ +
+

Customize app

+ +
+ +
+

Deploy

+ +
+ + +
+ + + \ No newline at end of file diff --git a/samples/UsePolicyBuilder/Startup.cs b/samples/UsePolicyBuilder/Startup.cs new file mode 100644 index 0000000..de8233c --- /dev/null +++ b/samples/UsePolicyBuilder/Startup.cs @@ -0,0 +1,21 @@ +using System; +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.Cors.Core; +using Microsoft.AspNet.Http; +using Microsoft.Framework.DependencyInjection; + +namespace UsePolicy +{ + public class Startup + { + public void ConfigureServices(IServiceCollection services) + { + services.AddCors(); + } + + public void Configure(IApplicationBuilder app) + { + app.UseCors(policy => policy.WithOrigins("http://example.com")); + } + } +} diff --git a/samples/UsePolicyBuilder/UsePolicyBuilder.xproj b/samples/UsePolicyBuilder/UsePolicyBuilder.xproj new file mode 100644 index 0000000..cf8bbbd --- /dev/null +++ b/samples/UsePolicyBuilder/UsePolicyBuilder.xproj @@ -0,0 +1,19 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 6916db8a-0246-45f8-9c64-9b05556c1a5d + UseOptions + ..\artifacts\obj\$(MSBuildProjectName) + ..\artifacts\bin\$(MSBuildProjectName)\ + + + 2.0 + 12497 + + + \ No newline at end of file diff --git a/samples/UsePolicyBuilder/project.json b/samples/UsePolicyBuilder/project.json new file mode 100644 index 0000000..6bcdc93 --- /dev/null +++ b/samples/UsePolicyBuilder/project.json @@ -0,0 +1,30 @@ +{ + "webroot": "wwwroot", + "version": "1.0.0-*", + "dependencies": { + "Microsoft.AspNet.Cors": "1.0.0-*", + "Microsoft.AspNet.Diagnostics": "1.0.0-*", + "Microsoft.AspNet.Server.IIS": "1.0.0-*", + "Microsoft.AspNet.Server.WebListener": "1.0.0-*", + "Microsoft.Framework.Logging.Console": "1.0.0-*" + }, + "commands": { + "web": "Microsoft.AspNet.Hosting --server Microsoft.AspNet.Server.WebListener --server.urls http://localhost:5000" + }, + "frameworks": { + "dnx451": {}, + "dnxcore50": {} + }, + "bundleExclude": [ + "node_modules", + "bower_components", + "**.kproj", + "**.user", + "**.vspscc" + ], + "exclude": [ + "wwwroot", + "node_modules", + "bower_components" + ] +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Cors.Core/CorsOptions.cs b/src/Microsoft.AspNet.Cors.Core/CorsOptions.cs index 7d76181..0a94c70 100644 --- a/src/Microsoft.AspNet.Cors.Core/CorsOptions.cs +++ b/src/Microsoft.AspNet.Cors.Core/CorsOptions.cs @@ -13,8 +13,27 @@ namespace Microsoft.AspNet.Cors.Core /// public class CorsOptions { + private string _defaultPolicyName = "__DefaultCorsPolicy"; private IDictionary PolicyMap { get; } = new Dictionary(); + public string DefaultPolicyName + { + get + { + return _defaultPolicyName; + } + + set + { + if (value == null) + { + throw new ArgumentNullException("value"); + } + + _defaultPolicyName = value; + } + } + /// /// Adds a new policy. /// diff --git a/src/Microsoft.AspNet.Cors.Core/CorsPolicyBuilder.cs b/src/Microsoft.AspNet.Cors.Core/CorsPolicyBuilder.cs index 8330300..b9dd7d9 100644 --- a/src/Microsoft.AspNet.Cors.Core/CorsPolicyBuilder.cs +++ b/src/Microsoft.AspNet.Cors.Core/CorsPolicyBuilder.cs @@ -21,7 +21,7 @@ namespace Microsoft.AspNet.Cors /// list of origins which can be added. public CorsPolicyBuilder(params string[] origins) { - AddOrigins(origins); + WithOrigins(origins); } /// @@ -38,7 +38,7 @@ namespace Microsoft.AspNet.Cors /// /// The origins that are allowed. /// The current policy builder - public CorsPolicyBuilder AddOrigins(params string[] origins) + public CorsPolicyBuilder WithOrigins(params string[] origins) { foreach (var req in origins) { @@ -53,7 +53,7 @@ namespace Microsoft.AspNet.Cors /// /// The headers which need to be allowed in the request. /// The current policy builder - public CorsPolicyBuilder AddHeaders(params string[] headers) + public CorsPolicyBuilder WithHeaders(params string[] headers) { foreach (var req in headers) { @@ -67,7 +67,7 @@ namespace Microsoft.AspNet.Cors /// /// The headers which need to be exposed to the client. /// The current policy builder - public CorsPolicyBuilder AddExposedHeaders(params string[] exposedHeaders) + public CorsPolicyBuilder WithExposedHeaders(params string[] exposedHeaders) { foreach (var req in exposedHeaders) { @@ -82,7 +82,7 @@ namespace Microsoft.AspNet.Cors /// /// The methods which need to be added to the policy. /// The current policy builder - public CorsPolicyBuilder AddMethods(params string[] methods) + public CorsPolicyBuilder WithMethods(params string[] methods) { foreach (var req in methods) { @@ -173,10 +173,10 @@ namespace Microsoft.AspNet.Cors /// The current policy builder private CorsPolicyBuilder Combine([NotNull] CorsPolicy policy) { - AddOrigins(policy.Origins.ToArray()); - AddHeaders(policy.Headers.ToArray()); - AddExposedHeaders(policy.ExposedHeaders.ToArray()); - AddMethods(policy.Methods.ToArray()); + WithOrigins(policy.Origins.ToArray()); + WithHeaders(policy.Headers.ToArray()); + WithExposedHeaders(policy.ExposedHeaders.ToArray()); + WithMethods(policy.Methods.ToArray()); SetPreflightMaxAge(policy.PreflightMaxAge.Value); if (policy.SupportsCredentials) diff --git a/src/Microsoft.AspNet.Cors.Core/CorsServiceCollectionExtensions.cs b/src/Microsoft.AspNet.Cors.Core/CorsServiceCollectionExtensions.cs index adb38d1..544b426 100644 --- a/src/Microsoft.AspNet.Cors.Core/CorsServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNet.Cors.Core/CorsServiceCollectionExtensions.cs @@ -36,6 +36,7 @@ namespace Microsoft.Framework.DependencyInjection { serviceCollection.AddOptions(); serviceCollection.AddTransient(); + serviceCollection.AddTransient(); return serviceCollection; } } diff --git a/src/Microsoft.AspNet.Cors.Core/DefaultCorsPolicyProvider.cs b/src/Microsoft.AspNet.Cors.Core/DefaultCorsPolicyProvider.cs new file mode 100644 index 0000000..307744e --- /dev/null +++ b/src/Microsoft.AspNet.Cors.Core/DefaultCorsPolicyProvider.cs @@ -0,0 +1,31 @@ +// 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.Threading.Tasks; +using Microsoft.AspNet.Http; +using Microsoft.Framework.Internal; +using Microsoft.Framework.OptionsModel; + +namespace Microsoft.AspNet.Cors.Core +{ + /// + public class DefaultCorsPolicyProvider : ICorsPolicyProvider + { + private readonly CorsOptions _options; + + /// + /// Creates a new instance of . + /// + /// The options configured for the application. + public DefaultCorsPolicyProvider(IOptions options) + { + _options = options.Options; + } + + /// + public Task GetPolicyAsync(HttpContext context, string policyName) + { + return Task.FromResult(_options.GetPolicy(policyName ?? _options.DefaultPolicyName)); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Cors.Core/ICorsPolicyProvider.cs b/src/Microsoft.AspNet.Cors.Core/ICorsPolicyProvider.cs new file mode 100644 index 0000000..b837230 --- /dev/null +++ b/src/Microsoft.AspNet.Cors.Core/ICorsPolicyProvider.cs @@ -0,0 +1,23 @@ +// 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.Threading.Tasks; +using Microsoft.AspNet.Http; +using Microsoft.Framework.Internal; + +namespace Microsoft.AspNet.Cors.Core +{ + /// + /// A type which can provide a for a particular . + /// + public interface ICorsPolicyProvider + { + /// + /// Gets a from the given + /// + /// The associated with this call. + /// An optional policy name to look for. + /// A + Task GetPolicyAsync([NotNull] HttpContext context, string policyName); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Cors.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Cors.Core/Properties/Resources.Designer.cs index f50fb00..8b6e085 100644 --- a/src/Microsoft.AspNet.Cors.Core/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Cors.Core/Properties/Resources.Designer.cs @@ -10,70 +10,6 @@ namespace Microsoft.AspNet.Cors.Core private static readonly ResourceManager _resourceManager = new ResourceManager("Microsoft.AspNet.Cors.Core.Resources", typeof(Resources).GetTypeInfo().Assembly); - /// - /// The collection of headers '{0}' is not allowed. - /// - internal static string HeadersNotAllowed - { - get { return GetString("HeadersNotAllowed"); } - } - - /// - /// The collection of headers '{0}' is not allowed. - /// - internal static string FormatHeadersNotAllowed(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("HeadersNotAllowed"), p0); - } - - /// - /// The method '{0}' is not allowed. - /// - internal static string MethodNotAllowed - { - get { return GetString("MethodNotAllowed"); } - } - - /// - /// The method '{0}' is not allowed. - /// - internal static string FormatMethodNotAllowed(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("MethodNotAllowed"), p0); - } - - /// - /// The request does not contain the Origin header. - /// - internal static string NoOriginHeader - { - get { return GetString("NoOriginHeader"); } - } - - /// - /// The request does not contain the Origin header. - /// - internal static string FormatNoOriginHeader() - { - return GetString("NoOriginHeader"); - } - - /// - /// The origin '{0}' is not allowed. - /// - internal static string OriginNotAllowed - { - get { return GetString("OriginNotAllowed"); } - } - - /// - /// The origin '{0}' is not allowed. - /// - internal static string FormatOriginNotAllowed(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("OriginNotAllowed"), p0); - } - /// /// PreflightMaxAge must be greater than or equal to 0. /// diff --git a/src/Microsoft.AspNet.Cors.Core/Resources.resx b/src/Microsoft.AspNet.Cors.Core/Resources.resx index ef0e5b5..6b9ebaa 100644 --- a/src/Microsoft.AspNet.Cors.Core/Resources.resx +++ b/src/Microsoft.AspNet.Cors.Core/Resources.resx @@ -117,19 +117,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - The collection of headers '{0}' is not allowed. - - - The method '{0}' is not allowed. - - - The request does not contain the Origin header. - - - The origin '{0}' is not allowed. - - + PreflightMaxAge must be greater than or equal to 0. \ No newline at end of file diff --git a/src/Microsoft.AspNet.Cors/CorsMiddleware.cs b/src/Microsoft.AspNet.Cors/CorsMiddleware.cs new file mode 100644 index 0000000..21c079e --- /dev/null +++ b/src/Microsoft.AspNet.Cors/CorsMiddleware.cs @@ -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. + +using System; +using System.Threading.Tasks; +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.Cors.Core; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.WebUtilities; +using Microsoft.Framework.Internal; +using Microsoft.Framework.OptionsModel; + +namespace Microsoft.AspNet.Cors +{ + /// + /// An ASP.NET middleware for handling CORS. + /// + public class CorsMiddleware + { + private readonly RequestDelegate _next; + private readonly ICorsService _corsService; + private readonly ICorsPolicyProvider _corsPolicyProvider; + private readonly CorsPolicy _policy; + private readonly string _corsPolicyName; + + /// + /// Instantiates a new . + /// + /// The next middleware in the pipeline. + /// An instance of . + /// A policy provider which can get an . + /// An optional name of the policy to be fetched. + public CorsMiddleware( + [NotNull] RequestDelegate next, + [NotNull] ICorsService corsService, + [NotNull] ICorsPolicyProvider policyProvider, + string policyName) + { + _next = next; + _corsService = corsService; + _corsPolicyProvider = policyProvider; + _corsPolicyName = policyName; + } + + /// + /// Instantiates a new . + /// + /// The next middleware in the pipeline. + /// An instance of . + /// An instance of the which can be applied. + public CorsMiddleware( + [NotNull] RequestDelegate next, + [NotNull] ICorsService corsService, + [NotNull] CorsPolicy policy) + { + _next = next; + _corsService = corsService; + _policy = policy; + } + + /// + public async Task Invoke(HttpContext context) + { + if (context.Request.Headers.ContainsKey(CorsConstants.Origin)) + { + var corsPolicy = _policy ?? await _corsPolicyProvider?.GetPolicyAsync(context, _corsPolicyName); + if (corsPolicy != null) + { + var corsResult = _corsService.EvaluatePolicy(context, corsPolicy); + _corsService.ApplyResult(corsResult, context.Response); + + var accessControlRequestMethod = + context.Request.Headers.Get(CorsConstants.AccessControlRequestMethod); + if (string.Equals( + context.Request.Method, + CorsConstants.PreflightHttpMethod, + StringComparison.Ordinal) && + accessControlRequestMethod != null) + { + // Since there is a policy which was identified, + // always respond to preflight requests. + context.Response.StatusCode = StatusCodes.Status204NoContent; + return; + } + } + } + + await _next(context); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Cors/CorsMiddlewareExtensions.cs b/src/Microsoft.AspNet.Cors/CorsMiddlewareExtensions.cs new file mode 100644 index 0000000..88e529d --- /dev/null +++ b/src/Microsoft.AspNet.Cors/CorsMiddlewareExtensions.cs @@ -0,0 +1,43 @@ +// 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; +using Microsoft.AspNet.Cors.Core; +using Microsoft.Framework.Internal; + +namespace Microsoft.AspNet.Builder +{ + /// + /// The extensions for adding CORS middleware support. + /// + public static class CorsMiddlewareExtensions + { + /// + /// Adds a CORS middleware to your web application pipeline to allow cross domain requests. + /// + /// The IApplicationBuilder passed to your Configure method + /// The policy name of a configured policy. + /// The original app parameter + public static IApplicationBuilder UseCors([NotNull]this IApplicationBuilder app, string policyName) + { + return app.UseMiddleware(policyName); + } + + /// + /// Adds a CORS middleware to your web application pipeline to allow cross domain requests. + /// + /// The IApplicationBuilder passed to your Configure method. + /// A delegate which can use a policy builder to build a policy. + /// The original app parameter + public static IApplicationBuilder UseCors( + [NotNull] this IApplicationBuilder app, + [NotNull] Action configurePolicy) + { + var policyBuilder = new CorsPolicyBuilder(); + configurePolicy(policyBuilder); + return app.UseMiddleware(policyBuilder.Build()); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Cors/Microsoft.AspNet.Cors.xproj b/src/Microsoft.AspNet.Cors/Microsoft.AspNet.Cors.xproj new file mode 100644 index 0000000..34f1a25 --- /dev/null +++ b/src/Microsoft.AspNet.Cors/Microsoft.AspNet.Cors.xproj @@ -0,0 +1,20 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + 41349fcd-d1c4-47a6-82d0-d16d00a8d59d + Microsoft.AspNet.Cors + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\bin\$(MSBuildProjectName)\ + + + + 2.0 + + + \ No newline at end of file diff --git a/src/Microsoft.AspNet.Cors/project.json b/src/Microsoft.AspNet.Cors/project.json new file mode 100644 index 0000000..bf9e28e --- /dev/null +++ b/src/Microsoft.AspNet.Cors/project.json @@ -0,0 +1,21 @@ +{ + "version": "1.0.0-*", + "dependencies": { + "Microsoft.AspNet.Cors.Core": "1.0.0-*", + "Microsoft.AspNet.RequestContainer": "1.0.0-*", + "Microsoft.AspNet.WebUtilities": "1.0.0-*", + "Microsoft.Framework.NotNullAttribute.Internal": { "version": "1.0.0-*", "type": "build" } + }, + + "frameworks" : { + "dnx451" : { + "dependencies": { + } + }, + "dnxcore50" : { + "dependencies": { + "System.Runtime": "4.0.20-beta-*" + } + } + } +} diff --git a/test/Microsoft.AspNet.Cors.Core.Test/CorsPolicyBuilderTests.cs b/test/Microsoft.AspNet.Cors.Core.Test/CorsPolicyBuilderTests.cs index 3a7a722..2b6d424 100644 --- a/test/Microsoft.AspNet.Cors.Core.Test/CorsPolicyBuilderTests.cs +++ b/test/Microsoft.AspNet.Cors.Core.Test/CorsPolicyBuilderTests.cs @@ -83,13 +83,13 @@ namespace Microsoft.AspNet.Cors.Core.Test } [Fact] - public void AddOrigins_AddsOrigins() + public void WithOrigins_AddsOrigins() { // Arrange var builder = new CorsPolicyBuilder(); // Act - builder.AddOrigins("http://example.com", "http://example2.com"); + builder.WithOrigins("http://example.com", "http://example2.com"); // Assert var corsPolicy = builder.Build(); @@ -114,13 +114,13 @@ namespace Microsoft.AspNet.Cors.Core.Test [Fact] - public void AddMethods_AddsMethods() + public void WithMethods_AddsMethods() { // Arrange var builder = new CorsPolicyBuilder(); // Act - builder.AddMethods("PUT", "GET"); + builder.WithMethods("PUT", "GET"); // Assert var corsPolicy = builder.Build(); @@ -144,13 +144,13 @@ namespace Microsoft.AspNet.Cors.Core.Test } [Fact] - public void AddHeaders_AddsHeaders() + public void WithHeaders_AddsHeaders() { // Arrange var builder = new CorsPolicyBuilder(); // Act - builder.AddHeaders("example1", "example2"); + builder.WithHeaders("example1", "example2"); // Assert var corsPolicy = builder.Build(); @@ -174,13 +174,13 @@ namespace Microsoft.AspNet.Cors.Core.Test } [Fact] - public void AddExposedHeaders_AddsExposedHeaders() + public void WithExposedHeaders_AddsExposedHeaders() { // Arrange var builder = new CorsPolicyBuilder(); // Act - builder.AddExposedHeaders("exposed1", "exposed2"); + builder.WithExposedHeaders("exposed1", "exposed2"); // Assert var corsPolicy = builder.Build(); diff --git a/test/Microsoft.AspNet.Cors.Core.Test/DefaultCorsPolicyProviderTests.cs b/test/Microsoft.AspNet.Cors.Core.Test/DefaultCorsPolicyProviderTests.cs new file mode 100644 index 0000000..a04297b --- /dev/null +++ b/test/Microsoft.AspNet.Cors.Core.Test/DefaultCorsPolicyProviderTests.cs @@ -0,0 +1,58 @@ +// 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.Threading.Tasks; +using Microsoft.AspNet.Http.Core; +using Microsoft.Framework.OptionsModel; +using Moq; +using Xunit; + +namespace Microsoft.AspNet.Cors.Core.Test +{ + public class DefaultPolicyProviderTests + { + [Fact] + public async Task UsesTheDefaultPolicyName() + { + // Arrange + var options = new CorsOptions(); + var policy = new CorsPolicy(); + options.AddPolicy(options.DefaultPolicyName, policy); + + var mockOptions = new Mock>(); + mockOptions + .SetupGet(o => o.Options) + .Returns(options); + var policyProvider = new DefaultCorsPolicyProvider(mockOptions.Object); + + // Act + var actualPolicy = await policyProvider.GetPolicyAsync(new DefaultHttpContext(), policyName: null); + + // Assert + Assert.Same(policy, actualPolicy); + } + + [Theory] + [InlineData("")] + [InlineData("policyName")] + public async Task GetsNamedPolicy(string policyName) + { + // Arrange + var options = new CorsOptions(); + var policy = new CorsPolicy(); + options.AddPolicy(policyName, policy); + + var mockOptions = new Mock>(); + mockOptions + .SetupGet(o => o.Options) + .Returns(options); + var policyProvider = new DefaultCorsPolicyProvider(mockOptions.Object); + + // Act + var actualPolicy = await policyProvider.GetPolicyAsync(new DefaultHttpContext(), policyName); + + // Assert + Assert.Same(policy, actualPolicy); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Cors.Core.Test/project.json b/test/Microsoft.AspNet.Cors.Core.Test/project.json index 3391960..4c01305 100644 --- a/test/Microsoft.AspNet.Cors.Core.Test/project.json +++ b/test/Microsoft.AspNet.Cors.Core.Test/project.json @@ -5,11 +5,11 @@ "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-*" + "xunit.runner.aspnet": "2.0.0-aspnet-*" }, "commands": { - "test": "xunit.runner.kre" + "test": "xunit.runner.aspnet" }, "frameworks" : { diff --git a/test/Microsoft.AspNet.Cors.Test/CorsMiddlewareTests.cs b/test/Microsoft.AspNet.Cors.Test/CorsMiddlewareTests.cs new file mode 100644 index 0000000..fc7da7b --- /dev/null +++ b/test/Microsoft.AspNet.Cors.Test/CorsMiddlewareTests.cs @@ -0,0 +1,214 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.Cors.Core; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.Http.Core; +using Microsoft.AspNet.TestHost; +using Microsoft.Framework.DependencyInjection; +using Moq; +using Xunit; + +namespace Microsoft.AspNet.Cors.Test +{ + public class CorsMiddlewareTests + { + [Fact] + public async Task CorsRequest_MatchPolicy_SetsResponseHeaders() + { + // Arrange + using (var server = TestServer.Create(app => + { + app.UseServices(services => services.AddCors()); + app.UseCors(builder => + builder.WithOrigins("http://localhost:5001") + .WithMethods("PUT") + .WithHeaders("Header1") + .WithExposedHeaders("AllowedHeader")); + app.Run(async context => + { + await context.Response.WriteAsync("Cross origin response"); + }); + })) + { + // Act + // Actual request. + var response = await server.CreateRequest("/") + .AddHeader(CorsConstants.Origin, "http://localhost:5001") + .SendAsync("PUT"); + + // Assert + response.EnsureSuccessStatusCode(); + Assert.Equal(2, response.Headers.Count()); + Assert.Equal("Cross origin response", await response.Content.ReadAsStringAsync()); + Assert.Equal("http://localhost:5001", response.Headers.GetValues(CorsConstants.AccessControlAllowOrigin).FirstOrDefault()); + Assert.Equal("AllowedHeader", response.Headers.GetValues(CorsConstants.AccessControlExposeHeaders).FirstOrDefault()); + } + } + + [Fact] + public async Task PreFlight_MatchesPolicy_SetsResponseHeaders() + { + // Arrange + var policy = new CorsPolicy(); + policy.Origins.Add("http://localhost:5001"); + policy.Methods.Add("PUT"); + policy.Headers.Add("Header1"); + policy.ExposedHeaders.Add("AllowedHeader"); + + using (var server = TestServer.Create(app => + { + app.UseServices(services => + { + services.AddCors(); + services.ConfigureCors(options => + { + options.AddPolicy("customPolicy", policy); + }); + }); + app.UseCors("customPolicy"); + app.Run(async context => + { + await context.Response.WriteAsync("Cross origin response"); + }); + })) + { + // Act + // Preflight request. + var response = await server.CreateRequest("/") + .AddHeader(CorsConstants.Origin, "http://localhost:5001") + .AddHeader(CorsConstants.AccessControlRequestMethod, "PUT") + .SendAsync(CorsConstants.PreflightHttpMethod); + + // Assert + response.EnsureSuccessStatusCode(); + Assert.Equal(2, response.Headers.Count()); + Assert.Equal("http://localhost:5001", response.Headers.GetValues(CorsConstants.AccessControlAllowOrigin).FirstOrDefault()); + Assert.Equal("PUT", response.Headers.GetValues(CorsConstants.AccessControlAllowMethods).FirstOrDefault()); + } + } + + [Fact] + public async Task PreFlightRequest_DoesNotMatchPolicy_DoesNotSetHeaders() + { + // Arrange + using (var server = TestServer.Create(app => + { + app.UseServices(services => services.AddCors()); + app.UseCors(builder => + builder.WithOrigins("http://localhost:5001") + .WithMethods("PUT") + .WithHeaders("Header1") + .WithExposedHeaders("AllowedHeader")); + app.Run(async context => + { + await context.Response.WriteAsync("Cross origin response"); + }); + })) + { + // Act + // Preflight request. + var response = await server.CreateRequest("/") + .AddHeader(CorsConstants.Origin, "http://localhost:5002") + .AddHeader(CorsConstants.AccessControlRequestMethod, "PUT") + .SendAsync(CorsConstants.PreflightHttpMethod); + + // Assert + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + Assert.Empty(response.Headers); + } + } + + [Fact] + public async Task CorsRequest_DoesNotMatchPolicy_DoesNotSetHeaders() + { + // Arrange + using (var server = TestServer.Create(app => + { + app.UseServices(services => services.AddCors()); + app.UseCors(builder => + builder.WithOrigins("http://localhost:5001") + .WithMethods("PUT") + .WithHeaders("Header1") + .WithExposedHeaders("AllowedHeader")); + app.Run(async context => + { + await context.Response.WriteAsync("Cross origin response"); + }); + })) + { + // Act + // Actual request. + var response = await server.CreateRequest("/") + .AddHeader(CorsConstants.Origin, "http://localhost:5002") + .SendAsync("PUT"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Empty(response.Headers); + } + } + + [Fact] + public async Task Uses_PolicyProvider_AsFallback() + { + // Arrange + var corsService = Mock.Of(); + var mockProvider = new Mock(); + mockProvider.Setup(o => o.GetPolicyAsync(It.IsAny(), It.IsAny())) + .Returns(Task.FromResult(null)) + .Verifiable(); + + var middleware = new CorsMiddleware( + Mock.Of(), + corsService, + mockProvider.Object, + policyName: null); + + var httpContext = new DefaultHttpContext(); + httpContext.Request.Headers.Add(CorsConstants.Origin, new[] { "http://example.com" }); + + // Act + await middleware.Invoke(httpContext); + + // Assert + mockProvider.Verify( + o => o.GetPolicyAsync(It.IsAny(), It.IsAny()), + Times.Once); + } + + [Fact] + public async Task DoesNotSetHeaders_ForNoPolicy() + { + // Arrange + var corsService = Mock.Of(); + var mockProvider = new Mock(); + mockProvider.Setup(o => o.GetPolicyAsync(It.IsAny(), It.IsAny())) + .Returns(Task.FromResult(null)) + .Verifiable(); + + var middleware = new CorsMiddleware( + Mock.Of(), + corsService, + mockProvider.Object, + policyName: null); + + var httpContext = new DefaultHttpContext(); + httpContext.Request.Headers.Add(CorsConstants.Origin, new[] { "http://example.com" }); + + // Act + await middleware.Invoke(httpContext); + + // Assert + Assert.Equal(200, httpContext.Response.StatusCode); + Assert.Empty(httpContext.Response.Headers); + mockProvider.Verify( + o => o.GetPolicyAsync(It.IsAny(), It.IsAny()), + Times.Once); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Cors.Test/Microsoft.AspNet.Cors.Test.xproj b/test/Microsoft.AspNet.Cors.Test/Microsoft.AspNet.Cors.Test.xproj new file mode 100644 index 0000000..e1e0c40 --- /dev/null +++ b/test/Microsoft.AspNet.Cors.Test/Microsoft.AspNet.Cors.Test.xproj @@ -0,0 +1,20 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + f05be96f-f869-4408-a480-96935b4835ee + Microsoft.AspNet.Cors.Test + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\bin\$(MSBuildProjectName)\ + + + + 2.0 + + + \ No newline at end of file diff --git a/test/Microsoft.AspNet.Cors.Test/project.json b/test/Microsoft.AspNet.Cors.Test/project.json new file mode 100644 index 0000000..a75b306 --- /dev/null +++ b/test/Microsoft.AspNet.Cors.Test/project.json @@ -0,0 +1,17 @@ +{ + "version": "1.0.0-*", + "dependencies": { + "Microsoft.AspNet.Cors": "1.0.0-*", + "Microsoft.AspNet.TestHost": "1.0.0-*", + "Moq": "4.2.1312.1622", + "xunit.runner.aspnet": "2.0.0-aspnet-*" + }, + "commands": { + "test": "xunit.runner.aspnet" + }, + "frameworks": { + "dnx451": { + "dependencies": {} + } + } +} \ No newline at end of file