Remove filtering from CorsService

* Add browser based integration tests
This commit is contained in:
Pranav K 2018-08-10 17:51:34 -07:00
Родитель b9166f14f1
Коммит 2690a3f621
38 изменённых файлов: 7173 добавлений и 885 удалений

Просмотреть файл

@ -5,6 +5,9 @@ mono: none
os: os:
- linux - linux
- osx - osx
env:
global:
- TRAVIS_NODE_VERSION: 8.9.3
osx_image: xcode8.2 osx_image: xcode8.2
addons: addons:
apt: apt:
@ -16,6 +19,7 @@ branches:
- /^release\/.*$/ - /^release\/.*$/
- /^(.*\/)?ci-.*$/ - /^(.*\/)?ci-.*$/
before_install: before_install:
- nvm install $TRAVIS_NODE_VERSION
- if test "$TRAVIS_OS_NAME" == "osx"; then brew update; brew install openssl; ln -s - if test "$TRAVIS_OS_NAME" == "osx"; then brew update; brew install openssl; ln -s
/usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib /usr/local/lib/; ln -s /usr/local/opt/openssl/lib/libssl.1.0.0.dylib /usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib /usr/local/lib/; ln -s /usr/local/opt/openssl/lib/libssl.1.0.0.dylib
/usr/local/lib/; fi /usr/local/lib/; fi

Просмотреть файл

@ -10,4 +10,29 @@ resources:
ref: refs/heads/release/2.2 ref: refs/heads/release/2.2
phases: phases:
- template: .vsts-pipelines/templates/project-ci.yml@buildtools - template: .vsts-pipelines/templates/phases/default-build.yml@buildtools
parameters:
agentOs: Windows
beforeBuild:
- task: NodeTool@0
displayName: Use Node 8.x
inputs:
versionSpec: 8.x
- template: .vsts-pipelines/templates/phases/default-build.yml@buildtools
parameters:
agentOs: macOS
beforeBuild:
- task: NodeTool@0
displayName: Use Node 8.x
inputs:
versionSpec: 8.x
- template: .vsts-pipelines/templates/phases/default-build.yml@buildtools
parameters:
agentOs: Linux
beforeBuild:
- task: NodeTool@0
displayName: Use Node 8.x
inputs:
versionSpec: 8.x

Просмотреть файл

@ -12,4 +12,29 @@ resources:
ref: refs/heads/release/2.2 ref: refs/heads/release/2.2
phases: phases:
- template: .vsts-pipelines/templates/project-ci.yml@buildtools - template: .vsts-pipelines/templates/phases/default-build.yml@buildtools
parameters:
agentOs: Windows
beforeBuild:
- task: NodeTool@0
displayName: Use Node 8.x
inputs:
versionSpec: 8.x
- template: .vsts-pipelines/templates/phases/default-build.yml@buildtools
parameters:
agentOs: macOS
beforeBuild:
- task: NodeTool@0
displayName: Use Node 8.x
inputs:
versionSpec: 8.x
- template: .vsts-pipelines/templates/phases/default-build.yml@buildtools
parameters:
agentOs: Linux
beforeBuild:
- task: NodeTool@0
displayName: Use Node 8.x
inputs:
versionSpec: 8.x

Просмотреть файл

@ -45,6 +45,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
version.xml = version.xml version.xml = version.xml
EndProjectSection EndProjectSection
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FunctionalTests", "test\FunctionalTests\FunctionalTests.csproj", "{99EB6889-C7D8-4BC3-AF99-B966B9E64C81}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -71,6 +73,10 @@ Global
{99460370-AE5D-4DC9-8DBF-04DF66D6B21D}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{99460370-AE5D-4DC9-8DBF-04DF66D6B21D}.Release|Any CPU.Build.0 = Release|Any CPU {99460370-AE5D-4DC9-8DBF-04DF66D6B21D}.Release|Any CPU.Build.0 = Release|Any CPU
{99EB6889-C7D8-4BC3-AF99-B966B9E64C81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{99EB6889-C7D8-4BC3-AF99-B966B9E64C81}.Debug|Any CPU.Build.0 = Debug|Any CPU
{99EB6889-C7D8-4BC3-AF99-B966B9E64C81}.Release|Any CPU.ActiveCfg = Release|Any CPU
{99EB6889-C7D8-4BC3-AF99-B966B9E64C81}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -82,6 +88,7 @@ Global
{B42D4844-FFF8-4EC2-88D1-3AE95234D9EB} = {538380BF-0D4C-4E30-8F41-E75C4B1C01FA} {B42D4844-FFF8-4EC2-88D1-3AE95234D9EB} = {538380BF-0D4C-4E30-8F41-E75C4B1C01FA}
{F6675DC1-AA21-453B-89B6-DA425FB9C3A5} = {960E0703-A8A5-44DF-AA87-B7C614683B3C} {F6675DC1-AA21-453B-89B6-DA425FB9C3A5} = {960E0703-A8A5-44DF-AA87-B7C614683B3C}
{99460370-AE5D-4DC9-8DBF-04DF66D6B21D} = {960E0703-A8A5-44DF-AA87-B7C614683B3C} {99460370-AE5D-4DC9-8DBF-04DF66D6B21D} = {960E0703-A8A5-44DF-AA87-B7C614683B3C}
{99EB6889-C7D8-4BC3-AF99-B966B9E64C81} = {F32074C7-087C-46CC-A913-422BFD2D6E0A}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F9ED9C53-44CD-4853-9621-D028B7B6A431} SolutionGuid = {F9ED9C53-44CD-4853-9621-D028B7B6A431}

Просмотреть файл

@ -1,10 +1 @@
@Library('dotnet-ci') _ @Library('dotnet-ci') _
simpleNode('Ubuntu16.04', 'latest-or-auto-docker') {
stage ('Checking out source') {
checkout scm
}
stage ('Build') {
sh './build.sh --ci'
}
}

Просмотреть файл

@ -1,10 +1 @@
@Library('dotnet-ci') _ @Library('dotnet-ci') _
simpleNode('OSX10.12','latest') {
stage ('Checking out source') {
checkout scm
}
stage ('Build') {
sh './build.sh --ci'
}
}

Просмотреть файл

