Merge pull request #95 from aspnet/jbagga/Logging78

Addresses #69 and #78
This commit is contained in:
Jass Bagga 2016-11-23 14:18:58 -08:00 committed by GitHub
commit 52ba62c4d1
15 changed files with 708 additions and 11 deletions

View File

@ -1,7 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.24720.0
VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{84FE6872-A610-4CEC-855F-A84CBF1F40FC}"
EndProject
@ -20,6 +19,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebSites", "WebSites", "{53
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CorsMiddlewareWebSite", "test\WebSites\CorsMiddlewareWebSite\CorsMiddlewareWebSite.xproj", "{B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{960E0703-A8A5-44DF-AA87-B7C614683B3C}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "SampleDestination", "samples\SampleDestination\SampleDestination.xproj", "{F6675DC1-AA21-453B-89B6-DA425FB9C3A5}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "SampleOrigin", "samples\SampleOrigin\SampleOrigin.xproj", "{99460370-AE5D-4DC9-8DBF-04DF66D6B21D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -38,6 +43,14 @@ Global
{B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}.Release|Any CPU.Build.0 = Release|Any CPU
{F6675DC1-AA21-453B-89B6-DA425FB9C3A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F6675DC1-AA21-453B-89B6-DA425FB9C3A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F6675DC1-AA21-453B-89B6-DA425FB9C3A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F6675DC1-AA21-453B-89B6-DA425FB9C3A5}.Release|Any CPU.Build.0 = Release|Any CPU
{99460370-AE5D-4DC9-8DBF-04DF66D6B21D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{99460370-AE5D-4DC9-8DBF-04DF66D6B21D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{99460370-AE5D-4DC9-8DBF-04DF66D6B21D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{99460370-AE5D-4DC9-8DBF-04DF66D6B21D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -47,5 +60,7 @@ Global
{F05BE96F-F869-4408-A480-96935B4835EE} = {F32074C7-087C-46CC-A913-422BFD2D6E0A}
{538380BF-0D4C-4E30-8F41-E75C4B1C01FA} = {F32074C7-087C-46CC-A913-422BFD2D6E0A}
{B42D4844-FFF8-4EC2-88D1-3AE95234D9EB} = {538380BF-0D4C-4E30-8F41-E75C4B1C01FA}
{F6675DC1-AA21-453B-89B6-DA425FB9C3A5} = {960E0703-A8A5-44DF-AA87-B7C614683B3C}
{99460370-AE5D-4DC9-8DBF-04DF66D6B21D} = {960E0703-A8A5-44DF-AA87-B7C614683B3C}
EndGlobalSection
EndGlobal

38
samples/README.md Normal file
View File

@ -0,0 +1,38 @@
# CORS Sample
This sample consists of a request origin (SampleOrigin) and a request destination (SampleDestination). Both have different domain names, to simulate a CORS request.
## Modify Hosts File
To run this CORS sample, modify the hosts file to register the hostnames `destination.example.com` and `origin.example.com`.
### Windows:
Run a text editor (e.g. Notepad) as an Administrator. Open the hosts file on the path: "C:\Windows\System32\drivers\etc\hosts".
### Linux:
On a Terminal window, type "sudo nano /etc/hosts" and enter your admin password when prompted.
In the hosts file, add the following to the bottom of the file:
```
127.0.0.1 destination.example.com
127.0.0.1 origin.example.com
```
Save the file and close it. Then clear your browser history.
## Run the sample
The SampleOrigin application will use port 5001, and SampleDestination will use 5000. Please ensure there are no other processes using those ports before running the CORS sample.
* In a command prompt window, open the directory where you cloned the repository, and open the SampleDestination directory. Run the command: dotnet run
* Repeat the above step in the SampleOrigin directory
* Open a browser window and go to `http://origin.example.com:5001`
* Input a method and header to create a CORS request or use one of the example buttons to see CORS in action
As an example, apart from `GET`, `HEAD` and `POST` requests, `PUT` requests are allowed in the CORS policy on SampleDestination. Any others, like `DELETE`, `OPTIONS` etc. are not allowed and throw an error.
`Cache-Control` has been added as an allowed header to the sample. Any other headers are not allowed and throw an error. You may leave the header name and value blank.
To edit the policy, please see `app.UseCors()` method in the `Startup.cs` file of SampleDestination.
**If using Visual Studio to launch the request origin:**
Open Visual Studio and in the `launchSettings.json` file for the SampleOrigin project, change the `launchUrl` under SampleOrigin to `http://origin.example.com:5001`.
Using the dropdown near the Start button, choose SampleOrigin before pressing Start to ensure that it uses Kestrel and not IIS Express.

View File

@ -0,0 +1,23 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.IO;
using Microsoft.AspNetCore.Hosting;
namespace SampleDestination
{
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseUrls("http://*:5000")
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
.Build();
host.Run();
}
}
}

View File

@ -0,0 +1,25 @@
<?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)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>f6675dc1-aa21-453b-89b6-da425fb9c3a5</ProjectGuid>
<RootNamespace>SampleDestination</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<ItemGroup>
<DnxInvisibleContent Include="bower.json" />
<DnxInvisibleContent Include=".bowerrc" />
</ItemGroup>
<Import Project="$(VSToolsPath)\DotNet.Web\Microsoft.DotNet.Web.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -0,0 +1,41 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace SampleDestination
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddCors();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
app.UseCors(policy => policy
.WithOrigins("http://origin.example.com:5001")
.WithMethods("PUT")
.WithHeaders("Cache-Control"));
app.Run(async context =>
{
var responseHeaders = context.Response.Headers;
context.Response.ContentType = "text/plain";
foreach (var responseHeader in responseHeaders)
{
await context.Response.WriteAsync("\n" + responseHeader.Key + ": " + responseHeader.Value);
}
await context.Response.WriteAsync("\nStatus code of your request: " + context.Response.StatusCode.ToString());
});
}
}
}

View File

@ -0,0 +1,37 @@
{
"dependencies": {
"Microsoft.NETCore.App": {
"version": "1.1.0-*",
"type": "platform"
},
"Microsoft.AspNetCore.Server.Kestrel": "1.2.0-*",
"Microsoft.Extensions.Logging.Console": "1.2.0-*",
"Microsoft.AspNetCore.Cors": "1.2.0-*"
},
"frameworks": {
"netcoreapp1.0": {
"imports": [
"dotnet5.6",
"portable-net45+win8"
]
}
},
"buildOptions": {
"emitEntryPoint": true,
"preserveCompilationContext": true
},
"runtimeOptions": {
"configProperties": {
"System.GC.Server": true
}
},
"publishOptions": {
"include": [
"wwwroot"
]
}
}

View File

@ -0,0 +1,23 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.IO;
using Microsoft.AspNetCore.Hosting;
namespace SampleOrigin
{
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseUrls("http://*:5001")
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
.Build();
host.Run();
}
}
}

View File

@ -0,0 +1,25 @@
<?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)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>99460370-ae5d-4dc9-8dbf-04df66d6b21d</ProjectGuid>
<RootNamespace>SampleOrigin</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<ItemGroup>
<DnxInvisibleContent Include="bower.json" />
<DnxInvisibleContent Include=".bowerrc" />
</ItemGroup>
<Import Project="$(VSToolsPath)\DotNet.Web\Microsoft.DotNet.Web.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -0,0 +1,30 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace SampleOrigin
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
app.Run(context =>
{
var fileInfoProvider = env.WebRootFileProvider;
var fileInfo = fileInfoProvider.GetFileInfo("/Index.html");
context.Response.ContentType = "text/html";
return context.Response.SendFileAsync(fileInfo);
});
}
}
}

View File

@ -0,0 +1,36 @@
{
"dependencies": {
"Microsoft.NETCore.App": {
"version": "1.1.0-*",
"type": "platform"
},
"Microsoft.AspNetCore.Server.Kestrel": "1.2.0-*",
"Microsoft.Extensions.Logging.Console": "1.2.0-*"
},
"frameworks": {
"netcoreapp1.0": {
"imports": [
"dotnet5.6",
"portable-net45+win8"
]
}
},
"buildOptions": {
"emitEntryPoint": true,
"preserveCompilationContext": true
},
"runtimeOptions": {
"configProperties": {
"System.GC.Server": true
}
},
"publishOptions": {
"include": [
"wwwroot"
]
}
}

View File

@ -0,0 +1,89 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<style>
p {
font-size: 20px;
}
.button {
border: none;
color: white;
padding: 10px 15px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 14px;
margin: 5px 5px;
cursor: pointer;
}
.green {
background-color: #4CAF50;
}
.red {
background-color: indianred;
}
.gray {
background-color: gray;
}
</style>
<title>CORS Sample</title>
</head>
<body>
<script type="text/javascript">
// Make the CORS request.
function makeCORSRequest(method, headerName, headerValue) {
// Destination server with CORS enabled.
var url = 'http://destination.example.com:5000/';
var request = new XMLHttpRequest();
request.open(method, url, true);
if (headerName && headerValue) {
request.setRequestHeader(headerName, headerValue);
}
if (!request) {
alert('CORS not supported');
return;
}
// Response handlers.
request.onload = function () {
var text = request.responseText;
alert('Response from CORS ' + method + ' request to ' + url + ': ' + text);
};
request.onerror = function () {
alert('There was an error making the request for method ' + method);
};
request.send();
}
</script>
<p>CORS Sample</p>
Method: <input type="text" id="methodName" /><br /><br />
Header Name: <input type="text" id="headerName" /> Header Value: <input type="text" id="headerValue" /><br /><br />
<script>
document.getElementById('headerValue')
.addEventListener("keyup", function (event) {
event.preventDefault();
if (event.keyCode == 13) {
document.getElementById("CORS").click();
}
});
</script>
<button class="button gray" id="CORS" type="submit" onclick="makeCORSRequest(document.getElementById('methodName').value, document.getElementById('headerName').value, document.getElementById('headerValue').value);">Make a CORS Request</button><br /><br /><br /><br />
Method DELETE is not allowed:<button class="button red" id="InvalidMethodCORS" type="submit" onclick="makeCORSRequest('DELETE', 'Cache-Control', 'no-cache');">Invalid Method CORS Request</button>
Method PUT is allowed:<button class="button green" id="ValidMethodCORS" type="submit" onclick="makeCORSRequest('PUT', 'Cache-Control', 'no-cache');">Valid Method CORS Request</button><br /><br />
Header 'Max-Forwards' not supported:<button class="button red" id="InvalidHeaderCORS" type="submit" onclick="makeCORSRequest('PUT', 'Max-Forwards', '2');">Invalid Header CORS Request</button>
Header 'Cache-Control' is supported:<button class="button green" id="ValidHeaderCORS" type="submit" onclick="makeCORSRequest('PUT', 'Cache-Control', 'no-cache');">Valid Header CORS Request</button><br /><br />
</body>
</html>

View File

@ -5,7 +5,9 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Microsoft.AspNetCore.Cors.Internal;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
@ -17,12 +19,23 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
public class CorsService : ICorsService
{
private readonly CorsOptions _options;
private readonly ILogger _logger;
/// <summary>
/// Creates a new instance of the <see cref="CorsService"/>.
/// </summary>
/// <param name="options">The option model representing <see cref="CorsOptions"/>.</param>
public CorsService(IOptions<CorsOptions> options)
: this(options, loggerFactory: null)
{
}
/// <summary>
/// Creates a new instance of the <see cref="CorsService"/>.
/// </summary>
/// <param name="options">The option model representing <see cref="CorsOptions"/>.</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
public CorsService(IOptions<CorsOptions> options, ILoggerFactory loggerFactory)
{
if (options == null)
{
@ -30,6 +43,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
}
_options = options.Value;
_logger = loggerFactory?.CreateLogger<CorsService>();
}
/// <summary>
@ -69,6 +83,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
if (string.Equals(context.Request.Method, CorsConstants.PreflightHttpMethod, StringComparison.OrdinalIgnoreCase) &&
!StringValues.IsNullOrEmpty(accessControlRequestMethod))
{
_logger?.IsPreflightRequest();
EvaluatePreflightRequest(context, policy, corsResult);
}
else
@ -82,21 +97,40 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
public virtual void EvaluateRequest(HttpContext context, CorsPolicy policy, CorsResult result)
{
var origin = context.Request.Headers[CorsConstants.Origin];
if (StringValues.IsNullOrEmpty(origin) || !policy.AllowAnyOrigin && !policy.Origins.Contains(origin))
if (StringValues.IsNullOrEmpty(origin))
{
_logger?.RequestDoesNotHaveOriginHeader();
return;
}
_logger?.RequestHasOriginHeader(origin);
if (!policy.AllowAnyOrigin && !policy.Origins.Contains(origin))
{
_logger?.PolicyFailure();
_logger?.OriginNotAllowed(origin);
return;
}
AddOriginToResult(origin, policy, result);
result.SupportsCredentials = policy.SupportsCredentials;
AddHeaderValues(result.AllowedExposedHeaders, policy.ExposedHeaders);
_logger?.PolicySuccess();
}
public virtual void EvaluatePreflightRequest(HttpContext context, CorsPolicy policy, CorsResult result)
{
var origin = context.Request.Headers[CorsConstants.Origin];
if (StringValues.IsNullOrEmpty(origin) || !policy.AllowAnyOrigin && !policy.Origins.Contains(origin))
if (StringValues.IsNullOrEmpty(origin))
{
_logger?.RequestDoesNotHaveOriginHeader();
return;
}
_logger?.RequestHasOriginHeader(origin);
if (!policy.AllowAnyOrigin && !policy.Origins.Contains(origin))
{
_logger?.PolicyFailure();
_logger?.OriginNotAllowed(origin);
return;
}
@ -124,16 +158,25 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
if (!found)
{
_logger?.PolicyFailure();
_logger?.AccessControlMethodNotAllowed(accessControlRequestMethod);
return;
}
}
if (!policy.AllowAnyHeader &&
requestHeaders != null &&
!requestHeaders.All(header => CorsConstants.SimpleRequestHeaders.Contains(header, StringComparer.OrdinalIgnoreCase) ||
policy.Headers.Contains(header, StringComparer.OrdinalIgnoreCase)))
requestHeaders != null)
{
return;
foreach (var requestHeader in requestHeaders)
{
if (!CorsConstants.SimpleRequestHeaders.Contains(requestHeader, StringComparer.OrdinalIgnoreCase) &&
!policy.Headers.Contains(requestHeader, StringComparer.OrdinalIgnoreCase))
{
_logger?.PolicyFailure();
_logger?.RequestHeaderNotAllowed(requestHeader);
return;
}
}
}
AddOriginToResult(origin, policy, result);
@ -141,6 +184,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
result.PreflightMaxAge = policy.PreflightMaxAge;
result.AllowedMethods.Add(accessControlRequestMethod);
AddHeaderValues(result.AllowedHeaders, requestHeaders);
_logger?.PolicySuccess();
}
/// <inheritdoc />
@ -261,4 +305,4 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
}
}
}
}
}

View File

@ -0,0 +1,103 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Cors.Internal
{
internal static class CORSLoggerExtensions
{
private static readonly Action<ILogger, Exception> _isPreflightRequest;
private static readonly Action<ILogger, string, Exception> _requestHasOriginHeader;
private static readonly Action<ILogger, Exception> _requestDoesNotHaveOriginHeader;
private static readonly Action<ILogger, Exception> _policySuccess;
private static readonly Action<ILogger, Exception> _policyFailure;
private static readonly Action<ILogger, string, Exception> _originNotAllowed;
private static readonly Action<ILogger, string, Exception> _accessControlMethodNotAllowed;
private static readonly Action<ILogger, string, Exception> _requestHeaderNotAllowed;
static CORSLoggerExtensions()
{
_isPreflightRequest = LoggerMessage.Define(
LogLevel.Debug,
1,
"The request is a preflight request.");
_requestHasOriginHeader = LoggerMessage.Define<string>(
LogLevel.Debug,
2,
"The request has an origin header: '{origin}'.");
_requestDoesNotHaveOriginHeader = LoggerMessage.Define(
LogLevel.Debug,
3,
"The request does not have an origin header.");
_policySuccess = LoggerMessage.Define(
LogLevel.Information,
4,
"Policy execution successful.");
_policyFailure = LoggerMessage.Define(
LogLevel.Information,
5,
"Policy execution failed.");
_originNotAllowed = LoggerMessage.Define<string>(
LogLevel.Information,
6,
"Request origin {origin} does not have permission to access the resource.");
_accessControlMethodNotAllowed = LoggerMessage.Define<string>(
LogLevel.Information,
7,
"Request method {accessControlRequestMethod} not allowed in CORS policy.");
_requestHeaderNotAllowed = LoggerMessage.Define<string>(
LogLevel.Information,
8,
"Request header '{requestHeader}' not allowed in CORS policy.");
}
public static void IsPreflightRequest(this ILogger logger)
{
_isPreflightRequest(logger, null);
}
public static void RequestHasOriginHeader(this ILogger logger, string origin)
{
_requestHasOriginHeader(logger, origin, null);
}
public static void RequestDoesNotHaveOriginHeader(this ILogger logger)
{
_requestDoesNotHaveOriginHeader(logger, null);
}
public static void PolicySuccess(this ILogger logger)
{
_policySuccess(logger, null);
}
public static void PolicyFailure(this ILogger logger)
{
_policyFailure(logger, null);
}
public static void OriginNotAllowed(this ILogger logger, string origin)
{
_originNotAllowed(logger, origin, null);
}
public static void AccessControlMethodNotAllowed(this ILogger logger, string accessControlMethod)
{
_accessControlMethodNotAllowed(logger, accessControlMethod, null);
}
public static void RequestHeaderNotAllowed(this ILogger logger, string requestHeader)
{
_requestHeaderNotAllowed(logger, requestHeader, null);
}
}
}

