Merge pull request #95 from aspnet/jbagga/Logging78
Addresses #69 and #78
This commit is contained in:
commit
52ba62c4d1
19
CORS.sln
19
CORS.sln
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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-*"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue