Remove filtering from CorsService
* Add browser based integration tests
This commit is contained in:
Родитель
b9166f14f1
Коммит
2690a3f621
|
@ -5,6 +5,9 @@ mono: none
|
|||
os:
|
||||
- linux
|
||||
- osx
|
||||
env:
|
||||
global:
|
||||
- TRAVIS_NODE_VERSION: 8.9.3
|
||||
osx_image: xcode8.2
|
||||
addons:
|
||||
apt:
|
||||
|
@ -16,6 +19,7 @@ branches:
|
|||
- /^release\/.*$/
|
||||
- /^(.*\/)?ci-.*$/
|
||||
before_install:
|
||||
- nvm install $TRAVIS_NODE_VERSION
|
||||
- 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/lib/; fi
|
||||
|
|
|
@ -10,4 +10,29 @@ resources:
|
|||
ref: refs/heads/release/2.2
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
|
7
CORS.sln
7
CORS.sln
|
@ -45,6 +45,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||
version.xml = version.xml
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FunctionalTests", "test\FunctionalTests\FunctionalTests.csproj", "{99EB6889-C7D8-4BC3-AF99-B966B9E64C81}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
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}.Release|Any CPU.ActiveCfg = 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
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -82,6 +88,7 @@ Global
|
|||
{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}
|
||||
{99EB6889-C7D8-4BC3-AF99-B966B9E64C81} = {F32074C7-087C-46CC-A913-422BFD2D6E0A}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {F9ED9C53-44CD-4853-9621-D028B7B6A431}
|
||||
|
|
|
@ -1,10 +1 @@
|
|||
@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') _
|
||||
|
||||
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
|
||||
// the label 'windows-with-vs'
|
||||
simpleNode('Windows_NT','latest') {
|
||||
simpleNode('Windows.10.Amd64.EnterpriseRS3.ASPNET.Open') {
|
||||
stage ('Checking out source') {
|
||||
checkout scm
|
||||
}
|
||||
|
|
|
@ -3,20 +3,22 @@
|
|||
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Package Versions">
|
||||
<InternalAspNetCoreSdkPackageVersion>2.2.0-preview1-20180928.5</InternalAspNetCoreSdkPackageVersion>
|
||||
<MicrosoftAspNetCoreHttpExtensionsPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreHttpExtensionsPackageVersion>
|
||||
<MicrosoftAspNetCoreServerIISIntegrationPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreServerIISIntegrationPackageVersion>
|
||||
<MicrosoftAspNetCoreServerKestrelPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreServerKestrelPackageVersion>
|
||||
<MicrosoftAspNetCoreTestHostPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreTestHostPackageVersion>
|
||||
<MicrosoftExtensionsConfigurationAbstractionsPackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsConfigurationAbstractionsPackageVersion>
|
||||
<MicrosoftExtensionsDependencyInjectionAbstractionsPackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsDependencyInjectionAbstractionsPackageVersion>
|
||||
<MicrosoftExtensionsLoggingAbstractionsPackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsLoggingAbstractionsPackageVersion>
|
||||
<MicrosoftExtensionsLoggingConsolePackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsLoggingConsolePackageVersion>
|
||||
<MicrosoftExtensionsLoggingTestingPackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsLoggingTestingPackageVersion>
|
||||
<MicrosoftExtensionsOptionsPackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsOptionsPackageVersion>
|
||||
<InternalAspNetCoreSdkPackageVersion>2.2.0-preview2-20181004.6</InternalAspNetCoreSdkPackageVersion>
|
||||
<MicrosoftAspNetCoreHttpExtensionsPackageVersion>2.2.0-preview3-35413</MicrosoftAspNetCoreHttpExtensionsPackageVersion>
|
||||
<MicrosoftAspNetCoreServerIISIntegrationPackageVersion>2.2.0-preview3-35413</MicrosoftAspNetCoreServerIISIntegrationPackageVersion>
|
||||
<MicrosoftAspNetCoreServerIntegrationTestingPackageVersion>0.6.0-preview3-35413</MicrosoftAspNetCoreServerIntegrationTestingPackageVersion>
|
||||
<MicrosoftAspNetCoreServerKestrelPackageVersion>2.2.0-preview3-35413</MicrosoftAspNetCoreServerKestrelPackageVersion>
|
||||
<MicrosoftAspNetCoreStaticFilesPackageVersion>2.2.0-preview3-35413</MicrosoftAspNetCoreStaticFilesPackageVersion>
|
||||
<MicrosoftAspNetCoreTestHostPackageVersion>2.2.0-preview3-35413</MicrosoftAspNetCoreTestHostPackageVersion>
|
||||
<MicrosoftExtensionsConfigurationAbstractionsPackageVersion>2.2.0-preview3-35413</MicrosoftExtensionsConfigurationAbstractionsPackageVersion>
|
||||
<MicrosoftExtensionsDependencyInjectionAbstractionsPackageVersion>2.2.0-preview3-35413</MicrosoftExtensionsDependencyInjectionAbstractionsPackageVersion>
|
||||
<MicrosoftExtensionsLoggingAbstractionsPackageVersion>2.2.0-preview3-35413</MicrosoftExtensionsLoggingAbstractionsPackageVersion>
|
||||
<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>
|
||||
<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>
|
||||
<MoqPackageVersion>4.7.49</MoqPackageVersion>
|
||||
<NETStandardLibrary20PackageVersion>2.0.3</NETStandardLibrary20PackageVersion>
|
||||
|
|
|
@ -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
|
||||
commithash:43faa29f679f47b88689d645b39e6be5e0055d70
|
||||
version:2.2.0-preview2-20181004.6
|
||||
commithash:c04c4b2f5018632647f96210ab01876661302dac
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace SampleDestination
|
|||
{
|
||||
var host = new WebHostBuilder()
|
||||
.UseKestrel()
|
||||
.UseUrls("http://*:5000")
|
||||
.UseUrls("http://+:9000")
|
||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||
.ConfigureLogging(factory => factory.AddConsole())
|
||||
.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.
|
||||
// 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.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
@ -11,6 +15,15 @@ namespace SampleDestination
|
|||
{
|
||||
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)
|
||||
{
|
||||
services.AddCors();
|
||||
|
@ -18,21 +31,60 @@ namespace SampleDestination
|
|||
|
||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
|
||||
{
|
||||
app.UseCors(policy => policy
|
||||
.WithOrigins("http://origin.example.com:5001")
|
||||
.WithMethods("PUT")
|
||||
.WithHeaders("Cache-Control"));
|
||||
|
||||
app.Run(async context =>
|
||||
app.Map("/allow-origin", innerBuilder =>
|
||||
{
|
||||
var responseHeaders = context.Response.Headers;
|
||||
context.Response.ContentType = "text/plain";
|
||||
foreach (var responseHeader in responseHeaders)
|
||||
{
|
||||
await context.Response.WriteAsync("\n" + responseHeader.Key + ": " + responseHeader.Value);
|
||||
}
|
||||
innerBuilder.UseCors(policy => policy
|
||||
.WithOrigins(DefaultAllowedOrigin)
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader());
|
||||
|
||||
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()
|
||||
.UseKestrel()
|
||||
.UseUrls("http://*:5001")
|
||||
.UseUrls("http://+:9001", "http://+:9002")
|
||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||
.ConfigureLogging(factory => factory.AddConsole())
|
||||
.UseStartup<Startup>()
|
||||
|
|
|
@ -6,7 +6,14 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(MicrosoftAspNetCoreServerKestrelPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="$(MicrosoftAspNetCoreStaticFilesPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Update="wwwroot\*.htm">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
// 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.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace SampleOrigin
|
||||
{
|
||||
|
@ -17,13 +17,8 @@ namespace SampleOrigin
|
|||
|
||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
|
||||
{
|
||||
app.Run(context =>
|
||||
{
|
||||
var fileInfoProvider = env.WebRootFileProvider;
|
||||
var fileInfo = fileInfoProvider.GetFileInfo("/Index.html");
|
||||
context.Response.ContentType = "text/html";
|
||||
return context.Response.SendFileAsync(fileInfo);
|
||||
});
|
||||
app.UseDefaultFiles();
|
||||
app.UseStaticFiles();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.Cors.Infrastructure
|
||||
{
|
||||
/// <summary>
|
||||
/// An ASP.NET middleware for handling CORS.
|
||||
/// A middleware for handling CORS.
|
||||
/// </summary>
|
||||
public class CorsMiddleware
|
||||
{
|
||||
private readonly Func<object, Task> OnResponseStartingDelegate = OnResponseStarting;
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly ICorsService _corsService;
|
||||
private readonly ICorsPolicyProvider _corsPolicyProvider;
|
||||
private readonly CorsPolicy _policy;
|
||||
private readonly string _corsPolicyName;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a new <see cref="CorsMiddleware"/>.
|
||||
|
@ -122,10 +120,10 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
|
|||
}
|
||||
|
||||
_next = next;
|
||||
_corsService = corsService;
|
||||
CorsService = corsService;
|
||||
_corsPolicyProvider = policyProvider;
|
||||
_corsPolicyName = policyName;
|
||||
_logger = loggerFactory.CreateLogger<CorsMiddleware>();
|
||||
Logger = loggerFactory.CreateLogger<CorsMiddleware>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -162,60 +160,65 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
|
|||
}
|
||||
|
||||
_next = next;
|
||||
_corsService = corsService;
|
||||
CorsService = corsService;
|
||||
_policy = policy;
|
||||
_logger = loggerFactory.CreateLogger<CorsMiddleware>();
|
||||
Logger = loggerFactory.CreateLogger<CorsMiddleware>();
|
||||
}
|
||||
|
||||
private ICorsService CorsService { get; }
|
||||
|
||||
private ILogger Logger { get; }
|
||||
|
||||
/// <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))
|
||||
{
|
||||
var corsPolicy = _policy ?? await _corsPolicyProvider?.GetPolicyAsync(context, _corsPolicyName);
|
||||
if (corsPolicy != null)
|
||||
{
|
||||
var accessControlRequestMethod =
|
||||
context.Request.Headers[CorsConstants.AccessControlRequestMethod];
|
||||
if (string.Equals(
|
||||
context.Request.Method,
|
||||
CorsConstants.PreflightHttpMethod,
|
||||
StringComparison.OrdinalIgnoreCase) &&
|
||||
!StringValues.IsNullOrEmpty(accessControlRequestMethod))
|
||||
{
|
||||
ApplyCorsHeaders(context, corsPolicy);
|
||||
|
||||
// Since there is a policy which was identified,
|
||||
// always respond to preflight requests.
|
||||
context.Response.StatusCode = StatusCodes.Status204NoContent;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Response.OnStarting(state =>
|
||||
{
|
||||
var (httpContext, policy) = (Tuple<HttpContext, CorsPolicy>)state;
|
||||
try
|
||||
{
|
||||
ApplyCorsHeaders(httpContext, policy);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.FailedToSetCorsHeaders(exception);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}, Tuple.Create(context, corsPolicy));
|
||||
}
|
||||
}
|
||||
return _next(context);
|
||||
}
|
||||
|
||||
await _next(context);
|
||||
return InvokeCore(context);
|
||||
}
|
||||
|
||||
private void ApplyCorsHeaders(HttpContext context, CorsPolicy corsPolicy)
|
||||
private async Task InvokeCore(HttpContext context)
|
||||
{
|
||||
var corsResult = _corsService.EvaluatePolicy(context, corsPolicy);
|
||||
_corsService.ApplyResult(corsResult, context.Response);
|
||||
var corsPolicy = _policy ?? await _corsPolicyProvider?.GetPolicyAsync(context, _corsPolicyName);
|
||||
if (corsPolicy == null)
|
||||
{
|
||||
Logger?.NoCorsPolicyFound();
|
||||
await _next(context);
|
||||
return;
|
||||
}
|
||||
|
||||
var corsResult = CorsService.EvaluatePolicy(context, corsPolicy);
|
||||
if (corsResult.IsPreflightRequest)
|
||||
{
|
||||
CorsService.ApplyResult(corsResult, context.Response);
|
||||
|
||||
// Since there is a policy which was identified,
|
||||
// always respond to preflight requests.
|
||||
context.Response.StatusCode = StatusCodes.Status204NoContent;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Response.OnStarting(OnResponseStartingDelegate, Tuple.Create(this, context, corsResult));
|
||||
await _next(context);
|
||||
}
|
||||
}
|
||||
|
||||
private static Task OnResponseStarting(object state)
|
||||
{
|
||||
var (middleware, context, result) = (Tuple<CorsMiddleware, HttpContext, CorsResult>)state;
|
||||
try
|
||||
{
|
||||
middleware.CorsService.ApplyResult(result, context.Response);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
middleware.Logger?.FailedToSetCorsHeaders(exception);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,11 +14,22 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
|
|||
{
|
||||
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>
|
||||
/// Gets or sets the allowed origin.
|
||||
/// </summary>
|
||||
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>
|
||||
/// Gets or sets a value indicating whether the resource supports user credentials.
|
||||
/// </summary>
|
||||
|
|
|
@ -8,6 +8,7 @@ using System.Linq;
|
|||
using Microsoft.AspNetCore.Cors.Internal;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
|
@ -25,8 +26,9 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
|
|||
/// Creates a new instance of the <see cref="CorsService"/>.
|
||||
/// </summary>
|
||||
/// <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)
|
||||
: this(options, loggerFactory: null)
|
||||
: this(options, loggerFactory: NullLoggerFactory.Instance)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -42,8 +44,13 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
|
|||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
if (loggerFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(loggerFactory));
|
||||
}
|
||||
|
||||
_options = options.Value;
|
||||
_logger = loggerFactory?.CreateLogger<CorsService>();
|
||||
_logger = loggerFactory.CreateLogger<CorsService>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -78,12 +85,25 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
|
|||
throw new ArgumentNullException(nameof(policy));
|
||||
}
|
||||
|
||||
var corsResult = new CorsResult();
|
||||
var accessControlRequestMethod = context.Request.Headers[CorsConstants.AccessControlRequestMethod];
|
||||
if (string.Equals(context.Request.Method, CorsConstants.PreflightHttpMethod, StringComparison.OrdinalIgnoreCase) &&
|
||||
!StringValues.IsNullOrEmpty(accessControlRequestMethod))
|
||||
if (policy.AllowAnyOrigin && policy.SupportsCredentials)
|
||||
{
|
||||
_logger.InsecureConfiguration();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
else
|
||||
|
@ -94,78 +114,37 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
|
|||
return corsResult;
|
||||
}
|
||||
|
||||
public virtual void EvaluateRequest(HttpContext context, CorsPolicy policy, CorsResult result)
|
||||
private static void PopulateResult(HttpContext context, CorsPolicy policy, CorsResult result)
|
||||
{
|
||||
var origin = context.Request.Headers[CorsConstants.Origin];
|
||||
if (!IsOriginAllowed(policy, origin))
|
||||
if (policy.AllowAnyOrigin)
|
||||
{
|
||||
return;
|
||||
result.AllowedOrigin = CorsConstants.AnyOrigin;
|
||||
result.VaryByOrigin = policy.SupportsCredentials;
|
||||
}
|
||||
else
|
||||
{
|
||||
var origin = context.Request.Headers[CorsConstants.Origin];
|
||||
result.AllowedOrigin = origin;
|
||||
result.VaryByOrigin = policy.Origins.Count > 1;
|
||||
}
|
||||
|
||||
AddOriginToResult(origin, policy, result);
|
||||
result.SupportsCredentials = policy.SupportsCredentials;
|
||||
result.PreflightMaxAge = policy.PreflightMaxAge;
|
||||
|
||||
|
||||
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)
|
||||
{
|
||||
var origin = context.Request.Headers[CorsConstants.Origin];
|
||||
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();
|
||||
PopulateResult(context, policy, result);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -181,86 +160,67 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
|
|||
throw new ArgumentNullException(nameof(response));
|
||||
}
|
||||
|
||||
var headers = response.Headers;
|
||||
|
||||
if (result.AllowedOrigin != null)
|
||||
if (!result.IsOriginAllowed)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
response.Headers[CorsConstants.AccessControlAllowOrigin] = result.AllowedOrigin;
|
||||
|
||||
if (result.SupportsCredentials)
|
||||
{
|
||||
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)
|
||||
{
|
||||
response.Headers.SetCommaSeparatedValues(CorsConstants.AccessControlAllowMethods, result.AllowedMethods.ToArray());
|
||||
}
|
||||
|
||||
if (result.PreflightMaxAge.HasValue)
|
||||
{
|
||||
response.Headers[CorsConstants.AccessControlMaxAge] = result.PreflightMaxAge.Value.TotalSeconds.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
response.Headers.SetCommaSeparatedValues(CorsConstants.AccessControlExposeHeaders, result.AllowedExposedHeaders.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
if (result.VaryByOrigin)
|
||||
{
|
||||
headers["Vary"] = "Origin";
|
||||
}
|
||||
|
||||
if (result.SupportsCredentials)
|
||||
{
|
||||
headers[CorsConstants.AccessControlAllowCredentials] = "true";
|
||||
}
|
||||
|
||||
if (result.AllowedMethods.Count > 0)
|
||||
{
|
||||
headers.SetCommaSeparatedValues(
|
||||
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)
|
||||
{
|
||||
headers[CorsConstants.AccessControlMaxAge]
|
||||
= result.PreflightMaxAge.Value.TotalSeconds.ToString(CultureInfo.InvariantCulture);
|
||||
response.Headers.Append("Vary", "Origin");
|
||||
}
|
||||
}
|
||||
|
||||
private void AddOriginToResult(string origin, CorsPolicy policy, CorsResult result)
|
||||
{
|
||||
if (policy.AllowAnyOrigin)
|
||||
{
|
||||
if (policy.SupportsCredentials)
|
||||
{
|
||||
result.AllowedOrigin = origin;
|
||||
result.VaryByOrigin = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.AllowedOrigin = CorsConstants.AnyOrigin;
|
||||
}
|
||||
}
|
||||
else if (policy.IsOriginAllowed(origin))
|
||||
{
|
||||
result.AllowedOrigin = origin;
|
||||
|
||||
if(policy.Origins.Count > 1)
|
||||
{
|
||||
result.VaryByOrigin = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddHeaderValues(IList<string> target, IEnumerable<string> headerValues)
|
||||
private static void AddHeaderValues(IList<string> target, IList<string> headerValues)
|
||||
{
|
||||
if (headerValues == null)
|
||||
{
|
||||
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))
|
||||
{
|
||||
_logger?.RequestDoesNotHaveOriginHeader();
|
||||
_logger.RequestDoesNotHaveOriginHeader();
|
||||
return false;
|
||||
}
|
||||
|
||||
_logger?.RequestHasOriginHeader(origin);
|
||||
_logger.RequestHasOriginHeader(origin);
|
||||
if (policy.AllowAnyOrigin || policy.IsOriginAllowed(origin))
|
||||
{
|
||||
_logger.PolicySuccess();
|
||||
return true;
|
||||
}
|
||||
_logger?.PolicyFailure();
|
||||
_logger?.OriginNotAllowed(origin);
|
||||
_logger.PolicyFailure();
|
||||
_logger.OriginNotAllowed(origin);
|
||||
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> _requestHeaderNotAllowed;
|
||||
private static readonly Action<ILogger, Exception> _failedToSetCorsHeaders;
|
||||
private static readonly Action<ILogger, Exception> _noCorsPolicyFound;
|
||||
private static readonly Action<ILogger, Exception> _insecureConfiguration;
|
||||
|
||||
static CORSLoggerExtensions()
|
||||
{
|
||||
|
@ -64,6 +66,16 @@ namespace Microsoft.AspNetCore.Cors.Internal
|
|||
LogLevel.Warning,
|
||||
9,
|
||||
"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)
|
||||
|
@ -110,5 +122,15 @@ namespace Microsoft.AspNetCore.Cors.Internal
|
|||
{
|
||||
_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; }
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -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.
|
||||
|
||||
using System;
|
||||
|
@ -10,7 +10,7 @@ using Microsoft.AspNetCore.Hosting;
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
|
@ -18,6 +18,8 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
|
|||
{
|
||||
public class CorsMiddlewareTests
|
||||
{
|
||||
private const string OriginUrl = "http://api.example.com";
|
||||
|
||||
[Theory]
|
||||
[InlineData("PuT")]
|
||||
[InlineData("PUT")]
|
||||
|
@ -28,7 +30,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
|
|||
.Configure(app =>
|
||||
{
|
||||
app.UseCors(builder =>
|
||||
builder.WithOrigins("http://localhost:5001")
|
||||
builder.WithOrigins(OriginUrl)
|
||||
.WithMethods("PUT"));
|
||||
app.Run(async context =>
|
||||
{
|
||||
|
@ -42,14 +44,14 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
|
|||
// Act
|
||||
// Actual request.
|
||||
var response = await server.CreateRequest("/")
|
||||
.AddHeader(CorsConstants.Origin, "http://localhost:5001")
|
||||
.AddHeader(CorsConstants.Origin, OriginUrl)
|
||||
.SendAsync(accessControlRequestMethod);
|
||||
|
||||
// Assert
|
||||
response.EnsureSuccessStatusCode();
|
||||
Assert.Single(response.Headers);
|
||||
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 =>
|
||||
{
|
||||
app.UseCors(builder =>
|
||||
builder.WithOrigins("http://localhost:5001")
|
||||
builder.WithOrigins(OriginUrl)
|
||||
.WithMethods("PUT")
|
||||
.WithHeaders("Header1")
|
||||
.WithExposedHeaders("AllowedHeader"));
|
||||
|
@ -77,14 +79,14 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
|
|||
// Act
|
||||
// Actual request.
|
||||
var response = await server.CreateRequest("/")
|
||||
.AddHeader(CorsConstants.Origin, "http://localhost:5001")
|
||||
.AddHeader(CorsConstants.Origin, OriginUrl)
|
||||
.SendAsync("PUT");
|
||||
|
||||
// Assert
|
||||
response.EnsureSuccessStatusCode();
|
||||
Assert.Equal(2, response.Headers.Count());
|
||||
Assert.Equal("Cross origin response", await response.Content.ReadAsStringAsync());
|
||||
Assert.Equal("http://localhost:5001", response.Headers.GetValues(CorsConstants.AccessControlAllowOrigin).FirstOrDefault());
|
||||
Assert.Equal(OriginUrl, response.Headers.GetValues(CorsConstants.AccessControlAllowOrigin).FirstOrDefault());
|
||||
Assert.Equal("AllowedHeader", response.Headers.GetValues(CorsConstants.AccessControlExposeHeaders).FirstOrDefault());
|
||||
}
|
||||
}
|
||||
|
@ -96,7 +98,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
|
|||
{
|
||||
// Arrange
|
||||
var policy = new CorsPolicy();
|
||||
policy.Origins.Add("http://localhost:5001");
|
||||
policy.Origins.Add(OriginUrl);
|
||||
policy.Methods.Add("PUT");
|
||||
|
||||
var hostBuilder = new WebHostBuilder()
|
||||
|
@ -121,13 +123,13 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
|
|||
// Act
|
||||
// Preflight request.
|
||||
var response = await server.CreateRequest("/")
|
||||
.AddHeader(CorsConstants.Origin, "http://localhost:5001")
|
||||
.AddHeader(CorsConstants.Origin, OriginUrl)
|
||||
.SendAsync(preflightMethod);
|
||||
|
||||
// Assert
|
||||
response.EnsureSuccessStatusCode();
|
||||
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
|
||||
var policy = new CorsPolicy();
|
||||
policy.Origins.Add("http://localhost:5001");
|
||||
policy.Origins.Add(OriginUrl);
|
||||
policy.Methods.Add("PUT");
|
||||
policy.Headers.Add("Header1");
|
||||
policy.ExposedHeaders.Add("AllowedHeader");
|
||||
|
@ -163,27 +165,105 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
|
|||
// Act
|
||||
// Preflight request.
|
||||
var response = await server.CreateRequest("/")
|
||||
.AddHeader(CorsConstants.Origin, "http://localhost:5001")
|
||||
.AddHeader(CorsConstants.Origin, OriginUrl)
|
||||
.AddHeader(CorsConstants.AccessControlRequestMethod, "PUT")
|
||||
.SendAsync(CorsConstants.PreflightHttpMethod);
|
||||
|
||||
// Assert
|
||||
response.EnsureSuccessStatusCode();
|
||||
Assert.Equal(2, response.Headers.Count());
|
||||
Assert.Equal("http://localhost:5001", response.Headers.GetValues(CorsConstants.AccessControlAllowOrigin).FirstOrDefault());
|
||||
Assert.Equal("PUT", response.Headers.GetValues(CorsConstants.AccessControlAllowMethods).FirstOrDefault());
|
||||
Assert.Collection(
|
||||
response.Headers.OrderBy(h => h.Key),
|
||||
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]
|
||||
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
|
||||
var hostBuilder = new WebHostBuilder()
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UseCors(builder =>
|
||||
builder.WithOrigins("http://localhost:5001")
|
||||
builder.WithOrigins(OriginUrl)
|
||||
.WithMethods("PUT")
|
||||
.WithHeaders("Header1")
|
||||
.WithExposedHeaders("AllowedHeader"));
|
||||
|
@ -199,7 +279,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
|
|||
// Act
|
||||
// Preflight request.
|
||||
var response = await server.CreateRequest("/")
|
||||
.AddHeader(CorsConstants.Origin, "http://localhost:5002")
|
||||
.AddHeader(CorsConstants.Origin, "http://test.example.com")
|
||||
.AddHeader(CorsConstants.AccessControlRequestMethod, "PUT")
|
||||
.SendAsync(CorsConstants.PreflightHttpMethod);
|
||||
|
||||
|
@ -217,7 +297,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
|
|||
.Configure(app =>
|
||||
{
|
||||
app.UseCors(builder =>
|
||||
builder.WithOrigins("http://localhost:5001")
|
||||
builder.WithOrigins(OriginUrl)
|
||||
.WithMethods("PUT")
|
||||
.WithHeaders("Header1")
|
||||
.WithExposedHeaders("AllowedHeader"));
|
||||
|
@ -233,7 +313,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
|
|||
// Act
|
||||
// Actual request.
|
||||
var response = await server.CreateRequest("/")
|
||||
.AddHeader(CorsConstants.Origin, "http://localhost:5002")
|
||||
.AddHeader(CorsConstants.Origin, "http://test.example.com")
|
||||
.SendAsync("PUT");
|
||||
|
||||
// Assert
|
||||
|
@ -248,7 +328,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
|
|||
// Arrange
|
||||
var corsService = Mock.Of<ICorsService>();
|
||||
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>()))
|
||||
.Returns(Task.FromResult<CorsPolicy>(null))
|
||||
.Verifiable();
|
||||
|
@ -278,7 +358,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
|
|||
// Arrange
|
||||
var corsService = Mock.Of<ICorsService>();
|
||||
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>()))
|
||||
.Returns(Task.FromResult<CorsPolicy>(null))
|
||||
.Verifiable();
|
||||
|
@ -324,7 +404,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
|
|||
options.AddDefaultPolicy(policyBuilder =>
|
||||
{
|
||||
policyBuilder
|
||||
.WithOrigins("http://localhost:5001")
|
||||
.WithOrigins(OriginUrl)
|
||||
.WithMethods("PUT")
|
||||
.WithHeaders("Header1")
|
||||
.WithExposedHeaders("AllowedHeader")
|
||||
|
@ -333,7 +413,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
|
|||
options.AddPolicy("policy2", policyBuilder =>
|
||||
{
|
||||
policyBuilder
|
||||
.WithOrigins("http://localhost:5002")
|
||||
.WithOrigins("http://test.example.com")
|
||||
.Build();
|
||||
});
|
||||
});
|
||||
|
@ -344,15 +424,29 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
|
|||
// Act
|
||||
// Preflight request.
|
||||
var response = await server.CreateRequest("/")
|
||||
.AddHeader(CorsConstants.Origin, "http://localhost:5001")
|
||||
.AddHeader(CorsConstants.Origin, OriginUrl)
|
||||
.AddHeader(CorsConstants.AccessControlRequestMethod, "PUT")
|
||||
.SendAsync(CorsConstants.PreflightHttpMethod);
|
||||
|
||||
// Assert
|
||||
response.EnsureSuccessStatusCode();
|
||||
Assert.Equal(2, response.Headers.Count());
|
||||
Assert.Equal("http://localhost:5001", response.Headers.GetValues(CorsConstants.AccessControlAllowOrigin).FirstOrDefault());
|
||||
Assert.Equal("PUT", response.Headers.GetValues(CorsConstants.AccessControlAllowMethods).FirstOrDefault());
|
||||
Assert.Collection(
|
||||
response.Headers.OrderBy(h => h.Key),
|
||||
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 =>
|
||||
{
|
||||
app.UseCors(builder =>
|
||||
builder.WithOrigins("http://localhost:5001")
|
||||
builder.WithOrigins(OriginUrl)
|
||||
.WithMethods("PUT")
|
||||
.WithHeaders("Header1")
|
||||
.WithExposedHeaders("AllowedHeader"));
|
||||
|
@ -381,7 +475,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
|
|||
// Act
|
||||
// Actual request.
|
||||
var response = await server.CreateRequest("/")
|
||||
.AddHeader(CorsConstants.Origin, "http://localhost:5001")
|
||||
.AddHeader(CorsConstants.Origin, OriginUrl)
|
||||
.SendAsync("PUT");
|
||||
|
||||
// Assert
|
||||
|
@ -391,7 +485,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
|
|||
kvp =>
|
||||
{
|
||||
Assert.Equal(CorsConstants.AccessControlAllowOrigin, kvp.Key);
|
||||
Assert.Equal("http://localhost:5001", Assert.Single(kvp.Value));
|
||||
Assert.Equal(OriginUrl, Assert.Single(kvp.Value));
|
||||
},
|
||||
kvp =>
|
||||
{
|
||||
|
@ -432,7 +526,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
|
|||
});
|
||||
|
||||
app.UseCors(builder =>
|
||||
builder.WithOrigins("http://localhost:5001")
|
||||
builder.WithOrigins(OriginUrl)
|
||||
.WithMethods("PUT")
|
||||
.WithHeaders("Header1")
|
||||
.WithExposedHeaders("AllowedHeader"));
|
||||
|
@ -450,7 +544,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
|
|||
// Act
|
||||
// Actual request.
|
||||
var response = await server.CreateRequest("/")
|
||||
.AddHeader(CorsConstants.Origin, "http://localhost:5001")
|
||||
.AddHeader(CorsConstants.Origin, OriginUrl)
|
||||
.SendAsync("PUT");
|
||||
|
||||
// Assert
|
||||
|
@ -462,7 +556,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
|
|||
kvp =>
|
||||
{
|
||||
Assert.Equal(CorsConstants.AccessControlAllowOrigin, kvp.Key);
|
||||
Assert.Equal("http://localhost:5001", Assert.Single(kvp.Value));
|
||||
Assert.Equal(OriginUrl, Assert.Single(kvp.Value));
|
||||
},
|
||||
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 Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Cors.Infrastructure
|
||||
|
@ -17,10 +18,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
|
|||
var policy = new CorsPolicy();
|
||||
options.AddPolicy(options.DefaultPolicyName, policy);
|
||||
|
||||
var corsOptions = new TestCorsOptions
|
||||
{
|
||||
Value = options
|
||||
};
|
||||
var corsOptions = Options.Create(options);
|
||||
var policyProvider = new DefaultCorsPolicyProvider(corsOptions);
|
||||
|
||||
// Act
|
||||
|
@ -40,10 +38,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
|
|||
var policy = new CorsPolicy();
|
||||
options.AddPolicy(policyName, policy);
|
||||
|
||||
var corsOptions = new TestCorsOptions
|
||||
{
|
||||
Value = options
|
||||
};
|
||||
var corsOptions = Options.Create(options);
|
||||
var policyProvider = new DefaultCorsPolicyProvider(corsOptions);
|
||||
|
||||
// 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)
|
||||
{
|
||||
// Act
|
||||
bool isSubdomain = UriHelpers.IsSubdomainOf(subdomain, domain);
|
||||
var isSubdomain = UriHelpers.IsSubdomainOf(subdomain, domain);
|
||||
|
||||
// Assert
|
||||
Assert.True(isSubdomain);
|
||||
|
@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
|
|||
public void TestIsSubdomainOf_ReturnsFalse_WhenNotSubdomain(Uri subdomain, Uri domain)
|
||||
{
|
||||
// Act
|
||||
bool isSubdomain = UriHelpers.IsSubdomainOf(subdomain, domain);
|
||||
var isSubdomain = UriHelpers.IsSubdomainOf(subdomain, domain);
|
||||
|
||||
// Assert
|
||||
Assert.False(isSubdomain);
|
||||
|
|
Загрузка…
Ссылка в новой задаче