Remove filtering from CorsService
* Add browser based integration tests
This commit is contained in:
Родитель
b9166f14f1
Коммит
2690a3f621
|
@ -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
|
||||||
|
|
7
CORS.sln
7
CORS.sln
|
@ -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>
|
||||||
|
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -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);
|
||||||
|
|
Загрузка…
Ссылка в новой задаче