View File

@ -23,6 +23,7 @@
"Microsoft.AspNetCore.Http.Extensions": "1.2.0-*",
"Microsoft.Extensions.Configuration.Abstractions": "1.2.0-*",
"Microsoft.Extensions.DependencyInjection.Abstractions": "1.2.0-*",
"Microsoft.Extensions.Logging.Abstractions": "1.2.0-*",
"Microsoft.Extensions.Options": "1.2.0-*",
"NETStandard.Library": "1.6.1-*"
},

View File

@ -3,6 +3,7 @@
using System;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Cors.Infrastructure
@ -227,6 +228,162 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
Assert.Contains("PUT", result.AllowedMethods);
}
public static TheoryData<LogData> PreflightRequests_LoggingData
{
get
{
return new TheoryData<LogData>
{
{
new LogData {
Origin = "http://example.com",
Method = "PUT",
Headers = null,
OriginLogMessage = "The request has an origin header: 'http://example.com'.",
PolicyLogMessage = "Policy execution failed.",
FailureReason = "Request origin http://example.com does not have permission to access the resource."
}
},
{
new LogData {
Origin = "http://allowed.example.com",
Method = "DELETE",
Headers = null,
OriginLogMessage = "The request has an origin header: 'http://allowed.example.com'.",
PolicyLogMessage = "Policy execution failed.",
FailureReason = "Request method DELETE not allowed in CORS policy."
}
},
{
new LogData {
Origin = "http://allowed.example.com",
Method = "PUT",
Headers = new[] { "test" },
OriginLogMessage = "The request has an origin header: 'http://allowed.example.com'.",
PolicyLogMessage = "Policy execution failed.",
FailureReason = "Request header 'test' not allowed in CORS policy."
}
},
};
}
}
[Theory]
[MemberData(nameof(PreflightRequests_LoggingData))]
public void EvaluatePolicy_LoggingForPreflightRequests_HasOriginHeader_PolicyFailed(LogData logData)
{
var sink = new TestSink();
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
var corsService = new CorsService(new TestCorsOptions(), loggerFactory);
var requestContext = GetHttpContext(method: "OPTIONS", origin: logData.Origin, accessControlRequestMethod: logData.Method, accessControlRequestHeaders: logData.Headers);
var policy = new CorsPolicy();
policy.Origins.Add("http://allowed.example.com");
policy.Methods.Add("PUT");
// Act
var result = corsService.EvaluatePolicy(requestContext, policy);
Assert.Equal("The request is a preflight request.", sink.Writes[0].State.ToString());
Assert.Equal(logData.OriginLogMessage, sink.Writes[1].State.ToString());
Assert.Equal(logData.PolicyLogMessage, sink.Writes[2].State.ToString());
Assert.Equal(logData.FailureReason, sink.Writes[3].State.ToString());
}
[Fact]
public void EvaluatePolicy_LoggingForPreflightRequests_HasOriginHeader_PolicySucceeded()
{
var sink = new TestSink();
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
var corsService = new CorsService(new TestCorsOptions(), loggerFactory);
var requestContext = GetHttpContext(method: "OPTIONS", origin: "http://allowed.example.com", accessControlRequestMethod: "PUT");
var policy = new CorsPolicy();
policy.Origins.Add("http://allowed.example.com");
policy.Methods.Add("PUT");
// Act
var result = corsService.EvaluatePolicy(requestContext, policy);
Assert.Equal("The request is a preflight request.", sink.Writes[0].State.ToString());
Assert.Equal("The request has an origin header: 'http://allowed.example.com'.", sink.Writes[1].State.ToString());
Assert.Equal("Policy execution successful.", sink.Writes[2].State.ToString());
}
[Fact]
public void EvaluatePolicy_LoggingForPreflightRequests_DoesNotHaveOriginHeader()
{
var sink = new TestSink();
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
var corsService = new CorsService(new TestCorsOptions(), loggerFactory);
var requestContext = GetHttpContext(method: "OPTIONS", origin: null, accessControlRequestMethod: "PUT");
var policy = new CorsPolicy();
policy.Origins.Add("http://allowed.example.com");
policy.Methods.Add("PUT");
// Act
var result = corsService.EvaluatePolicy(requestContext, policy);
Assert.Equal("The request is a preflight request.", sink.Writes[0].State.ToString());
Assert.Equal("The request does not have an origin header.", sink.Writes[1].State.ToString());
}
[Fact]
public void EvaluatePolicy_LoggingForNonPreflightRequests_HasOriginHeader_PolicyFailed()
{
var sink = new TestSink();
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
var corsService = new CorsService(new TestCorsOptions(), loggerFactory);
var requestContext = GetHttpContext(origin: "http://example.com");
var policy = new CorsPolicy();
policy.Origins.Add("http://allowed.example.com");
// Act
var result = corsService.EvaluatePolicy(requestContext, policy);
Assert.Equal("The request has an origin header: 'http://example.com'.", sink.Writes[0].State.ToString());
Assert.Equal("Policy execution failed.", sink.Writes[1].State.ToString());
Assert.Equal("Request origin http://example.com does not have permission to access the resource.", sink.Writes[2].State.ToString());
}
[Fact]
public void EvaluatePolicy_LoggingForNonPreflightRequests_HasOriginHeader_PolicySucceeded()
{
var sink = new TestSink();
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
var corsService = new CorsService(new TestCorsOptions(), loggerFactory);
var requestContext = GetHttpContext(origin: "http://allowed.example.com");
var policy = new CorsPolicy();
policy.Origins.Add("http://allowed.example.com");
// Act
var result = corsService.EvaluatePolicy(requestContext, policy);
Assert.Equal("The request has an origin header: 'http://allowed.example.com'.", sink.Writes[0].State.ToString());
Assert.Equal("Policy execution successful.", sink.Writes[1].State.ToString());
}
[Fact]
public void EvaluatePolicy_LoggingForNonPreflightRequests_DoesNotHaveOriginHeader()
{
var sink = new TestSink();
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
var corsService = new CorsService(new TestCorsOptions(), loggerFactory);
var requestContext = GetHttpContext(origin: null);
var policy = new CorsPolicy();
policy.Origins.Add("http://allowed.example.com");
// Act
var result = corsService.EvaluatePolicy(requestContext, policy);
var logMessage = Assert.Single(sink.Writes);
Assert.Equal("The request does not have an origin header.", logMessage.State.ToString());
}
[Theory]
[InlineData("OpTions")]
[InlineData("OPTIONS")]
@ -446,7 +603,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
}
[Fact]
public void EaluatePolicy_DoesCaseSensitiveComparison()
public void EvaluatePolicy_DoesCaseSensitiveComparison()
{
// Arrange
var corsService = new CorsService(new TestCorsOptions());
@ -913,5 +1070,15 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
return context;
}
public class LogData
{
public string Origin { get; set; }
public string Method { get; set; }
public string[] Headers { get; set; }
public string OriginLogMessage { get; set; }
public string PolicyLogMessage { get; set; }
public string FailureReason { get; set; }
}
}
}
}