@ -2,7 +2,7 @@
// 'node' indicates to Jenkins that the enclosed block runs on a node that matches // 'node' indicates to Jenkins that the enclosed block runs on a node that matches
// the label 'windows-with-vs' // the label 'windows-with-vs'
simpleNode('Windows_NT','latest') { simpleNode('Windows.10.Amd64.EnterpriseRS3.ASPNET.Open') {
stage ('Checking out source') { stage ('Checking out source') {
checkout scm checkout scm
} }

Просмотреть файл

@ -3,20 +3,22 @@
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects> <MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Label="Package Versions"> <PropertyGroup Label="Package Versions">
<InternalAspNetCoreSdkPackageVersion>2.2.0-preview1-20180928.5</InternalAspNetCoreSdkPackageVersion> <InternalAspNetCoreSdkPackageVersion>2.2.0-preview2-20181004.6</InternalAspNetCoreSdkPackageVersion>
<MicrosoftAspNetCoreHttpExtensionsPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreHttpExtensionsPackageVersion> <MicrosoftAspNetCoreHttpExtensionsPackageVersion>2.2.0-preview3-35413</MicrosoftAspNetCoreHttpExtensionsPackageVersion>
<MicrosoftAspNetCoreServerIISIntegrationPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreServerIISIntegrationPackageVersion> <MicrosoftAspNetCoreServerIISIntegrationPackageVersion>2.2.0-preview3-35413</MicrosoftAspNetCoreServerIISIntegrationPackageVersion>
<MicrosoftAspNetCoreServerKestrelPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreServerKestrelPackageVersion> <MicrosoftAspNetCoreServerIntegrationTestingPackageVersion>0.6.0-preview3-35413</MicrosoftAspNetCoreServerIntegrationTestingPackageVersion>
<MicrosoftAspNetCoreTestHostPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreTestHostPackageVersion> <MicrosoftAspNetCoreServerKestrelPackageVersion>2.2.0-preview3-35413</MicrosoftAspNetCoreServerKestrelPackageVersion>
<MicrosoftExtensionsConfigurationAbstractionsPackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsConfigurationAbstractionsPackageVersion> <MicrosoftAspNetCoreStaticFilesPackageVersion>2.2.0-preview3-35413</MicrosoftAspNetCoreStaticFilesPackageVersion>
<MicrosoftExtensionsDependencyInjectionAbstractionsPackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsDependencyInjectionAbstractionsPackageVersion> <MicrosoftAspNetCoreTestHostPackageVersion>2.2.0-preview3-35413</MicrosoftAspNetCoreTestHostPackageVersion>
<MicrosoftExtensionsLoggingAbstractionsPackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsLoggingAbstractionsPackageVersion> <MicrosoftExtensionsConfigurationAbstractionsPackageVersion>2.2.0-preview3-35413</MicrosoftExtensionsConfigurationAbstractionsPackageVersion>
<MicrosoftExtensionsLoggingConsolePackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsLoggingConsolePackageVersion> <MicrosoftExtensionsDependencyInjectionAbstractionsPackageVersion>2.2.0-preview3-35413</MicrosoftExtensionsDependencyInjectionAbstractionsPackageVersion>
<MicrosoftExtensionsLoggingTestingPackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsLoggingTestingPackageVersion> <MicrosoftExtensionsLoggingAbstractionsPackageVersion>2.2.0-preview3-35413</MicrosoftExtensionsLoggingAbstractionsPackageVersion>
<MicrosoftExtensionsOptionsPackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsOptionsPackageVersion> <MicrosoftExtensionsLoggingConsolePackageVersion>2.2.0-preview3-35413</MicrosoftExtensionsLoggingConsolePackageVersion>
<MicrosoftExtensionsLoggingTestingPackageVersion>2.2.0-preview3-35413</MicrosoftExtensionsLoggingTestingPackageVersion>
<MicrosoftExtensionsOptionsPackageVersion>2.2.0-preview3-35413</MicrosoftExtensionsOptionsPackageVersion>
<MicrosoftNETCoreApp20PackageVersion>2.0.9</MicrosoftNETCoreApp20PackageVersion> <MicrosoftNETCoreApp20PackageVersion>2.0.9</MicrosoftNETCoreApp20PackageVersion>
<MicrosoftNETCoreApp21PackageVersion>2.1.3</MicrosoftNETCoreApp21PackageVersion> <MicrosoftNETCoreApp21PackageVersion>2.1.3</MicrosoftNETCoreApp21PackageVersion>
<MicrosoftNETCoreApp22PackageVersion>2.2.0-preview3-26927-02</MicrosoftNETCoreApp22PackageVersion> <MicrosoftNETCoreApp22PackageVersion>2.2.0-preview3-27001-02</MicrosoftNETCoreApp22PackageVersion>
<MicrosoftNETTestSdkPackageVersion>15.6.1</MicrosoftNETTestSdkPackageVersion> <MicrosoftNETTestSdkPackageVersion>15.6.1</MicrosoftNETTestSdkPackageVersion>
<MoqPackageVersion>4.7.49</MoqPackageVersion> <MoqPackageVersion>4.7.49</MoqPackageVersion>
<NETStandardLibrary20PackageVersion>2.0.3</NETStandardLibrary20PackageVersion> <NETStandardLibrary20PackageVersion>2.0.3</NETStandardLibrary20PackageVersion>

11
build/repo.targets Normal file
Просмотреть файл

@ -0,0 +1,11 @@
<Project>
<PropertyGroup>
<RestoreDependsOn>$(RestoreDependsOn);RestoreNpm</RestoreDependsOn>
</PropertyGroup>
<Target Name="RestoreNpm" Condition="'$(PreflightRestore)' != 'True'">
<Message Text="Restoring NPM modules" Importance="high" />
<Exec Command="npm install --no-optional" WorkingDirectory="$(RepositoryRoot)test\FunctionalTests" />
</Target>
</Project>

Просмотреть файл

@ -1,2 +1,2 @@
version:2.2.0-preview1-20180928.5 version:2.2.0-preview2-20181004.6
commithash:43faa29f679f47b88689d645b39e6be5e0055d70 commithash:c04c4b2f5018632647f96210ab01876661302dac

Просмотреть файл

@ -13,7 +13,7 @@ namespace SampleDestination
{ {
var host = new WebHostBuilder() var host = new WebHostBuilder()
.UseKestrel() .UseKestrel()
.UseUrls("http://*:5000") .UseUrls("http://+:9000")
.UseContentRoot(Directory.GetCurrentDirectory()) .UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureLogging(factory => factory.AddConsole()) .ConfigureLogging(factory => factory.AddConsole())
.UseStartup<Startup>() .UseStartup<Startup>()

Просмотреть файл

@ -0,0 +1,33 @@
// 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.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace SampleDestination
{
public class SampleMiddleware
{
private readonly RequestDelegate _next;
public SampleMiddleware(RequestDelegate next)
{
_next = next;
}
public Task Invoke(HttpContext context)
{
var content = Encoding.UTF8.GetBytes("Hello world");
context.Response.Headers["X-AllowedHeader"] = "Test-Value";
context.Response.Headers["X-DisallowedHeader"] = "Test-Value";
context.Response.ContentType = "text/plain; charset=utf-8";
context.Response.ContentLength = content.Length;
context.Response.Body.Write(content, 0, content.Length);
return Task.CompletedTask;
}
}
}

Просмотреть файл

@ -1,6 +1,10 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
@ -11,6 +15,15 @@ namespace SampleDestination
{ {
public class Startup public class Startup
{ {
private static readonly string DefaultAllowedOrigin = $"http://{Dns.GetHostName()}:9001";
private readonly ILogger<Startup> _logger;
public Startup(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<Startup>();
_logger.LogInformation($"Setting up CORS middleware to allow clients on {DefaultAllowedOrigin}");
}
public void ConfigureServices(IServiceCollection services) public void ConfigureServices(IServiceCollection services)
{ {
services.AddCors(); services.AddCors();
@ -18,21 +31,60 @@ namespace SampleDestination
public void Configure(IApplicationBuilder app, IHostingEnvironment env) public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{ {
app.UseCors(policy => policy app.Map("/allow-origin", innerBuilder =>
.WithOrigins("http://origin.example.com:5001")
.WithMethods("PUT")
.WithHeaders("Cache-Control"));
app.Run(async context =>
{ {
var responseHeaders = context.Response.Headers; innerBuilder.UseCors(policy => policy
context.Response.ContentType = "text/plain"; .WithOrigins(DefaultAllowedOrigin)
foreach (var responseHeader in responseHeaders) .AllowAnyMethod()
{ .AllowAnyHeader());
await context.Response.WriteAsync("\n" + responseHeader.Key + ": " + responseHeader.Value);
}
await context.Response.WriteAsync("\nStatus code of your request: " + context.Response.StatusCode.ToString()); innerBuilder.UseMiddleware<SampleMiddleware>();
});
app.Map("/allow-header-method", innerBuilder =>
{
innerBuilder.UseCors(policy => policy
.WithOrigins(DefaultAllowedOrigin)
.WithHeaders("X-Test", "Content-Type")
.WithMethods("PUT"));
innerBuilder.UseMiddleware<SampleMiddleware>();
});
app.Map("/allow-credentials", innerBuilder =>
{
innerBuilder.UseCors(policy => policy
.WithOrigins(DefaultAllowedOrigin)
.AllowAnyHeader()
.WithMethods("GET", "PUT")
.AllowCredentials());
innerBuilder.UseMiddleware<SampleMiddleware>();
});
app.Map("/exposed-header", innerBuilder =>
{
innerBuilder.UseCors(policy => policy
.WithOrigins(DefaultAllowedOrigin)
.WithExposedHeaders("X-AllowedHeader", "Content-Length"));
innerBuilder.UseMiddleware<SampleMiddleware>();
});
app.Map("/allow-all", innerBuilder =>
{
innerBuilder.UseCors(policy => policy
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
innerBuilder.UseMiddleware<SampleMiddleware>();
});
app.Run(async (context) =>
{
await context.Response.WriteAsync("Hello World!");
}); });
} }
} }

Просмотреть файл

@ -13,7 +13,7 @@ namespace SampleOrigin
{ {
var host = new WebHostBuilder() var host = new WebHostBuilder()
.UseKestrel() .UseKestrel()
.UseUrls("http://*:5001") .UseUrls("http://+:9001", "http://+:9002")
.UseContentRoot(Directory.GetCurrentDirectory()) .UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureLogging(factory => factory.AddConsole()) .ConfigureLogging(factory => factory.AddConsole())
.UseStartup<Startup>() .UseStartup<Startup>()

Просмотреть файл

@ -6,7 +6,14 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(MicrosoftAspNetCoreServerKestrelPackageVersion)" /> <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(MicrosoftAspNetCoreServerKestrelPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="$(MicrosoftAspNetCoreStaticFilesPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" /> <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Content Update="wwwroot\*.htm">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project> </Project>

Просмотреть файл

@ -1,11 +1,11 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace SampleOrigin namespace SampleOrigin
{ {
@ -17,13 +17,8 @@ namespace SampleOrigin
public void Configure(IApplicationBuilder app, IHostingEnvironment env) public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{ {
app.Run(context => app.UseDefaultFiles();
{ app.UseStaticFiles();
var fileInfoProvider = env.WebRootFileProvider;
var fileInfo = fileInfoProvider.GetFileInfo("/Index.html");
context.Response.ContentType = "text/html";
return context.Response.SendFileAsync(fileInfo);
});
} }
} }
} }

Просмотреть файл

@ -1,89 +0,0 @@
<!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>

Просмотреть файл

@ -0,0 +1,2 @@
<h1>Welcome to the CORS test suite. Please wait...</h1>
<a href="test.htm">Click here for the browser test suite.</a>

Просмотреть файл

@ -0,0 +1,135 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<style>
.success {
background-color: #4CAF50;
}
</style>
<title>CORS Sample</title>
</head>
<body>
<p>CORS Sample</p>
<table>
<thead>
<tr>
<th>Scenario</th>
<th>Result</th>
</tr>
</thead>
<tbody>
<tr id="scenarios"></tr>
</tbody>
</table>
<script type="text/javascript">
(function () {
function _fetch(options) {
const url = `http://${location.hostname}:9000/${options.testPath}`;
options.cors = 'cors';
return fetch(url, options);
}
function assertSuccess(options) {
return _fetch(options).then(s => true).catch(s => false);
}
function assertFailure(options) {
return _fetch(options).then(s => false).catch(s => true);
}
const scenarios = {
"Simple GET Request": assertSuccess({
testPath: 'allow-origin',
method: 'GET'
}),
"Simple PUT Request": assertSuccess({
testPath: 'allow-origin',
method: 'PUT'
}),
"Disallowed DELETE Method": assertFailure({
testPath: 'allow-header-method',
method: 'DELETE'
}),
"Allowed Header": assertSuccess({
testPath: 'allow-header-method',
method: 'PUT',
headers: new Headers({ "X-Test": "value", 'Content-Type': 'application/json' })
}),
// This one is weird - although the server performs a preflight request and receives a Access-Control-Allow-Methods: PUT,
// the browser happily ignores the disallowed POST method
"Allowed Header Disallowed POST": assertSuccess({
testPath: 'allow-header-method',
method: 'POST',
headers: new Headers({ "X-Test": "value", 'Content-Type': 'application/json' })
}),
"Disallowed Header": assertFailure({
testPath: 'allow-header-method',
method: 'POST',
headers: new Headers({ "Api-Key": "some_key", 'Content-Type': 'application/json' })
}),
"Disallowed Credentials": assertFailure({
testPath: 'allow-origin',
method: 'POST',
credentials: 'include'
}),
"Disallowed Credentials with preflight": assertFailure({
testPath: 'allow-origin',
method: 'PUT',
credentials: 'include'
}),
"Allowed Credentials": assertSuccess({
testPath: 'allow-credentials',
method: 'GET',
credentials: 'include'
}),
"Allowed Credentials with preflight": assertSuccess({
testPath: 'allow-credentials',
method: 'PUT',
credentials: 'include'
}),
"Disallowed exposed header": _fetch({
testPath: 'exposed-header',
method: 'GET'
}).then(response => !response.headers.has("x-disallowedheader")),
"Allowed exposed header": _fetch({
testPath: 'exposed-header',
method: 'GET'
}).then(response => response.headers.has("x-allowedheader"))
};
const scenariosElement = document.querySelector('#scenarios');
Object.keys(scenarios).map(scenario => {
const row = document.createElement('tr');
row.setAttribute("class", "scenario");
scenariosElement.appendChild(row);
let resultHtml = '<span class="waiting"></span>';
const setHtml = () => row.innerHTML = `<td>${scenario}</td><td>${resultHtml}</td>`;
setHtml();
return scenarios[scenario].then(result => {
resultHtml = result ?
'<span class="success"></span>' :
'<span class="failed"><span>';
setHtml();
});
});
})();
</script>
</body>
</html>

Просмотреть файл

@ -7,21 +7,19 @@ using Microsoft.AspNetCore.Cors.Internal;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNetCore.Cors.Infrastructure namespace Microsoft.AspNetCore.Cors.Infrastructure
{ {
/// <summary> /// <summary>
/// An ASP.NET middleware for handling CORS. /// A middleware for handling CORS.
/// </summary> /// </summary>
public class CorsMiddleware public class CorsMiddleware
{ {
private readonly Func<object, Task> OnResponseStartingDelegate = OnResponseStarting;
private readonly RequestDelegate _next; private readonly RequestDelegate _next;
private readonly ICorsService _corsService;
private readonly ICorsPolicyProvider _corsPolicyProvider; private readonly ICorsPolicyProvider _corsPolicyProvider;
private readonly CorsPolicy _policy; private readonly CorsPolicy _policy;
private readonly string _corsPolicyName; private readonly string _corsPolicyName;
private readonly ILogger _logger;
/// <summary> /// <summary>
/// Instantiates a new <see cref="CorsMiddleware"/>. /// Instantiates a new <see cref="CorsMiddleware"/>.
@ -122,10 +120,10 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
} }
_next = next; _next = next;
_corsService = corsService; CorsService = corsService;
_corsPolicyProvider = policyProvider; _corsPolicyProvider = policyProvider;
_corsPolicyName = policyName; _corsPolicyName = policyName;
_logger = loggerFactory.CreateLogger<CorsMiddleware>(); Logger = loggerFactory.CreateLogger<CorsMiddleware>();
} }
/// <summary> /// <summary>
@ -162,28 +160,40 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
} }
_next = next; _next = next;
_corsService = corsService; CorsService = corsService;
_policy = policy; _policy = policy;
_logger = loggerFactory.CreateLogger<CorsMiddleware>(); Logger = loggerFactory.CreateLogger<CorsMiddleware>();
} }
private ICorsService CorsService { get; }
private ILogger Logger { get; }
/// <inheritdoc /> /// <inheritdoc />
public async Task Invoke(HttpContext context) public Task Invoke(HttpContext context)
{ {
if (context.Request.Headers.ContainsKey(CorsConstants.Origin)) if (!context.Request.Headers.ContainsKey(CorsConstants.Origin))
{
return _next(context);
}
return InvokeCore(context);
}
private async Task InvokeCore(HttpContext context)
{ {
var corsPolicy = _policy ?? await _corsPolicyProvider?.GetPolicyAsync(context, _corsPolicyName); var corsPolicy = _policy ?? await _corsPolicyProvider?.GetPolicyAsync(context, _corsPolicyName);
if (corsPolicy != null) if (corsPolicy == null)
{ {
var accessControlRequestMethod = Logger?.NoCorsPolicyFound();
context.Request.Headers[CorsConstants.AccessControlRequestMethod]; await _next(context);
if (string.Equals( return;
context.Request.Method, }
CorsConstants.PreflightHttpMethod,
StringComparison.OrdinalIgnoreCase) && var corsResult = CorsService.EvaluatePolicy(context, corsPolicy);
!StringValues.IsNullOrEmpty(accessControlRequestMethod)) if (corsResult.IsPreflightRequest)
{ {
ApplyCorsHeaders(context, corsPolicy); CorsService.ApplyResult(corsResult, context.Response);
// Since there is a policy which was identified, // Since there is a policy which was identified,
// always respond to preflight requests. // always respond to preflight requests.
@ -192,30 +202,23 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
} }
else else
{ {
context.Response.OnStarting(state => context.Response.OnStarting(OnResponseStartingDelegate, Tuple.Create(this, context, corsResult));
await _next(context);
}
}
private static Task OnResponseStarting(object state)
{ {
var (httpContext, policy) = (Tuple<HttpContext, CorsPolicy>)state; var (middleware, context, result) = (Tuple<CorsMiddleware, HttpContext, CorsResult>)state;
try try
{ {
ApplyCorsHeaders(httpContext, policy); middleware.CorsService.ApplyResult(result, context.Response);
} }
catch (Exception exception) catch (Exception exception)
{ {
_logger.FailedToSetCorsHeaders(exception); middleware.Logger?.FailedToSetCorsHeaders(exception);
} }
return Task.CompletedTask; return Task.CompletedTask;
}, Tuple.Create(context, corsPolicy));
}
}
}
await _next(context);
}
private void ApplyCorsHeaders(HttpContext context, CorsPolicy corsPolicy)
{
var corsResult = _corsService.EvaluatePolicy(context, corsPolicy);
_corsService.ApplyResult(corsResult, context.Response);
} }
} }
} }

Просмотреть файл

@ -14,11 +14,22 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
{ {
private TimeSpan? _preflightMaxAge; private TimeSpan? _preflightMaxAge;
/// <summary>
/// Gets or sets a value that determines if the current request is a CORS-preflight request.
/// </summary>
public bool IsPreflightRequest { get; set; }
/// <summary> /// <summary>
/// Gets or sets the allowed origin. /// Gets or sets the allowed origin.
/// </summary> /// </summary>
public string AllowedOrigin { get; set; } public string AllowedOrigin { get; set; }
/// <summary>
/// Gets or sets a value that determines if the origin is allowed.
/// When <c>false</c>, no CORS headers should be sent.
/// </summary>
public bool IsOriginAllowed { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the resource supports user credentials. /// Gets or sets a value indicating whether the resource supports user credentials.
/// </summary> /// </summary>

Просмотреть файл

@ -8,6 +8,7 @@ using System.Linq;
using Microsoft.AspNetCore.Cors.Internal; using Microsoft.AspNetCore.Cors.Internal;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
@ -25,8 +26,9 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
/// Creates a new instance of the <see cref="CorsService"/>. /// Creates a new instance of the <see cref="CorsService"/>.
/// </summary> /// </summary>
/// <param name="options">The option model representing <see cref="CorsOptions"/>.</param> /// <param name="options">The option model representing <see cref="CorsOptions"/>.</param>
[Obsolete("This constructor is obsolete and will be removed in a future version.")]
public CorsService(IOptions<CorsOptions> options) public CorsService(IOptions<CorsOptions> options)
: this(options, loggerFactory: null) : this(options, loggerFactory: NullLoggerFactory.Instance)
{ {
} }
@ -42,8 +44,13 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
throw new ArgumentNullException(nameof(options)); throw new ArgumentNullException(nameof(options));
} }
if (loggerFactory == null)
{
throw new ArgumentNullException(nameof(loggerFactory));
}
_options = options.Value; _options = options.Value;
_logger = loggerFactory?.CreateLogger<CorsService>(); _logger = loggerFactory.CreateLogger<CorsService>();
} }
/// <summary> /// <summary>
@ -78,12 +85,25 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
throw new ArgumentNullException(nameof(policy)); throw new ArgumentNullException(nameof(policy));
} }
var corsResult = new CorsResult(); if (policy.AllowAnyOrigin && policy.SupportsCredentials)
var accessControlRequestMethod = context.Request.Headers[CorsConstants.AccessControlRequestMethod]; {
if (string.Equals(context.Request.Method, CorsConstants.PreflightHttpMethod, StringComparison.OrdinalIgnoreCase) && _logger.InsecureConfiguration();
!StringValues.IsNullOrEmpty(accessControlRequestMethod)) }
var origin = context.Request.Headers[CorsConstants.Origin];
var requestHeaders = context.Request.Headers;
var isPreflightRequest =
string.Equals(context.Request.Method, CorsConstants.PreflightHttpMethod, StringComparison.OrdinalIgnoreCase) &&
requestHeaders.ContainsKey(CorsConstants.AccessControlRequestMethod);
var corsResult = new CorsResult
{
IsPreflightRequest = isPreflightRequest,
IsOriginAllowed = IsOriginAllowed(policy, origin),
};
if (isPreflightRequest)
{ {
_logger?.IsPreflightRequest();
EvaluatePreflightRequest(context, policy, corsResult); EvaluatePreflightRequest(context, policy, corsResult);
} }
else else
@ -94,78 +114,37 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
return corsResult; return corsResult;
} }
public virtual void EvaluateRequest(HttpContext context, CorsPolicy policy, CorsResult result) private static void PopulateResult(HttpContext context, CorsPolicy policy, CorsResult result)
{
if (policy.AllowAnyOrigin)
{
result.AllowedOrigin = CorsConstants.AnyOrigin;
result.VaryByOrigin = policy.SupportsCredentials;
}
else
{ {
var origin = context.Request.Headers[CorsConstants.Origin]; var origin = context.Request.Headers[CorsConstants.Origin];
if (!IsOriginAllowed(policy, origin)) result.AllowedOrigin = origin;
{ result.VaryByOrigin = policy.Origins.Count > 1;
return;
} }
AddOriginToResult(origin, policy, result);
result.SupportsCredentials = policy.SupportsCredentials; result.SupportsCredentials = policy.SupportsCredentials;
result.PreflightMaxAge = policy.PreflightMaxAge;
AddHeaderValues(result.AllowedExposedHeaders, policy.ExposedHeaders); AddHeaderValues(result.AllowedExposedHeaders, policy.ExposedHeaders);
_logger?.PolicySuccess(); AddHeaderValues(result.AllowedMethods, policy.Methods);
AddHeaderValues(result.AllowedHeaders, policy.Headers);
}
public virtual void EvaluateRequest(HttpContext context, CorsPolicy policy, CorsResult result)
{
PopulateResult(context, policy, result);
} }
public virtual void EvaluatePreflightRequest(HttpContext context, CorsPolicy policy, CorsResult result) public virtual void EvaluatePreflightRequest(HttpContext context, CorsPolicy policy, CorsResult result)
{ {
var origin = context.Request.Headers[CorsConstants.Origin]; PopulateResult(context, policy, result);
if (!IsOriginAllowed(policy, origin))
{
return;
}
var accessControlRequestMethod = context.Request.Headers[CorsConstants.AccessControlRequestMethod];
if (StringValues.IsNullOrEmpty(accessControlRequestMethod))
{
return;
}
var requestHeaders =
context.Request.Headers.GetCommaSeparatedValues(CorsConstants.AccessControlRequestHeaders);
if (!policy.AllowAnyMethod)
{
var found = false;
for (var i = 0; i < policy.Methods.Count; i++)
{
var method = policy.Methods[i];
if (string.Equals(method, accessControlRequestMethod, StringComparison.OrdinalIgnoreCase))
{
found = true;
break;
}
}
if (!found)
{
_logger?.PolicyFailure();
_logger?.AccessControlMethodNotAllowed(accessControlRequestMethod);
return;
}
}
if (!policy.AllowAnyHeader &&
requestHeaders != null)
{
foreach (var requestHeader in requestHeaders)
{
if (!policy.Headers.Contains(requestHeader, StringComparer.OrdinalIgnoreCase))
{
_logger?.PolicyFailure();
_logger?.RequestHeaderNotAllowed(requestHeader);
return;
}
}
}
AddOriginToResult(origin, policy, result);
result.SupportsCredentials = policy.SupportsCredentials;
result.PreflightMaxAge = policy.PreflightMaxAge;
result.AllowedMethods.Add(accessControlRequestMethod);
AddHeaderValues(result.AllowedHeaders, requestHeaders);
_logger?.PolicySuccess();
} }
/// <inheritdoc /> /// <inheritdoc />
@ -181,86 +160,67 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
throw new ArgumentNullException(nameof(response)); throw new ArgumentNullException(nameof(response));
} }
var headers = response.Headers; if (!result.IsOriginAllowed)
if (result.AllowedOrigin != null)
{ {
headers[CorsConstants.AccessControlAllowOrigin] = result.AllowedOrigin; // In case a server does not wish to participate in the CORS protocol, its HTTP response to the
// CORS or CORS-preflight request must not include any of the above headers.
return;
} }
if (result.VaryByOrigin) response.Headers[CorsConstants.AccessControlAllowOrigin] = result.AllowedOrigin;
{
headers["Vary"] = "Origin";
}
if (result.SupportsCredentials) if (result.SupportsCredentials)
{ {
headers[CorsConstants.AccessControlAllowCredentials] = "true"; response.Headers[CorsConstants.AccessControlAllowCredentials] = "true";
}
if (result.IsPreflightRequest)
{
_logger.IsPreflightRequest();
// An HTTP response to a CORS-preflight request can include the following headers:
// `Access-Control-Allow-Methods`, `Access-Control-Allow-Headers`, `Access-Control-Max-Age`
if (result.AllowedHeaders.Count > 0)
{
response.Headers.SetCommaSeparatedValues(CorsConstants.AccessControlAllowHeaders, result.AllowedHeaders.ToArray());
} }
if (result.AllowedMethods.Count > 0) if (result.AllowedMethods.Count > 0)
{ {
headers.SetCommaSeparatedValues( response.Headers.SetCommaSeparatedValues(CorsConstants.AccessControlAllowMethods, result.AllowedMethods.ToArray());
CorsConstants.AccessControlAllowMethods,
result.AllowedMethods.ToArray());
}
if (result.AllowedHeaders.Count > 0)
{
headers.SetCommaSeparatedValues(
CorsConstants.AccessControlAllowHeaders,
result.AllowedHeaders.ToArray());
}
if (result.AllowedExposedHeaders.Count > 0)
{
headers.SetCommaSeparatedValues(
CorsConstants.AccessControlExposeHeaders,
result.AllowedExposedHeaders.ToArray());
} }
if (result.PreflightMaxAge.HasValue) if (result.PreflightMaxAge.HasValue)
{ {
headers[CorsConstants.AccessControlMaxAge] response.Headers[CorsConstants.AccessControlMaxAge] = result.PreflightMaxAge.Value.TotalSeconds.ToString(CultureInfo.InvariantCulture);
= result.PreflightMaxAge.Value.TotalSeconds.ToString(CultureInfo.InvariantCulture);
} }
} }
private void AddOriginToResult(string origin, CorsPolicy policy, CorsResult result)
{
if (policy.AllowAnyOrigin)
{
if (policy.SupportsCredentials)
{
result.AllowedOrigin = origin;
result.VaryByOrigin = true;
}
else else
{ {
result.AllowedOrigin = CorsConstants.AnyOrigin; // An HTTP response to a CORS request that is not a CORS-preflight request can also include the following header:
} // `Access-Control-Expose-Headers`
} if (result.AllowedExposedHeaders.Count > 0)
else if (policy.IsOriginAllowed(origin))
{ {
result.AllowedOrigin = origin; response.Headers.SetCommaSeparatedValues(CorsConstants.AccessControlExposeHeaders, result.AllowedExposedHeaders.ToArray());
if(policy.Origins.Count > 1)
{
result.VaryByOrigin = true;
}
} }
} }
private static void AddHeaderValues(IList<string> target, IEnumerable<string> headerValues) if (result.VaryByOrigin)
{
response.Headers.Append("Vary", "Origin");
}
}
private static void AddHeaderValues(IList<string> target, IList<string> headerValues)
{ {
if (headerValues == null) if (headerValues == null)
{ {
return; return;
} }
foreach (var current in headerValues) for (var i = 0; i < headerValues.Count; i++)
{ {
target.Add(current); target.Add(headerValues[i]);
} }
} }
@ -268,17 +228,18 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
{ {
if (StringValues.IsNullOrEmpty(origin)) if (StringValues.IsNullOrEmpty(origin))
{ {
_logger?.RequestDoesNotHaveOriginHeader(); _logger.RequestDoesNotHaveOriginHeader();
return false; return false;
} }
_logger?.RequestHasOriginHeader(origin); _logger.RequestHasOriginHeader(origin);
if (policy.AllowAnyOrigin || policy.IsOriginAllowed(origin)) if (policy.AllowAnyOrigin || policy.IsOriginAllowed(origin))
{ {
_logger.PolicySuccess();
return true; return true;
} }
_logger?.PolicyFailure(); _logger.PolicyFailure();
_logger?.OriginNotAllowed(origin); _logger.OriginNotAllowed(origin);
return false; return false;
} }
} }

Просмотреть файл

@ -17,6 +17,8 @@ namespace Microsoft.AspNetCore.Cors.Internal
private static readonly Action<ILogger, string, Exception> _accessControlMethodNotAllowed; private static readonly Action<ILogger, string, Exception> _accessControlMethodNotAllowed;
private static readonly Action<ILogger, string, Exception> _requestHeaderNotAllowed; private static readonly Action<ILogger, string, Exception> _requestHeaderNotAllowed;
private static readonly Action<ILogger, Exception> _failedToSetCorsHeaders; private static readonly Action<ILogger, Exception> _failedToSetCorsHeaders;
private static readonly Action<ILogger, Exception> _noCorsPolicyFound;
private static readonly Action<ILogger, Exception> _insecureConfiguration;
static CORSLoggerExtensions() static CORSLoggerExtensions()
{ {
@ -64,6 +66,16 @@ namespace Microsoft.AspNetCore.Cors.Internal
LogLevel.Warning, LogLevel.Warning,
9, 9,
"Failed to apply CORS Response headers."); "Failed to apply CORS Response headers.");
_noCorsPolicyFound = LoggerMessage.Define(
LogLevel.Information,
new EventId(10, "NoCorsPolicyFound"),
"No CORS policy found for the specified request.");
_insecureConfiguration = LoggerMessage.Define(
LogLevel.Warning,
new EventId(11, "CorsInsecureConfiguration"),
"The CORS protocol does not allow specifying a wildcard (any) origin and credentials at the same time. Configure the policy by listing individual origins if credentials needs to be supported.");
} }
public static void IsPreflightRequest(this ILogger logger) public static void IsPreflightRequest(this ILogger logger)
@ -110,5 +122,15 @@ namespace Microsoft.AspNetCore.Cors.Internal
{ {
_failedToSetCorsHeaders(logger, exception); _failedToSetCorsHeaders(logger, exception);
} }
public static void NoCorsPolicyFound(this ILogger logger)
{
_noCorsPolicyFound(logger, null);
}
public static void InsecureConfiguration(this ILogger logger)
{
_insecureConfiguration(logger, null);
}
} }
} }

Просмотреть файл

@ -0,0 +1,45 @@
// 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.Text;
using Xunit.Sdk;
namespace FunctionalTests
{
public class Assert : Xunit.Assert
{
public static void Success(in ProcessResult processResult)
{
if (processResult.ExitCode != 0)
{
throw new ProcessAssertException(processResult);
}
}
private class ProcessAssertException : XunitException
{
public ProcessAssertException(in ProcessResult processResult)
{
Result = processResult;
}
public ProcessResult Result { get; }
public override string Message
{
get
{
var message = new StringBuilder();
message.Append(Result.ProcessStartInfo.FileName);
message.Append(" ");
message.Append(Result.ProcessStartInfo.Arguments);
message.Append($" exited with {Result.ExitCode}.");
message.AppendLine();
message.AppendLine();
message.Append(Result.Output);
return message.ToString();
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,144 @@
// 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 System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.IntegrationTesting;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
using Xunit.Abstractions;
namespace FunctionalTests
{
public class CorsMiddlewareFunctionalTests : LoggedTest
{
public CorsMiddlewareFunctionalTests(ITestOutputHelper output)
: base(output)
{
Output = output;
}
public ITestOutputHelper Output { get; }
[Fact]
public async Task RunClientTests()
{
using (StartLog(out var loggerFactory))
using (var deploymentResult = await CreateDeployments(loggerFactory))
{
ProcessStartInfo processStartInfo;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
processStartInfo = new ProcessStartInfo
{
FileName = "cmd",
Arguments = "/c npm test --no-color",
};
}
else
{
processStartInfo = new ProcessStartInfo
{
FileName = "npm",
Arguments = "test",
};
}
// Act
var result = await ProcessManager.RunProcessAsync(processStartInfo);
// Assert
Assert.Success(result);
Assert.Contains("Test Suites: 1 passed, 1 total", result.Output);
}
}
private static async Task<SamplesDeploymentResult> CreateDeployments(ILoggerFactory loggerFactory)
{
var solutionPath = TestPathUtilities.GetSolutionRootDirectory("CORS");
var runtimeFlavor = GetRuntimeFlavor();
var applicationType = runtimeFlavor == RuntimeFlavor.Clr ? ApplicationType.Standalone : ApplicationType.Portable;
var configuration =
#if RELEASE
"Release";
#else
"Debug";
#endif
var destinationParameters = new DeploymentParameters
{
RuntimeFlavor = runtimeFlavor,
ServerType = ServerType.Kestrel,
ApplicationPath = Path.Combine(solutionPath, "samples", "SampleDestination"),
PublishApplicationBeforeDeployment = false,
ApplicationType = applicationType,
Configuration = configuration,
};
var destinationFactory = ApplicationDeployerFactory.Create(destinationParameters, loggerFactory);
var destinationDeployment = await destinationFactory.DeployAsync();
var originParameters = new DeploymentParameters
{
RuntimeFlavor = runtimeFlavor,
ServerType = ServerType.Kestrel,
ApplicationPath = Path.Combine(solutionPath, "samples", "SampleOrigin"),
PublishApplicationBeforeDeployment = false,
ApplicationType = applicationType,
Configuration = configuration,
};
var originFactory = ApplicationDeployerFactory.Create(originParameters, loggerFactory);
var originDeployment = await originFactory.DeployAsync();
return new SamplesDeploymentResult(originFactory, originDeployment, destinationFactory, destinationDeployment);
}
private static RuntimeFlavor GetRuntimeFlavor()
{
#if NET461
return RuntimeFlavor.Clr;
#elif NETCOREAPP2_2
return RuntimeFlavor.CoreClr;
#else
#error Target frameworks need to be updated
#endif
}
private readonly struct SamplesDeploymentResult : IDisposable
{
public SamplesDeploymentResult(
ApplicationDeployer originDeployer,
DeploymentResult originResult,
ApplicationDeployer destinationDeployer,
DeploymentResult destinationResult)
{
OriginDeployer = originDeployer;
OriginResult = originResult;
DestinationDeployer = destinationDeployer;
DestinationResult = destinationResult;
}
public ApplicationDeployer OriginDeployer { get; }
public DeploymentResult OriginResult { get; }
public ApplicationDeployer DestinationDeployer { get; }
public DeploymentResult DestinationResult { get; }
public void Dispose()
{
OriginDeployer.Dispose();
DestinationDeployer.Dispose();
}
}
}
}

Просмотреть файл

@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<Compile Remove="node_modules\**" />
<EmbeddedResource Remove="node_modules\**" />
<None Remove="node_modules\**" />
</ItemGroup>
<ItemGroup>
<!-- We don't need anything in this assembly, we just want to make sure it's built -->
<ProjectReference Include="..\..\samples\SampleOrigin\SampleOrigin.csproj" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\..\samples\SampleDestination\SampleDestination.csproj" ReferenceOutputAssembly="false" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Server.IntegrationTesting" Version="$(MicrosoftAspNetCoreServerIntegrationTestingPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Testing" Version="$(MicrosoftExtensionsLoggingTestingPackageVersion)" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPackageVersion)" />
<PackageReference Include="xunit.analyzers" Version="$(XunitAnalyzersPackageVersion)" />
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitRunnerVisualstudioPackageVersion)" />
<PackageReference Include="xunit" Version="$(XunitPackageVersion)" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,101 @@
// 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 System.Diagnostics;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace FunctionalTests
{
internal static class ProcessManager
{
private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(60);
public static Task<ProcessResult> RunProcessAsync(ProcessStartInfo processStartInfo)
{
processStartInfo.UseShellExecute = false;
processStartInfo.RedirectStandardError = true;
processStartInfo.RedirectStandardOutput = true;
var process = new Process()
{
StartInfo = processStartInfo,
EnableRaisingEvents = true,
};
var output = new StringBuilder();
var outputLock = new object();
process.ErrorDataReceived += Process_ErrorDataReceived;
process.OutputDataReceived += Process_OutputDataReceived;
process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();
var timeoutTask = Task.Delay(Timeout).ContinueWith((t) =>
{
// Don't timeout during debug sessions
while (Debugger.IsAttached)
{
Thread.Sleep(TimeSpan.FromSeconds(1));
}
if (process.HasExited)
{
// This will happen on success, the 'real' task has already completed so this value will
// never be visible.
return (ProcessResult)default;
}
// This is a timeout.
process.Kill();
throw new TimeoutException($"command '${process.StartInfo.FileName} {process.StartInfo.Arguments}' timed out after {Timeout}.");
});
var waitTask = Task.Run(() =>
{
// We need to use two WaitForExit calls to ensure that all of the output/events are processed. Previously
// this code used Process.Exited, which could result in us missing some output due to the ordering of
// events.
//
// See the remarks here: https://msdn.microsoft.com/en-us/library/ty0d8k56(v=vs.110).aspx
if (!process.WaitForExit(int.MaxValue))
{
// unreachable - the timeoutTask will kill the process before this happens.
throw new TimeoutException();
}
process.WaitForExit();
string outputString;
lock (outputLock)
{
outputString = output.ToString();
}
return new ProcessResult(processStartInfo, process.ExitCode, outputString);
});
return Task.WhenAny<ProcessResult>(waitTask, timeoutTask).Unwrap();
void Process_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
lock (outputLock)
{
output.AppendLine(e.Data);
}
}
void Process_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
lock (outputLock)
{
output.AppendLine(e.Data);
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,21 @@
// 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.Diagnostics;
namespace FunctionalTests
{
public readonly struct ProcessResult
{
public ProcessResult(ProcessStartInfo processStartInfo, int exitCode, string output)
{
ProcessStartInfo = processStartInfo;
ExitCode = exitCode;
Output = output;
}
public ProcessStartInfo ProcessStartInfo { get; }
public int ExitCode { get; }
public string Output { get; }
}
}

5637
test/FunctionalTests/package-lock.json сгенерированный Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,10 @@
{
"devDependencies": {
"jest": "^23.6.0",
"puppeteer": "^1.8.0"
},
"dependencies": {},
"scripts": {
"test": "jest"
}
}

Просмотреть файл

@ -0,0 +1,280 @@
const puppeteer = require('puppeteer');
const os = require("os");
const hostname = os.hostname();
const corsServerPath = `http://${hostname}:9000`;
// e.g., npm test --debug
// In debug mode we show the editor, slow down operations, and increase the timeout for each test
const debug = process.env.npm_config_debug || false;
jest.setTimeout(debug ? 60000 : 30000);
let browser;
beforeAll(async () => {
const options = debug ?
{ headless: false, slowMo: 100 } :
{ args: ['--no-sandbox'] };
browser = await puppeteer.launch(options);
expect(browser).toBeDefined();
});
afterAll(async () => {
await browser.close();
});
describe('CORS allowed origin tests ', () => {
const testPagePath = `http://${hostname}:9001/`;
let page;
beforeAll(async () => {
page = await browser.newPage();
await page.goto(testPagePath);
});
test('allows simple GET requests', async () => {
const result = await page.evaluate(async (corsServerPath) => {
const url = `${corsServerPath}/allow-origin`;
const options = { method: 'GET', mode: 'cors' };
const response = await fetch(url, options);
return response.status;
}, corsServerPath);
expect(result).toBe(200);
});
test('allows simple PUT requests when any method is allowed', async () => {
const result = await page.evaluate(async (corsServerPath) => {
const url = `${corsServerPath}/allow-origin`;
const options = { method: 'PUT', mode: 'cors' };
const response = await fetch(url, options);
return response.status;
}, corsServerPath);
expect(result).toBe(200);
});
// This one is weird - although the server performs a preflight request and receives a Access-Control-Allow-Methods: PUT,
// the browser happily ignores the disallowed POST method.
test('allows POST requests when not explicitly allowed', async () => {
const result = await page.evaluate(async (corsServerPath) => {
const url = `${corsServerPath}/allow-header-method`;
const options = {
method: 'POST',
mode: 'cors',
body: JSON.stringify({ hello: 'world' }),
headers: new Headers({ "X-Test": "value", 'Content-Type': 'application/json' })
};
const response = await fetch(url, options);
return response.status;
}, corsServerPath);
expect(result).toBe(200);
});
test('allows header to be sent when allowed', async () => {
const result = await page.evaluate(async (corsServerPath) => {
const url = `${corsServerPath}/allow-header-method`;
const options = { method: 'PUT', mode: 'cors', headers: new Headers({ "X-Test": "value", 'Content-Type': 'application/json' }) };
const response = await fetch(url, options);
return response.status;
}, corsServerPath);
expect(result).toBe(200);
});
test('does not allow disallowed HTTP Methods', async () => {
expect.assertions(1);
try {
await page.evaluate(async (corsServerPath) => {
const url = `${corsServerPath}/allow-header-method`;
const options = { method: 'DELETE', mode: 'cors', headers: new Headers({ "X-Test": "value", 'Content-Type': 'application/json' }) };
return await fetch(url, options);
}, corsServerPath);
} catch (e) {
expect(e).toBeDefined();
}
});
test('does not allow disallowed header', async () => {
expect.assertions(1);
try {
await page.evaluate(async (corsServerPath) => {
const url = `${corsServerPath}/allow-header-method`;
const options = { method: 'PUT', mode: 'cors', headers: new Headers({ "X-Not-Test": "value", 'Content-Type': 'application/json' }) };
return await fetch(url, options);
}, corsServerPath);
} catch (e) {
expect(e).toBeDefined();
}
});
test('does not allow fetch with credentials in non-Preflighted request', async () => {
expect.assertions(1);
try {
await page.evaluate(async (corsServerPath) => {
const url = `${corsServerPath}/allow-origin`;
const options = { method: 'POST', mode: 'cors', credentials: 'include' };
return await fetch(url, options);
}, corsServerPath);
} catch (e) {
expect(e).toBeDefined();
}
});
test('does not allow fetch with credentials in Preflighted request', async () => {
expect.assertions(1);
try {
await page.evaluate(async (corsServerPath) => {
const url = `${corsServerPath}/allow-origin`;
const options = { method: 'PUT', mode: 'cors', credentials: 'include' };
return await fetch(url, options);
}, corsServerPath);
} catch (e) {
expect(e).toBeDefined();
}
});
test('allows request with credentials', async () => {
const result = await page.evaluate(async (corsServerPath) => {
const url = `${corsServerPath}/allow-credentials`;
const options = { method: 'GET', mode: 'cors', credentials: 'include' };
const response = await fetch(url, options);
return response.status;
}, corsServerPath);
expect(result).toBe(200);
});
test('allows Preflighted request with credentials', async () => {
const result = await page.evaluate(async (corsServerPath) => {
const url = `${corsServerPath}/allow-credentials`;
const options = { method: 'PUT', mode: 'cors', credentials: 'include' };
const response = await fetch(url, options);
return response.status;
}, corsServerPath);
expect(result).toBe(200);
});
test('disallows accessing header when not included in exposed-header', async () => {
const result = await page.evaluate(async (corsServerPath) => {
const url = `${corsServerPath}/exposed-header`;
const options = { method: 'GET', mode: 'cors' };
const response = await fetch(url, options);
try {
return response.headers.get('x-disallowedheader');
} catch (e) {
return null;
}
}, corsServerPath);
expect(result).toBeNull();
});
test('allows accessing header when included in exposed-header', async () => {
const result = await page.evaluate(async (corsServerPath) => {
const url = `${corsServerPath}/exposed-header`;
const options = { method: 'GET', mode: 'cors' };
const response = await fetch(url, options);
try {
return response.headers.get('x-allowedheader');
} catch (e) {
return e;
}
}, corsServerPath);
expect(result).toBe("Test-Value");
});
});
describe('CORS disallowed origin tests ', () => {
const testPagePath = `http://${hostname}:9002/`;
let page;
beforeAll(async () => {
page = await browser.newPage();
await page.goto(testPagePath);
});
test('allow opaque requests without CORS', async () => {
const result = await page.evaluate(async (corsServerPath) => {
const url = `${corsServerPath}/allow-origin`;
const options = { method: 'GET', mode: 'no-cors' };
// The request will succeed, but we get an opaque filtered response (https://fetch.spec.whatwg.org/#concept-filtered-response).
const response = await fetch(url, options);
return response.type;
}, corsServerPath);
expect(result).toBe("opaque");
});
test('does not allow requests when origin is disallowed', async () => {
expect.assertions(1);
try {
await page.evaluate(async (corsServerPath) => {
const url = `${corsServerPath}/allow-origin`;
const options = { method: 'GET', mode: 'cors' };
return await fetch(url, options);
}, corsServerPath);
} catch (e) {
expect(e).toBeDefined();
}
});
test('does not allow preflight requests when origin is disallowed', async () => {
expect.assertions(1);
try {
await page.evaluate(async (corsServerPath) => {
const url = `${corsServerPath}/allow-origin`;
const options = { method: 'PUT', mode: 'cors' };
return await fetch(url, options);
}, corsServerPath);
} catch (e) {
expect(e).toBeDefined();
}
});
test('allow requests to any origin endpoint', async () => {
const result = await page.evaluate(async (corsServerPath) => {
const url = `${corsServerPath}/allow-all`;
const options = { method: 'PUT', mode: 'cors' };
const response = await fetch(url, options);
return response.status;
}, corsServerPath);
expect(result).toBe(200);
});
test('does not allow requests to any origin endpoint with credentials', async () => {
expect.assertions(1);
try {
await page.evaluate(async (corsServerPath) => {
const url = `${corsServerPath}/allow-all`;
const options = { method: 'PUT', mode: 'cors', headers: new Headers({ "X-Test": "value", 'Content-Type': 'application/json' }), credentials: 'include' };
const response = await fetch(url, options);
return response.status;
}, corsServerPath);
} catch (e) {
expect(e).toBeDefined();
}
});
});

Просмотреть файл

@ -1,98 +0,0 @@
// 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.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.Cors.Infrastructure
{
public class CorsMiddlewareFunctionalTests : IClassFixture<CorsTestFixture<CorsMiddlewareWebSite.Startup>>
{
public CorsMiddlewareFunctionalTests(CorsTestFixture<CorsMiddlewareWebSite.Startup> fixture)
{
Client = fixture.Client;
}
public HttpClient Client { get; }
[Theory]
[InlineData("GET")]
[InlineData("HEAD")]
[InlineData("POST")]
public async Task ResourceWithSimpleRequestPolicy_Allows_SimpleRequests(string method)
{
// Arrange
var path = "/CorsMiddleware/EC6AA70D-BA3E-4B71-A87F-18625ADDB2BD";
var origin = "http://example.com";
var request = new HttpRequestMessage(new HttpMethod(method), path);
request.Headers.Add(CorsConstants.Origin, origin);
// Act
var response = await Client.SendAsync(request);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
Assert.Equal(path, content);
var responseHeaders = response.Headers;
var header = Assert.Single(response.Headers);
Assert.Equal(CorsConstants.AccessControlAllowOrigin, header.Key);
Assert.Equal(new[] { "http://example.com" }, header.Value.ToArray());
}
[Theory]
[InlineData("GET")]
[InlineData("HEAD")]
[InlineData("POST")]
[InlineData("PUT")]
public async Task PolicyFailed_Disallows_PreFlightRequest(string method)
{
// Arrange
var path = "/CorsMiddleware/9B8BB9C6-5BF2-4255-A636-DCB450D51AAE";
var request = new HttpRequestMessage(new HttpMethod(CorsConstants.PreflightHttpMethod), path);
// Adding a custom header makes it a non-simple request.
request.Headers.Add(CorsConstants.Origin, "http://example.com");
request.Headers.Add(CorsConstants.AccessControlRequestMethod, method);
request.Headers.Add(CorsConstants.AccessControlRequestHeaders, "Custom");
// Act
var response = await Client.SendAsync(request);
// Assert
// Middleware applied the policy and since that did not pass, there were no access control headers.
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
Assert.Empty(response.Headers);
// It should short circuit and hence no result.
var content = await response.Content.ReadAsStringAsync();
Assert.Equal(string.Empty, content);
}
[Fact]
public async Task PolicyFailed_Allows_ActualRequest_WithMissingResponseHeaders()
{
// Arrange
var path = "/CorsMiddleware/1E6C6F4D-1E1C-450E-8BD0-73DBF089A78F";
var request = new HttpRequestMessage(HttpMethod.Put, path);
// Adding a custom header makes it a non simple request.
request.Headers.Add(CorsConstants.Origin, "http://example2.com");
// Act
var response = await Client.SendAsync(request);
// Assert
// Middleware applied the policy and since that did not pass, there were no access control headers.
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Empty(response.Headers);
// It still has executed the action.
var content = await response.Content.ReadAsStringAsync();
Assert.Equal(path, content);
}
}
}

Просмотреть файл

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
@ -10,7 +10,7 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.TestHost; using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions;
using Moq; using Moq;
using Xunit; using Xunit;
@ -18,6 +18,8 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
{ {
public class CorsMiddlewareTests public class CorsMiddlewareTests
{ {
private const string OriginUrl = "http://api.example.com";
[Theory] [Theory]
[InlineData("PuT")] [InlineData("PuT")]
[InlineData("PUT")] [InlineData("PUT")]
@ -28,7 +30,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
.Configure(app => .Configure(app =>
{ {
app.UseCors(builder => app.UseCors(builder =>
builder.WithOrigins("http://localhost:5001") builder.WithOrigins(OriginUrl)
.WithMethods("PUT")); .WithMethods("PUT"));
app.Run(async context => app.Run(async context =>
{ {
@ -42,14 +44,14 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
// Act // Act
// Actual request. // Actual request.
var response = await server.CreateRequest("/") var response = await server.CreateRequest("/")
.AddHeader(CorsConstants.Origin, "http://localhost:5001") .AddHeader(CorsConstants.Origin, OriginUrl)
.SendAsync(accessControlRequestMethod); .SendAsync(accessControlRequestMethod);
// Assert // Assert
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
Assert.Single(response.Headers); Assert.Single(response.Headers);
Assert.Equal("Cross origin response", await response.Content.ReadAsStringAsync()); Assert.Equal("Cross origin response", await response.Content.ReadAsStringAsync());
Assert.Equal("http://localhost:5001", response.Headers.GetValues(CorsConstants.AccessControlAllowOrigin).FirstOrDefault()); Assert.Equal(OriginUrl, response.Headers.GetValues(CorsConstants.AccessControlAllowOrigin).FirstOrDefault());
} }
} }
@ -61,7 +63,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
.Configure(app => .Configure(app =>
{ {
app.UseCors(builder => app.UseCors(builder =>
builder.WithOrigins("http://localhost:5001") builder.WithOrigins(OriginUrl)
.WithMethods("PUT") .WithMethods("PUT")
.WithHeaders("Header1") .WithHeaders("Header1")
.WithExposedHeaders("AllowedHeader")); .WithExposedHeaders("AllowedHeader"));
@ -77,14 +79,14 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
// Act // Act
// Actual request. // Actual request.
var response = await server.CreateRequest("/") var response = await server.CreateRequest("/")
.AddHeader(CorsConstants.Origin, "http://localhost:5001") .AddHeader(CorsConstants.Origin, OriginUrl)
.SendAsync("PUT"); .SendAsync("PUT");
// Assert // Assert
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
Assert.Equal(2, response.Headers.Count()); Assert.Equal(2, response.Headers.Count());
Assert.Equal("Cross origin response", await response.Content.ReadAsStringAsync()); Assert.Equal("Cross origin response", await response.Content.ReadAsStringAsync());
Assert.Equal("http://localhost:5001", response.Headers.GetValues(CorsConstants.AccessControlAllowOrigin).FirstOrDefault()); Assert.Equal(OriginUrl, response.Headers.GetValues(CorsConstants.AccessControlAllowOrigin).FirstOrDefault());
Assert.Equal("AllowedHeader", response.Headers.GetValues(CorsConstants.AccessControlExposeHeaders).FirstOrDefault()); Assert.Equal("AllowedHeader", response.Headers.GetValues(CorsConstants.AccessControlExposeHeaders).FirstOrDefault());
} }
} }
@ -96,7 +98,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
{ {
// Arrange // Arrange
var policy = new CorsPolicy(); var policy = new CorsPolicy();
policy.Origins.Add("http://localhost:5001"); policy.Origins.Add(OriginUrl);
policy.Methods.Add("PUT"); policy.Methods.Add("PUT");
var hostBuilder = new WebHostBuilder() var hostBuilder = new WebHostBuilder()
@ -121,13 +123,13 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
// Act // Act
// Preflight request. // Preflight request.
var response = await server.CreateRequest("/") var response = await server.CreateRequest("/")
.AddHeader(CorsConstants.Origin, "http://localhost:5001") .AddHeader(CorsConstants.Origin, OriginUrl)
.SendAsync(preflightMethod); .SendAsync(preflightMethod);
// Assert // Assert
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
Assert.Single(response.Headers); Assert.Single(response.Headers);
Assert.Equal("http://localhost:5001", response.Headers.GetValues(CorsConstants.AccessControlAllowOrigin).FirstOrDefault()); Assert.Equal(OriginUrl, response.Headers.GetValues(CorsConstants.AccessControlAllowOrigin).FirstOrDefault());
} }
} }
@ -136,7 +138,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
{ {
// Arrange // Arrange
var policy = new CorsPolicy(); var policy = new CorsPolicy();
policy.Origins.Add("http://localhost:5001"); policy.Origins.Add(OriginUrl);
policy.Methods.Add("PUT"); policy.Methods.Add("PUT");
policy.Headers.Add("Header1"); policy.Headers.Add("Header1");
policy.ExposedHeaders.Add("AllowedHeader"); policy.ExposedHeaders.Add("AllowedHeader");
@ -163,27 +165,105 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
// Act // Act
// Preflight request. // Preflight request.
var response = await server.CreateRequest("/") var response = await server.CreateRequest("/")
.AddHeader(CorsConstants.Origin, "http://localhost:5001") .AddHeader(CorsConstants.Origin, OriginUrl)
.AddHeader(CorsConstants.AccessControlRequestMethod, "PUT") .AddHeader(CorsConstants.AccessControlRequestMethod, "PUT")
.SendAsync(CorsConstants.PreflightHttpMethod); .SendAsync(CorsConstants.PreflightHttpMethod);
// Assert // Assert
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
Assert.Equal(2, response.Headers.Count()); Assert.Collection(
Assert.Equal("http://localhost:5001", response.Headers.GetValues(CorsConstants.AccessControlAllowOrigin).FirstOrDefault()); response.Headers.OrderBy(h => h.Key),
Assert.Equal("PUT", response.Headers.GetValues(CorsConstants.AccessControlAllowMethods).FirstOrDefault()); kvp =>
{
Assert.Equal(CorsConstants.AccessControlAllowHeaders, kvp.Key);
Assert.Equal(new[] { "Header1" }, kvp.Value);
},
kvp =>
{
Assert.Equal(CorsConstants.AccessControlAllowMethods, kvp.Key);
Assert.Equal(new[] { "PUT" }, kvp.Value);
},
kvp =>
{
Assert.Equal(CorsConstants.AccessControlAllowOrigin, kvp.Key);
Assert.Equal(new[] { OriginUrl }, kvp.Value);
});
} }
} }
[Fact] [Fact]
public async Task PreFlightRequest_DoesNotMatchPolicy_DoesNotSetHeaders() public async Task PreFlight_WithCredentialsAllowed_ReturnsWildcardValues()
{
// Arrange
var policy = new CorsPolicyBuilder(OriginUrl)
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials()
.Build();
var hostBuilder = new WebHostBuilder()
.Configure(app =>
{
app.UseCors("customPolicy");
app.Run(async context =>
{
await context.Response.WriteAsync("Cross origin response");
});
})
.ConfigureServices(services =>
{
services.AddCors(options =>
{
options.AddPolicy("customPolicy", policy);
});
});
using (var server = new TestServer(hostBuilder))
{
// Act
// Preflight request.
var response = await server.CreateRequest("/")
.AddHeader(CorsConstants.Origin, OriginUrl)
.AddHeader(CorsConstants.AccessControlRequestMethod, "PUT")
.AddHeader(CorsConstants.AccessControlRequestHeaders, "X-Test1,X-Test2")
.SendAsync(CorsConstants.PreflightHttpMethod);
// Assert
response.EnsureSuccessStatusCode();
Assert.Collection(
response.Headers.OrderBy(h => h.Key),
kvp =>
{
Assert.Equal(CorsConstants.AccessControlAllowCredentials, kvp.Key);
Assert.Equal(new[] { "true" }, kvp.Value);
},
kvp =>
{
Assert.Equal(CorsConstants.AccessControlAllowHeaders, kvp.Key);
Assert.Equal(new[] { "*" }, kvp.Value);
},
kvp =>
{
Assert.Equal(CorsConstants.AccessControlAllowMethods, kvp.Key);
Assert.Equal(new[] { "*" }, kvp.Value);
},
kvp =>
{
Assert.Equal(CorsConstants.AccessControlAllowOrigin, kvp.Key);
Assert.Equal(new[] { OriginUrl }, kvp.Value);
});
}
}
[Fact]
public async Task PreFlightRequest_DoesNotMatchPolicy_DoesNotSetHeadersAndReturnsForbidden()
{ {
// Arrange // Arrange
var hostBuilder = new WebHostBuilder() var hostBuilder = new WebHostBuilder()
.Configure(app => .Configure(app =>
{ {
app.UseCors(builder => app.UseCors(builder =>
builder.WithOrigins("http://localhost:5001") builder.WithOrigins(OriginUrl)
.WithMethods("PUT") .WithMethods("PUT")
.WithHeaders("Header1") .WithHeaders("Header1")
.WithExposedHeaders("AllowedHeader")); .WithExposedHeaders("AllowedHeader"));
@ -199,7 +279,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
// Act // Act
// Preflight request. // Preflight request.
var response = await server.CreateRequest("/") var response = await server.CreateRequest("/")
.AddHeader(CorsConstants.Origin, "http://localhost:5002") .AddHeader(CorsConstants.Origin, "http://test.example.com")
.AddHeader(CorsConstants.AccessControlRequestMethod, "PUT") .AddHeader(CorsConstants.AccessControlRequestMethod, "PUT")
.SendAsync(CorsConstants.PreflightHttpMethod); .SendAsync(CorsConstants.PreflightHttpMethod);
@ -217,7 +297,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
.Configure(app => .Configure(app =>
{ {
app.UseCors(builder => app.UseCors(builder =>
builder.WithOrigins("http://localhost:5001") builder.WithOrigins(OriginUrl)
.WithMethods("PUT") .WithMethods("PUT")
.WithHeaders("Header1") .WithHeaders("Header1")
.WithExposedHeaders("AllowedHeader")); .WithExposedHeaders("AllowedHeader"));
@ -233,7 +313,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
// Act // Act
// Actual request. // Actual request.
var response = await server.CreateRequest("/") var response = await server.CreateRequest("/")
.AddHeader(CorsConstants.Origin, "http://localhost:5002") .AddHeader(CorsConstants.Origin, "http://test.example.com")
.SendAsync("PUT"); .SendAsync("PUT");
// Assert // Assert
@ -248,7 +328,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
// Arrange // Arrange
var corsService = Mock.Of<ICorsService>(); var corsService = Mock.Of<ICorsService>();
var mockProvider = new Mock<ICorsPolicyProvider>(); var mockProvider = new Mock<ICorsPolicyProvider>();
var loggerFactory = Mock.Of<ILoggerFactory>(); var loggerFactory = NullLoggerFactory.Instance;
mockProvider.Setup(o => o.GetPolicyAsync(It.IsAny<HttpContext>(), It.IsAny<string>())) mockProvider.Setup(o => o.GetPolicyAsync(It.IsAny<HttpContext>(), It.IsAny<string>()))
.Returns(Task.FromResult<CorsPolicy>(null)) .Returns(Task.FromResult<CorsPolicy>(null))
.Verifiable(); .Verifiable();
@ -278,7 +358,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
// Arrange // Arrange
var corsService = Mock.Of<ICorsService>(); var corsService = Mock.Of<ICorsService>();
var mockProvider = new Mock<ICorsPolicyProvider>(); var mockProvider = new Mock<ICorsPolicyProvider>();
var loggerFactory = Mock.Of<ILoggerFactory>(); var loggerFactory = NullLoggerFactory.Instance;
mockProvider.Setup(o => o.GetPolicyAsync(It.IsAny<HttpContext>(), It.IsAny<string>())) mockProvider.Setup(o => o.GetPolicyAsync(It.IsAny<HttpContext>(), It.IsAny<string>()))
.Returns(Task.FromResult<CorsPolicy>(null)) .Returns(Task.FromResult<CorsPolicy>(null))
.Verifiable(); .Verifiable();
@ -324,7 +404,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
options.AddDefaultPolicy(policyBuilder => options.AddDefaultPolicy(policyBuilder =>
{ {
policyBuilder policyBuilder
.WithOrigins("http://localhost:5001") .WithOrigins(OriginUrl)
.WithMethods("PUT") .WithMethods("PUT")
.WithHeaders("Header1") .WithHeaders("Header1")
.WithExposedHeaders("AllowedHeader") .WithExposedHeaders("AllowedHeader")
@ -333,7 +413,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
options.AddPolicy("policy2", policyBuilder => options.AddPolicy("policy2", policyBuilder =>
{ {
policyBuilder policyBuilder
.WithOrigins("http://localhost:5002") .WithOrigins("http://test.example.com")
.Build(); .Build();
}); });
}); });
@ -344,15 +424,29 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
// Act // Act
// Preflight request. // Preflight request.
var response = await server.CreateRequest("/") var response = await server.CreateRequest("/")
.AddHeader(CorsConstants.Origin, "http://localhost:5001") .AddHeader(CorsConstants.Origin, OriginUrl)
.AddHeader(CorsConstants.AccessControlRequestMethod, "PUT") .AddHeader(CorsConstants.AccessControlRequestMethod, "PUT")
.SendAsync(CorsConstants.PreflightHttpMethod); .SendAsync(CorsConstants.PreflightHttpMethod);
// Assert // Assert
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
Assert.Equal(2, response.Headers.Count()); Assert.Collection(
Assert.Equal("http://localhost:5001", response.Headers.GetValues(CorsConstants.AccessControlAllowOrigin).FirstOrDefault()); response.Headers.OrderBy(h => h.Key),
Assert.Equal("PUT", response.Headers.GetValues(CorsConstants.AccessControlAllowMethods).FirstOrDefault()); kvp =>
{
Assert.Equal(CorsConstants.AccessControlAllowHeaders, kvp.Key);
Assert.Equal(new[] { "Header1" }, kvp.Value);
},
kvp =>
{
Assert.Equal(CorsConstants.AccessControlAllowMethods, kvp.Key);
Assert.Equal(new[] { "PUT" }, kvp.Value);
},
kvp =>
{
Assert.Equal(CorsConstants.AccessControlAllowOrigin, kvp.Key);
Assert.Equal(new[] { OriginUrl }, kvp.Value);
});
} }
} }
@ -364,7 +458,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
.Configure(app => .Configure(app =>
{ {
app.UseCors(builder => app.UseCors(builder =>
builder.WithOrigins("http://localhost:5001") builder.WithOrigins(OriginUrl)
.WithMethods("PUT") .WithMethods("PUT")
.WithHeaders("Header1") .WithHeaders("Header1")
.WithExposedHeaders("AllowedHeader")); .WithExposedHeaders("AllowedHeader"));
@ -381,7 +475,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
// Act // Act
// Actual request. // Actual request.
var response = await server.CreateRequest("/") var response = await server.CreateRequest("/")
.AddHeader(CorsConstants.Origin, "http://localhost:5001") .AddHeader(CorsConstants.Origin, OriginUrl)
.SendAsync("PUT"); .SendAsync("PUT");
// Assert // Assert
@ -391,7 +485,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
kvp => kvp =>
{ {
Assert.Equal(CorsConstants.AccessControlAllowOrigin, kvp.Key); Assert.Equal(CorsConstants.AccessControlAllowOrigin, kvp.Key);
Assert.Equal("http://localhost:5001", Assert.Single(kvp.Value)); Assert.Equal(OriginUrl, Assert.Single(kvp.Value));
}, },
kvp => kvp =>
{ {
@ -432,7 +526,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
}); });
app.UseCors(builder => app.UseCors(builder =>
builder.WithOrigins("http://localhost:5001") builder.WithOrigins(OriginUrl)
.WithMethods("PUT") .WithMethods("PUT")
.WithHeaders("Header1") .WithHeaders("Header1")
.WithExposedHeaders("AllowedHeader")); .WithExposedHeaders("AllowedHeader"));
@ -450,7 +544,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
// Act // Act
// Actual request. // Actual request.
var response = await server.CreateRequest("/") var response = await server.CreateRequest("/")
.AddHeader(CorsConstants.Origin, "http://localhost:5001") .AddHeader(CorsConstants.Origin, OriginUrl)
.SendAsync("PUT"); .SendAsync("PUT");
// Assert // Assert
@ -462,7 +556,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
kvp => kvp =>
{ {
Assert.Equal(CorsConstants.AccessControlAllowOrigin, kvp.Key); Assert.Equal(CorsConstants.AccessControlAllowOrigin, kvp.Key);
Assert.Equal("http://localhost:5001", Assert.Single(kvp.Value)); Assert.Equal(OriginUrl, Assert.Single(kvp.Value));
}, },
kvp => kvp =>
{ {

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -1,33 +0,0 @@
// 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 System.Net.Http;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
namespace Microsoft.AspNetCore.Cors.Infrastructure
{
public class CorsTestFixture<TStartup> : IDisposable
where TStartup : class
{
private readonly TestServer _server;
public CorsTestFixture()
{
var builder = new WebHostBuilder().UseStartup<TStartup>();
_server = new TestServer(builder);
Client = _server.CreateClient();
Client.BaseAddress = new Uri("http://localhost");
}
public HttpClient Client { get; }
public void Dispose()
{
Client.Dispose();
_server.Dispose();
}
}
}

Просмотреть файл

@ -3,6 +3,7 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using Xunit; using Xunit;
namespace Microsoft.AspNetCore.Cors.Infrastructure namespace Microsoft.AspNetCore.Cors.Infrastructure
@ -17,10 +18,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
var policy = new CorsPolicy(); var policy = new CorsPolicy();
options.AddPolicy(options.DefaultPolicyName, policy); options.AddPolicy(options.DefaultPolicyName, policy);
var corsOptions = new TestCorsOptions var corsOptions = Options.Create(options);
{
Value = options
};
var policyProvider = new DefaultCorsPolicyProvider(corsOptions); var policyProvider = new DefaultCorsPolicyProvider(corsOptions);
// Act // Act
@ -40,10 +38,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
var policy = new CorsPolicy(); var policy = new CorsPolicy();
options.AddPolicy(policyName, policy); options.AddPolicy(policyName, policy);
var corsOptions = new TestCorsOptions var corsOptions = Options.Create(options);
{
Value = options
};
var policyProvider = new DefaultCorsPolicyProvider(corsOptions); var policyProvider = new DefaultCorsPolicyProvider(corsOptions);
// Act // Act

Просмотреть файл

@ -1,12 +0,0 @@
// 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.Extensions.Options;
namespace Microsoft.AspNetCore.Cors.Infrastructure
{
public class TestCorsOptions : IOptions<CorsOptions>
{
public CorsOptions Value { get; set; }
}
}

Просмотреть файл

@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
public void TestIsSubdomainOf(Uri subdomain, Uri domain) public void TestIsSubdomainOf(Uri subdomain, Uri domain)
{ {
// Act // Act
bool isSubdomain = UriHelpers.IsSubdomainOf(subdomain, domain); var isSubdomain = UriHelpers.IsSubdomainOf(subdomain, domain);
// Assert // Assert
Assert.True(isSubdomain); Assert.True(isSubdomain);
@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
public void TestIsSubdomainOf_ReturnsFalse_WhenNotSubdomain(Uri subdomain, Uri domain) public void TestIsSubdomainOf_ReturnsFalse_WhenNotSubdomain(Uri subdomain, Uri domain)
{ {
// Act // Act
bool isSubdomain = UriHelpers.IsSubdomainOf(subdomain, domain); var isSubdomain = UriHelpers.IsSubdomainOf(subdomain, domain);
// Assert // Assert
Assert.False(isSubdomain); Assert.False(isSubdomain);