Merge remote-tracking branch 'CORS/rybrande/release22ToSrc' into rybrande/Mondo2.2

This commit is contained in:
Ryan Brandenburg 2018-11-21 16:35:26 -08:00
commit 396bb7a2d8
44 changed files with 7533 additions and 1035 deletions

View File

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

View File

@ -14,7 +14,6 @@
<RepositoryRoot>$(MSBuildThisFileDirectory)</RepositoryRoot>
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)build\Key.snk</AssemblyOriginatorKeyFile>
<SignAssembly>true</SignAssembly>
<PublicSign Condition="'$(OS)' != 'Windows_NT'">true</PublicSign>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>

View File

@ -1,7 +1,10 @@
<Project>
<Project>
<PropertyGroup>
<RuntimeFrameworkVersion Condition=" '$(TargetFramework)' == 'netcoreapp2.0' ">$(MicrosoftNETCoreApp20PackageVersion)</RuntimeFrameworkVersion>
<RuntimeFrameworkVersion Condition=" '$(TargetFramework)' == 'netcoreapp2.1' ">$(MicrosoftNETCoreApp21PackageVersion)</RuntimeFrameworkVersion>
<RuntimeFrameworkVersion Condition=" '$(TargetFramework)' == 'netcoreapp2.2' ">$(MicrosoftNETCoreApp22PackageVersion)</RuntimeFrameworkVersion>
<NETStandardImplicitPackageVersion Condition=" '$(TargetFramework)' == 'netstandard2.0' ">$(NETStandardLibrary20PackageVersion)</NETStandardImplicitPackageVersion>
<!-- aspnet/BuildTools#662 Don't police what version of NetCoreApp we use -->
<NETCoreAppMaximumVersion>99.9</NETCoreAppMaximumVersion>
</PropertyGroup>
</Project>

View File

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

View File

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

View File

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

View File

@ -2,34 +2,30 @@
<PropertyGroup>
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
</PropertyGroup>
<!-- These package versions may be overridden or updated by automation. -->
<PropertyGroup Label="Package Versions: Auto">
<InternalAspNetCoreSdkPackageVersion>2.1.3-rtm-15802</InternalAspNetCoreSdkPackageVersion>
<MicrosoftNETCoreApp20PackageVersion>2.0.0</MicrosoftNETCoreApp20PackageVersion>
<MicrosoftNETCoreApp21PackageVersion>2.1.2</MicrosoftNETCoreApp21PackageVersion>
<PropertyGroup Label="Package Versions">
<InternalAspNetCoreSdkPackageVersion>2.2.0-preview2-20181004.6</InternalAspNetCoreSdkPackageVersion>
<MicrosoftAspNetCoreHttpExtensionsPackageVersion>2.2.0-preview3-35413</MicrosoftAspNetCoreHttpExtensionsPackageVersion>
<MicrosoftAspNetCoreServerIISIntegrationPackageVersion>2.2.0-preview3-35413</MicrosoftAspNetCoreServerIISIntegrationPackageVersion>
<MicrosoftAspNetCoreServerIntegrationTestingPackageVersion>0.6.0-preview3-35413</MicrosoftAspNetCoreServerIntegrationTestingPackageVersion>
<MicrosoftAspNetCoreServerKestrelPackageVersion>2.2.0-preview3-35413</MicrosoftAspNetCoreServerKestrelPackageVersion>
<MicrosoftAspNetCoreStaticFilesPackageVersion>2.2.0-preview3-35413</MicrosoftAspNetCoreStaticFilesPackageVersion>
<MicrosoftAspNetCoreTestHostPackageVersion>2.2.0-preview3-35413</MicrosoftAspNetCoreTestHostPackageVersion>
<MicrosoftExtensionsConfigurationAbstractionsPackageVersion>2.2.0-preview3-35413</MicrosoftExtensionsConfigurationAbstractionsPackageVersion>
<MicrosoftExtensionsDependencyInjectionAbstractionsPackageVersion>2.2.0-preview3-35413</MicrosoftExtensionsDependencyInjectionAbstractionsPackageVersion>
<MicrosoftExtensionsLoggingAbstractionsPackageVersion>2.2.0-preview3-35413</MicrosoftExtensionsLoggingAbstractionsPackageVersion>
<MicrosoftExtensionsLoggingConsolePackageVersion>2.2.0-preview3-35413</MicrosoftExtensionsLoggingConsolePackageVersion>
<MicrosoftExtensionsLoggingTestingPackageVersion>2.2.0-preview3-35413</MicrosoftExtensionsLoggingTestingPackageVersion>
<MicrosoftExtensionsOptionsPackageVersion>2.2.0-preview3-35413</MicrosoftExtensionsOptionsPackageVersion>
<MicrosoftNETCoreApp20PackageVersion>2.0.9</MicrosoftNETCoreApp20PackageVersion>
<MicrosoftNETCoreApp21PackageVersion>2.1.3</MicrosoftNETCoreApp21PackageVersion>
<MicrosoftNETCoreApp22PackageVersion>2.2.0-preview3-27001-02</MicrosoftNETCoreApp22PackageVersion>
<MicrosoftNETTestSdkPackageVersion>15.6.1</MicrosoftNETTestSdkPackageVersion>
<MoqPackageVersion>4.7.49</MoqPackageVersion>
<NETStandardLibrary20PackageVersion>2.0.3</NETStandardLibrary20PackageVersion>
<XunitAnalyzersPackageVersion>0.8.0</XunitAnalyzersPackageVersion>
<XunitAnalyzersPackageVersion>0.10.0</XunitAnalyzersPackageVersion>
<XunitPackageVersion>2.3.1</XunitPackageVersion>
<XunitRunnerVisualStudioPackageVersion>2.4.0-beta.1.build3945</XunitRunnerVisualStudioPackageVersion>
<XunitRunnerVisualStudioPackageVersion>2.4.0</XunitRunnerVisualStudioPackageVersion>
</PropertyGroup>
<!-- This may import a generated file which may override the variables above. -->
<Import Project="$(DotNetPackageVersionPropsPath)" Condition=" '$(DotNetPackageVersionPropsPath)' != '' " />
<!-- These are package versions that should not be overridden or updated by automation. -->
<PropertyGroup Label="Package Versions: Pinned">
<MicrosoftAspNetCoreHttpExtensionsPackageVersion>2.1.1</MicrosoftAspNetCoreHttpExtensionsPackageVersion>
<MicrosoftAspNetCoreServerIISIntegrationPackageVersion>2.1.1</MicrosoftAspNetCoreServerIISIntegrationPackageVersion>
<MicrosoftAspNetCoreServerKestrelPackageVersion>2.1.2</MicrosoftAspNetCoreServerKestrelPackageVersion>
<MicrosoftAspNetCoreTestHostPackageVersion>2.1.1</MicrosoftAspNetCoreTestHostPackageVersion>
<MicrosoftExtensionsConfigurationAbstractionsPackageVersion>2.1.1</MicrosoftExtensionsConfigurationAbstractionsPackageVersion>
<MicrosoftExtensionsDependencyInjectionAbstractionsPackageVersion>2.1.1</MicrosoftExtensionsDependencyInjectionAbstractionsPackageVersion>
<MicrosoftExtensionsLoggingAbstractionsPackageVersion>2.1.1</MicrosoftExtensionsLoggingAbstractionsPackageVersion>
<MicrosoftExtensionsLoggingConsolePackageVersion>2.1.1</MicrosoftExtensionsLoggingConsolePackageVersion>
<MicrosoftExtensionsLoggingTestingPackageVersion>2.1.1</MicrosoftExtensionsLoggingTestingPackageVersion>
<MicrosoftExtensionsOptionsPackageVersion>2.1.1</MicrosoftExtensionsOptionsPackageVersion>
</PropertyGroup>
</Project>
<PropertyGroup Label="Package Versions: Pinned" />
</Project>

View File

@ -4,12 +4,13 @@
<PropertyGroup>
<!-- These properties are use by the automation that updates dependencies.props -->
<LineupPackageId>Internal.AspNetCore.Universe.Lineup</LineupPackageId>
<LineupPackageVersion>2.1.0-rc1-*</LineupPackageVersion>
<LineupPackageVersion>2.2.0-*</LineupPackageVersion>
<LineupPackageRestoreSource>https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json</LineupPackageRestoreSource>
</PropertyGroup>
<ItemGroup>
<DotNetCoreRuntime Include="$(MicrosoftNETCoreApp20PackageVersion)" />
<DotNetCoreRuntime Include="$(MicrosoftNETCoreApp21PackageVersion)" />
<DotNetCoreRuntime Include="$(MicrosoftNETCoreApp22PackageVersion)" />
</ItemGroup>
</Project>

View File

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

View File

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

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.1;net461</TargetFrameworks>
<TargetFrameworks>netcoreapp2.2;net461</TargetFrameworks>
</PropertyGroup>
<ItemGroup>

View File

@ -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;
}
}
}

View File

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

View File

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

View File

@ -1,12 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.1;net461</TargetFrameworks>
<TargetFrameworks>netcoreapp2.2;net461</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(MicrosoftAspNetCoreServerKestrelPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="$(MicrosoftAspNetCoreStaticFilesPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
</ItemGroup>
<ItemGroup>
<Content Update="wwwroot\*.htm">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

View File

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

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -65,31 +65,5 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
/// The Access-Control-Max-Age response header.
/// </summary>
public static readonly string AccessControlMaxAge = HeaderNames.AccessControlMaxAge;
internal static readonly string[] SimpleRequestHeaders =
{
HeaderNames.Origin,
HeaderNames.Accept,
HeaderNames.AcceptLanguage,
HeaderNames.ContentLanguage,
};
internal static readonly string[] SimpleResponseHeaders =
{
HeaderNames.CacheControl,
HeaderNames.ContentLanguage,
HeaderNames.ContentType,
HeaderNames.Expires,
HeaderNames.LastModified,
HeaderNames.Pragma
};
internal static readonly string[] SimpleMethods =
{
HttpMethods.Get,
HttpMethods.Head,
HttpMethods.Post
};
}
}
}

View File

@ -3,18 +3,20 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Cors.Internal;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
namespace Microsoft.AspNetCore.Cors.Infrastructure
{
/// <summary>
/// An ASP.NET middleware for handling CORS.
/// A middleware for handling CORS.
/// </summary>
public class CorsMiddleware
{
private readonly Func<object, Task> OnResponseStartingDelegate = OnResponseStarting;
private readonly RequestDelegate _next;
private readonly ICorsService _corsService;
private readonly ICorsPolicyProvider _corsPolicyProvider;
private readonly CorsPolicy _policy;
private readonly string _corsPolicyName;
@ -25,11 +27,12 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
/// <param name="next">The next middleware in the pipeline.</param>
/// <param name="corsService">An instance of <see cref="ICorsService"/>.</param>
/// <param name="policyProvider">A policy provider which can get an <see cref="CorsPolicy"/>.</param>
[Obsolete("This constructor has been replaced with an equivalent constructor which requires an ILoggerFactory")]
public CorsMiddleware(
RequestDelegate next,
ICorsService corsService,
ICorsPolicyProvider policyProvider)
: this(next, corsService, policyProvider, policyName: null)
: this(next, corsService, policyProvider, NullLoggerFactory.Instance, policyName: null)
{
}
@ -40,11 +43,61 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
/// <param name="corsService">An instance of <see cref="ICorsService"/>.</param>
/// <param name="policyProvider">A policy provider which can get an <see cref="CorsPolicy"/>.</param>
/// <param name="policyName">An optional name of the policy to be fetched.</param>
[Obsolete("This constructor has been replaced with an equivalent constructor which requires an ILoggerFactory")]
public CorsMiddleware(
RequestDelegate next,
ICorsService corsService,
ICorsPolicyProvider policyProvider,
string policyName)
: this(next, corsService, policyProvider, NullLoggerFactory.Instance, policyName)
{
}
/// <summary>
/// Instantiates a new <see cref="CorsMiddleware"/>.
/// </summary>
/// <param name="next">The next middleware in the pipeline.</param>
/// <param name="corsService">An instance of <see cref="ICorsService"/>.</param>
/// <param name="policy">An instance of the <see cref="CorsPolicy"/> which can be applied.</param>
[Obsolete("This constructor has been replaced with an equivalent constructor which requires an ILoggerFactory")]
public CorsMiddleware(
RequestDelegate next,
ICorsService corsService,
CorsPolicy policy)
: this(next, corsService, policy, NullLoggerFactory.Instance)
{
}
/// <summary>
/// Instantiates a new <see cref="CorsMiddleware"/>.
/// </summary>
/// <param name="next">The next middleware in the pipeline.</param>
/// <param name="corsService">An instance of <see cref="ICorsService"/>.</param>
/// <param name="policyProvider">A policy provider which can get an <see cref="CorsPolicy"/>.</param>
/// <param name="loggerFactory">An instance of <see cref="ILoggerFactory"/>.</param>
public CorsMiddleware(
RequestDelegate next,
ICorsService corsService,
ICorsPolicyProvider policyProvider,
ILoggerFactory loggerFactory)
: this(next, corsService, policyProvider, loggerFactory, policyName: null)
{
}
/// <summary>
/// Instantiates a new <see cref="CorsMiddleware"/>.
/// </summary>
/// <param name="next">The next middleware in the pipeline.</param>
/// <param name="corsService">An instance of <see cref="ICorsService"/>.</param>
/// <param name="policyProvider">A policy provider which can get an <see cref="CorsPolicy"/>.</param>
/// <param name="loggerFactory">An instance of <see cref="ILoggerFactory"/>.</param>
/// <param name="policyName">An optional name of the policy to be fetched.</param>
public CorsMiddleware(
RequestDelegate next,
ICorsService corsService,
ICorsPolicyProvider policyProvider,
ILoggerFactory loggerFactory,
string policyName)
{
if (next == null)
{
@ -61,10 +114,16 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
throw new ArgumentNullException(nameof(policyProvider));
}
if (loggerFactory == null)
{
throw new ArgumentNullException(nameof(loggerFactory));
}
_next = next;
_corsService = corsService;
CorsService = corsService;
_corsPolicyProvider = policyProvider;
_corsPolicyName = policyName;
Logger = loggerFactory.CreateLogger<CorsMiddleware>();
}
/// <summary>
@ -73,10 +132,12 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
/// <param name="next">The next middleware in the pipeline.</param>
/// <param name="corsService">An instance of <see cref="ICorsService"/>.</param>
/// <param name="policy">An instance of the <see cref="CorsPolicy"/> which can be applied.</param>
/// <param name="loggerFactory">An instance of <see cref="ILoggerFactory"/>.</param>
public CorsMiddleware(
RequestDelegate next,
ICorsService corsService,
CorsPolicy policy)
RequestDelegate next,
ICorsService corsService,
CorsPolicy policy,
ILoggerFactory loggerFactory)
{
if (next == null)
{
@ -93,39 +154,71 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
throw new ArgumentNullException(nameof(policy));
}
_next = next;
_corsService = corsService;
_policy = policy;
}
/// <inheritdoc />
public async Task Invoke(HttpContext context)
{
if (context.Request.Headers.ContainsKey(CorsConstants.Origin))
if (loggerFactory == null)
{
var corsPolicy = _policy ?? await _corsPolicyProvider?.GetPolicyAsync(context, _corsPolicyName);
if (corsPolicy != null)
{
var corsResult = _corsService.EvaluatePolicy(context, corsPolicy);
_corsService.ApplyResult(corsResult, context.Response);
var accessControlRequestMethod =
context.Request.Headers[CorsConstants.AccessControlRequestMethod];
if (string.Equals(
context.Request.Method,
CorsConstants.PreflightHttpMethod,
StringComparison.OrdinalIgnoreCase) &&
!StringValues.IsNullOrEmpty(accessControlRequestMethod))
{
// Since there is a policy which was identified,
// always respond to preflight requests.
context.Response.StatusCode = StatusCodes.Status204NoContent;
return;
}
}
throw new ArgumentNullException(nameof(loggerFactory));
}
await _next(context);
_next = next;
CorsService = corsService;
_policy = policy;
Logger = loggerFactory.CreateLogger<CorsMiddleware>();
}
private ICorsService CorsService { get; }
private ILogger Logger { get; }
/// <inheritdoc />
public Task Invoke(HttpContext context)
{
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);
if (corsPolicy == null)
{
Logger?.NoCorsPolicyFound();
await _next(context);
return;
}
var corsResult = CorsService.EvaluatePolicy(context, corsPolicy);
if (corsResult.IsPreflightRequest)
{
CorsService.ApplyResult(corsResult, context.Response);
// Since there is a policy which was identified,
// always respond to preflight requests.
context.Response.StatusCode = StatusCodes.Status204NoContent;
return;
}
else
{
context.Response.OnStarting(OnResponseStartingDelegate, Tuple.Create(this, context, corsResult));
await _next(context);
}
}
private static Task OnResponseStarting(object state)
{
var (middleware, context, result) = (Tuple<CorsMiddleware, HttpContext, CorsResult>)state;
try
{
middleware.CorsService.ApplyResult(result, context.Response);
}
catch (Exception exception)
{
middleware.Logger?.FailedToSetCorsHeaders(exception);
}
return Task.CompletedTask;
}
}
}
}

View File

@ -17,6 +17,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
/// Creates a new instance of the <see cref="CorsPolicyBuilder"/>.
/// </summary>
/// <param name="origins">list of origins which can be added.</param>
/// <remarks> <see cref="WithOrigins(string[])"/> for details on normalizing the origin value.</remarks>
public CorsPolicyBuilder(params string[] origins)
{
WithOrigins(origins);
@ -36,16 +37,55 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
/// </summary>
/// <param name="origins">The origins that are allowed.</param>
/// <returns>The current policy builder.</returns>
/// <remarks>
/// This method normalizes the origin value prior to adding it to <see cref="CorsPolicy.Origins"/> to match
/// the normalization performed by the browser on the value sent in the <c>ORIGIN</c> header.
/// <list type="bullet">
/// <item>
/// If the specified origin has an internationalized domain name (IDN), the punycoded value is used. If the origin
/// specifies a default port (e.g. 443 for HTTPS or 80 for HTTP), this will be dropped as part of normalization.
/// Finally, the scheme and punycoded host name are culture invariant lower cased before being added to the <see cref="CorsPolicy.Origins"/>
/// collection.
/// </item>
/// <item>
/// For all other origins, normalization involves performing a culture invariant lower casing of the host name.
/// </item>
/// </list>
/// </remarks>
public CorsPolicyBuilder WithOrigins(params string[] origins)
{
foreach (var req in origins)
foreach (var origin in origins)
{
_policy.Origins.Add(req);
var normalizedOrigin = GetNormalizedOrigin(origin);
_policy.Origins.Add(normalizedOrigin);
}
return this;
}
internal static string GetNormalizedOrigin(string origin)
{
if (Uri.TryCreate(origin, UriKind.Absolute, out var uri) &&
(uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps) &&
!string.Equals(uri.IdnHost, uri.Host, StringComparison.Ordinal))
{
var builder = new UriBuilder(uri.Scheme.ToLowerInvariant(), uri.IdnHost.ToLowerInvariant());
if (!uri.IsDefaultPort)
{
// Uri does not have a way to differentiate between a port value inferred by default (e.g. Port = 80 for http://www.example.com) and
// a default port value that is specified (e.g. Port = 80 for http://www.example.com:80). Although the HTTP or FETCH spec does not say
// anything about including the default port as part of the Origin header, at the time of writing, browsers drop "default" port when navigating
// and when sending the Origin header. All this goes to say, it appears OK to drop an explicitly specified port,
// if it is the default port when working with an IDN host.
builder.Port = uri.Port;
}
return builder.Uri.GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped);
}
return origin.ToLowerInvariant();
}
/// <summary>
/// Adds the specified <paramref name="headers"/> to the policy.
/// </summary>
@ -222,4 +262,4 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
return this;
}
}
}
}

View File

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

View File

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

View File

@ -16,6 +16,10 @@ namespace Microsoft.AspNetCore.Cors.Internal
private static readonly Action<ILogger, string, Exception> _originNotAllowed;
private static readonly Action<ILogger, string, Exception> _accessControlMethodNotAllowed;
private static readonly Action<ILogger, string, Exception> _requestHeaderNotAllowed;
private static readonly Action<ILogger, Exception> _failedToSetCorsHeaders;
private static readonly Action<ILogger, Exception> _noCorsPolicyFound;
private static readonly Action<ILogger, Exception> _insecureConfiguration;
private static readonly Action<ILogger, Exception> _isNotPreflightRequest;
static CORSLoggerExtensions()
{
@ -37,12 +41,12 @@ namespace Microsoft.AspNetCore.Cors.Internal
_policySuccess = LoggerMessage.Define(
LogLevel.Information,
4,
"Policy execution successful.");
"CORS policy execution successful.");
_policyFailure = LoggerMessage.Define(
LogLevel.Information,
5,
"Policy execution failed.");
"CORS policy execution failed.");
_originNotAllowed = LoggerMessage.Define<string>(
LogLevel.Information,
@ -58,6 +62,26 @@ namespace Microsoft.AspNetCore.Cors.Internal
LogLevel.Information,
8,
"Request header '{requestHeader}' not allowed in CORS policy.");
_failedToSetCorsHeaders = LoggerMessage.Define(
LogLevel.Warning,
9,
"Failed to apply CORS Response headers.");
_noCorsPolicyFound = LoggerMessage.Define(
LogLevel.Information,
new EventId(10, "NoCorsPolicyFound"),
"No CORS policy found for the specified request.");
_insecureConfiguration = LoggerMessage.Define(
LogLevel.Warning,
new EventId(11, "CorsInsecureConfiguration"),
"The CORS protocol does not allow specifying a wildcard (any) origin and credentials at the same time. Configure the policy by listing individual origins if credentials needs to be supported.");
_isNotPreflightRequest = LoggerMessage.Define(
LogLevel.Debug,
new EventId(12, "OptionsRequestWithoutAccessControlRequestMethodHeader"),
"This request uses the HTTP OPTIONS method but does not have an Access-Control-Request-Method header. This request will not be treated as a CORS preflight request.");
}
public static void IsPreflightRequest(this ILogger logger)
@ -99,5 +123,25 @@ namespace Microsoft.AspNetCore.Cors.Internal
{
_requestHeaderNotAllowed(logger, requestHeader, null);
}
public static void FailedToSetCorsHeaders(this ILogger logger, Exception exception)
{
_failedToSetCorsHeaders(logger, exception);
}
public static void NoCorsPolicyFound(this ILogger logger)
{
_noCorsPolicyFound(logger, null);
}
public static void InsecureConfiguration(this ILogger logger)
{
_insecureConfiguration(logger, null);
}
public static void IsNotPreflightRequest(this ILogger logger)
{
_isNotPreflightRequest(logger, null);
}
}
}

View File

@ -2,9 +2,9 @@
<Import Project="..\Directory.Build.props" />
<PropertyGroup>
<DeveloperBuildTestTfms>netcoreapp2.1</DeveloperBuildTestTfms>
<DeveloperBuildTestTfms>netcoreapp2.2</DeveloperBuildTestTfms>
<StandardTestTfms>$(DeveloperBuildTestTfms)</StandardTestTfms>
<StandardTestTfms Condition=" '$(DeveloperBuild)' != 'true' ">$(StandardTestTfms);netcoreapp2.0</StandardTestTfms>
<StandardTestTfms Condition=" '$(DeveloperBuild)' != 'true' ">$(StandardTestTfms)</StandardTestTfms>
<StandardTestTfms Condition=" '$(DeveloperBuild)' != 'true' AND '$(OS)' == 'Windows_NT' ">$(StandardTestTfms);net461</StandardTestTfms>
</PropertyGroup>

View File

@ -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();
}
}
}
}
}

View File

@ -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, loggerFactory.CreateLogger("ProcessManager"));
// 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();
}
}
}
}

View File

@ -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>

View File

@ -0,0 +1,105 @@
// 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;
using Microsoft.Extensions.Logging;
namespace FunctionalTests
{
internal static class ProcessManager
{
private static readonly TimeSpan Timeout = TimeSpan.FromMinutes(3);
public static Task<ProcessResult> RunProcessAsync(ProcessStartInfo processStartInfo, ILogger logger)
{
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;
logger.LogInformation($"Executing command '{process.StartInfo.FileName} {process.StartInfo.Arguments}'");
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)
{
logger.LogInformation(e.Data);
lock (outputLock)
{
output.AppendLine(e.Data);
}
}
void Process_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
logger.LogInformation(e.Data);
lock (outputLock)
{
output.AppendLine(e.Data);
}
}
}
}
}

View File

@ -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; }
}
}

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -0,0 +1,307 @@
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
let debug = process.env.npm_config_debug || false;
jest.setTimeout(debug ? 60000 : 30000);
let browser;
let error;
beforeAll(async () => {
const options = debug ?
{ headless: false, slowMo: 100 } :
{ args: ['--no-sandbox'] };
const label = 'Launch puppeteer ';
console.log('Begin launching puppeteer');
console.time(label);
try {
browser = await puppeteer.launch(options);
} catch (ex) {
error = ex;
}
console.timeEnd(label);
});
afterAll(async () => {
if (browser) {
await browser.close();
}
});
describe('Browser is initialized', () => {
// Workaround for https://github.com/jasmine/jasmine/issues/1533.
// Jasmine will not report errors from beforeAll and instead fail all the tests that
// expect the browser to be available. This test allows us to ensure the setup was successful
// and if unsuccessful report the error
test('no errors on launch', () => {
expect(error).toBeUndefined();
expect(browser).toBeDefined();
});
});
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', headers: new Headers({
'X-Custom-Header': 'X-Custom-Value'
})
};
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();
}
});
});

View File

@ -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);
}
}
}

View File

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
@ -9,6 +10,7 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Xunit;
@ -16,6 +18,8 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
{
public class CorsMiddlewareTests
{
private const string OriginUrl = "http://api.example.com";
[Theory]
[InlineData("PuT")]
[InlineData("PUT")]
@ -26,7 +30,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
.Configure(app =>
{
app.UseCors(builder =>
builder.WithOrigins("http://localhost:5001")
builder.WithOrigins(OriginUrl)
.WithMethods("PUT"));
app.Run(async context =>
{
@ -40,14 +44,14 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
// Act
// Actual request.
var response = await server.CreateRequest("/")
.AddHeader(CorsConstants.Origin, "http://localhost:5001")
.AddHeader(CorsConstants.Origin, OriginUrl)
.SendAsync(accessControlRequestMethod);
// Assert
response.EnsureSuccessStatusCode();
Assert.Single(response.Headers);
Assert.Equal("Cross origin response", await response.Content.ReadAsStringAsync());
Assert.Equal("http://localhost:5001", response.Headers.GetValues(CorsConstants.AccessControlAllowOrigin).FirstOrDefault());
Assert.Equal(OriginUrl, response.Headers.GetValues(CorsConstants.AccessControlAllowOrigin).FirstOrDefault());
}
}
@ -59,7 +63,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
.Configure(app =>
{
app.UseCors(builder =>
builder.WithOrigins("http://localhost:5001")
builder.WithOrigins(OriginUrl)
.WithMethods("PUT")
.WithHeaders("Header1")
.WithExposedHeaders("AllowedHeader"));
@ -75,14 +79,14 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
// Act
// Actual request.
var response = await server.CreateRequest("/")
.AddHeader(CorsConstants.Origin, "http://localhost:5001")
.AddHeader(CorsConstants.Origin, OriginUrl)
.SendAsync("PUT");
// Assert
response.EnsureSuccessStatusCode();
Assert.Equal(2, response.Headers.Count());
Assert.Equal("Cross origin response", await response.Content.ReadAsStringAsync());
Assert.Equal("http://localhost:5001", response.Headers.GetValues(CorsConstants.AccessControlAllowOrigin).FirstOrDefault());
Assert.Equal(OriginUrl, response.Headers.GetValues(CorsConstants.AccessControlAllowOrigin).FirstOrDefault());
Assert.Equal("AllowedHeader", response.Headers.GetValues(CorsConstants.AccessControlExposeHeaders).FirstOrDefault());
}
}
@ -94,7 +98,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
{
// Arrange
var policy = new CorsPolicy();
policy.Origins.Add("http://localhost:5001");
policy.Origins.Add(OriginUrl);
policy.Methods.Add("PUT");
var hostBuilder = new WebHostBuilder()
@ -119,13 +123,13 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
// Act
// Preflight request.
var response = await server.CreateRequest("/")
.AddHeader(CorsConstants.Origin, "http://localhost:5001")
.AddHeader(CorsConstants.Origin, OriginUrl)
.SendAsync(preflightMethod);
// Assert
response.EnsureSuccessStatusCode();
Assert.Single(response.Headers);
Assert.Equal("http://localhost:5001", response.Headers.GetValues(CorsConstants.AccessControlAllowOrigin).FirstOrDefault());
Assert.Equal(OriginUrl, response.Headers.GetValues(CorsConstants.AccessControlAllowOrigin).FirstOrDefault());
}
}
@ -134,7 +138,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
{
// Arrange
var policy = new CorsPolicy();
policy.Origins.Add("http://localhost:5001");
policy.Origins.Add(OriginUrl);
policy.Methods.Add("PUT");
policy.Headers.Add("Header1");
policy.ExposedHeaders.Add("AllowedHeader");
@ -161,27 +165,105 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
// Act
// Preflight request.
var response = await server.CreateRequest("/")
.AddHeader(CorsConstants.Origin, "http://localhost:5001")
.AddHeader(CorsConstants.Origin, OriginUrl)
.AddHeader(CorsConstants.AccessControlRequestMethod, "PUT")
.SendAsync(CorsConstants.PreflightHttpMethod);
// Assert
response.EnsureSuccessStatusCode();
Assert.Equal(2, response.Headers.Count());
Assert.Equal("http://localhost:5001", response.Headers.GetValues(CorsConstants.AccessControlAllowOrigin).FirstOrDefault());
Assert.Equal("PUT", response.Headers.GetValues(CorsConstants.AccessControlAllowMethods).FirstOrDefault());
Assert.Collection(
response.Headers.OrderBy(h => h.Key),
kvp =>
{
Assert.Equal(CorsConstants.AccessControlAllowHeaders, kvp.Key);
Assert.Equal(new[] { "Header1" }, kvp.Value);
},
kvp =>
{
Assert.Equal(CorsConstants.AccessControlAllowMethods, kvp.Key);
Assert.Equal(new[] { "PUT" }, kvp.Value);
},
kvp =>
{
Assert.Equal(CorsConstants.AccessControlAllowOrigin, kvp.Key);
Assert.Equal(new[] { OriginUrl }, kvp.Value);
});
}
}
[Fact]
public async Task PreFlightRequest_DoesNotMatchPolicy_DoesNotSetHeaders()
public async Task PreFlight_WithCredentialsAllowed_ReflectsRequestHeaders()
{
// 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[] { "X-Test1,X-Test2" }, kvp.Value);
},
kvp =>
{
Assert.Equal(CorsConstants.AccessControlAllowMethods, kvp.Key);
Assert.Equal(new[] { "PUT" }, kvp.Value);
},
kvp =>
{
Assert.Equal(CorsConstants.AccessControlAllowOrigin, kvp.Key);
Assert.Equal(new[] { OriginUrl }, kvp.Value);
});
}
}
[Fact]
public async Task PreFlightRequest_DoesNotMatchPolicy_DoesNotSetHeadersAndReturnsForbidden()
{
// Arrange
var hostBuilder = new WebHostBuilder()
.Configure(app =>
{
app.UseCors(builder =>
builder.WithOrigins("http://localhost:5001")
builder.WithOrigins(OriginUrl)
.WithMethods("PUT")
.WithHeaders("Header1")
.WithExposedHeaders("AllowedHeader"));
@ -197,7 +279,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
// Act
// Preflight request.
var response = await server.CreateRequest("/")
.AddHeader(CorsConstants.Origin, "http://localhost:5002")
.AddHeader(CorsConstants.Origin, "http://test.example.com")
.AddHeader(CorsConstants.AccessControlRequestMethod, "PUT")
.SendAsync(CorsConstants.PreflightHttpMethod);
@ -215,7 +297,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
.Configure(app =>
{
app.UseCors(builder =>
builder.WithOrigins("http://localhost:5001")
builder.WithOrigins(OriginUrl)
.WithMethods("PUT")
.WithHeaders("Header1")
.WithExposedHeaders("AllowedHeader"));
@ -231,7 +313,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
// Act
// Actual request.
var response = await server.CreateRequest("/")
.AddHeader(CorsConstants.Origin, "http://localhost:5002")
.AddHeader(CorsConstants.Origin, "http://test.example.com")
.SendAsync("PUT");
// Assert
@ -246,6 +328,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
// Arrange
var corsService = Mock.Of<ICorsService>();
var mockProvider = new Mock<ICorsPolicyProvider>();
var loggerFactory = NullLoggerFactory.Instance;
mockProvider.Setup(o => o.GetPolicyAsync(It.IsAny<HttpContext>(), It.IsAny<string>()))
.Returns(Task.FromResult<CorsPolicy>(null))
.Verifiable();
@ -254,6 +337,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
Mock.Of<RequestDelegate>(),
corsService,
mockProvider.Object,
loggerFactory,
policyName: null);
var httpContext = new DefaultHttpContext();
@ -274,6 +358,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
// Arrange
var corsService = Mock.Of<ICorsService>();
var mockProvider = new Mock<ICorsPolicyProvider>();
var loggerFactory = NullLoggerFactory.Instance;
mockProvider.Setup(o => o.GetPolicyAsync(It.IsAny<HttpContext>(), It.IsAny<string>()))
.Returns(Task.FromResult<CorsPolicy>(null))
.Verifiable();
@ -282,6 +367,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
Mock.Of<RequestDelegate>(),
corsService,
mockProvider.Object,
loggerFactory,
policyName: null);
var httpContext = new DefaultHttpContext();
@ -318,7 +404,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
options.AddDefaultPolicy(policyBuilder =>
{
policyBuilder
.WithOrigins("http://localhost:5001")
.WithOrigins(OriginUrl)
.WithMethods("PUT")
.WithHeaders("Header1")
.WithExposedHeaders("AllowedHeader")
@ -327,7 +413,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
options.AddPolicy("policy2", policyBuilder =>
{
policyBuilder
.WithOrigins("http://localhost:5002")
.WithOrigins("http://test.example.com")
.Build();
});
});
@ -338,16 +424,146 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
// Act
// Preflight request.
var response = await server.CreateRequest("/")
.AddHeader(CorsConstants.Origin, "http://localhost:5001")
.AddHeader(CorsConstants.Origin, OriginUrl)
.AddHeader(CorsConstants.AccessControlRequestMethod, "PUT")
.SendAsync(CorsConstants.PreflightHttpMethod);
// Assert
response.EnsureSuccessStatusCode();
Assert.Equal(2, response.Headers.Count());
Assert.Equal("http://localhost:5001", response.Headers.GetValues(CorsConstants.AccessControlAllowOrigin).FirstOrDefault());
Assert.Equal("PUT", response.Headers.GetValues(CorsConstants.AccessControlAllowMethods).FirstOrDefault());
Assert.Collection(
response.Headers.OrderBy(h => h.Key),
kvp =>
{
Assert.Equal(CorsConstants.AccessControlAllowHeaders, kvp.Key);
Assert.Equal(new[] { "Header1" }, kvp.Value);
},
kvp =>
{
Assert.Equal(CorsConstants.AccessControlAllowMethods, kvp.Key);
Assert.Equal(new[] { "PUT" }, kvp.Value);
},
kvp =>
{
Assert.Equal(CorsConstants.AccessControlAllowOrigin, kvp.Key);
Assert.Equal(new[] { OriginUrl }, kvp.Value);
});
}
}
[Fact]
public async Task CorsRequest_SetsResponseHeaders()
{
// Arrange
var hostBuilder = new WebHostBuilder()
.Configure(app =>
{
app.UseCors(builder =>
builder.WithOrigins(OriginUrl)
.WithMethods("PUT")
.WithHeaders("Header1")
.WithExposedHeaders("AllowedHeader"));
app.Run(async context =>
{
context.Response.Headers.Add("Test", "Should-Appear");
await context.Response.WriteAsync("Cross origin response");
});
})
.ConfigureServices(services => services.AddCors());
using (var server = new TestServer(hostBuilder))
{
// Act
// Actual request.
var response = await server.CreateRequest("/")
.AddHeader(CorsConstants.Origin, OriginUrl)
.SendAsync("PUT");
// Assert
response.EnsureSuccessStatusCode();
Assert.Collection(
response.Headers.OrderBy(o => o.Key),
kvp =>
{
Assert.Equal(CorsConstants.AccessControlAllowOrigin, kvp.Key);
Assert.Equal(OriginUrl, Assert.Single(kvp.Value));
},
kvp =>
{
Assert.Equal(CorsConstants.AccessControlExposeHeaders, kvp.Key);
Assert.Equal("AllowedHeader", Assert.Single(kvp.Value));
},
kvp =>
{
Assert.Equal("Test", kvp.Key);
Assert.Equal("Should-Appear", Assert.Single(kvp.Value));
});
Assert.Equal("Cross origin response", await response.Content.ReadAsStringAsync());
}
}
[Fact]
public async Task CorsRequest_SetsResponseHeader_IfExceptionHandlerClearsResponse()
{
// Arrange
var exceptionSeen = true;
var hostBuilder = new WebHostBuilder()
.Configure(app =>
{
// Simulate ExceptionHandler middleware
app.Use(async (context, next) =>
{
try
{
await next();
}
catch (Exception)
{
exceptionSeen = true;
context.Response.Clear();
context.Response.StatusCode = 500;
}
});
app.UseCors(builder =>
builder.WithOrigins(OriginUrl)
.WithMethods("PUT")
.WithHeaders("Header1")
.WithExposedHeaders("AllowedHeader"));
app.Run(context =>
{
context.Response.Headers.Add("Test", "Should-Not-Exist");
throw new Exception("Runtime error");
});
})
.ConfigureServices(services => services.AddCors());
using (var server = new TestServer(hostBuilder))
{
// Act
// Actual request.
var response = await server.CreateRequest("/")
.AddHeader(CorsConstants.Origin, OriginUrl)
.SendAsync("PUT");
// Assert
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
Assert.True(exceptionSeen, "We expect exception middleware to have executed");
Assert.Collection(
response.Headers.OrderBy(o => o.Key),
kvp =>
{
Assert.Equal(CorsConstants.AccessControlAllowOrigin, kvp.Key);
Assert.Equal(OriginUrl, Assert.Single(kvp.Value));
},
kvp =>
{
Assert.Equal(CorsConstants.AccessControlExposeHeaders, kvp.Key);
Assert.Equal("AllowedHeader", Assert.Single(kvp.Value));
});
}
}
}
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
@ -128,6 +128,17 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
Assert.Equal(new List<string>() { "http://example.com", "http://example2.com" }, corsPolicy.Origins);
}
[Fact]
public void WithOrigins_NormalizesOrigins()
{
// Arrange
var builder = new CorsPolicyBuilder("http://www.EXAMPLE.com", "HTTPS://example2.com");
// Assert
var corsPolicy = builder.Build();
Assert.Equal(new List<string>() { "http://www.example.com", "https://example2.com" }, corsPolicy.Origins);
}
[Fact]
public void AllowAnyOrigin_AllowsAny()
{
@ -288,5 +299,85 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
var corsPolicy = builder.Build();
Assert.False(corsPolicy.SupportsCredentials);
}
[Theory]
[InlineData("Some-String", "some-string")]
[InlineData("x:\\Test", "x:\\test")]
[InlineData("FTP://Some-url", "ftp://some-url")]
public void GetNormalizedOrigin_ReturnsLowerCasedValue_IfStringIsNotHttpOrHttpsUrl(string origin, string expected)
{
// Act
var normalizedOrigin = CorsPolicyBuilder.GetNormalizedOrigin(origin);
// Assert
Assert.Equal(expected, normalizedOrigin);
}
[Fact]
public void GetNormalizedOrigin_DoesNotAddPort_IfUriDoesNotSpecifyOne()
{
// Arrange
var origin = "http://www.example.com";
// Act
var normalizedOrigin = CorsPolicyBuilder.GetNormalizedOrigin(origin);
// Assert
Assert.Equal(origin, normalizedOrigin);
}
[Fact]
public void GetNormalizedOrigin_LowerCasesScheme()
{
// Arrange
var origin = "HTTP://www.example.com";
// Act
var normalizedOrigin = CorsPolicyBuilder.GetNormalizedOrigin(origin);
// Assert
Assert.Equal("http://www.example.com", normalizedOrigin);
}
[Fact]
public void GetNormalizedOrigin_LowerCasesHost()
{
// Arrange
var origin = "http://www.Example.Com";
// Act
var normalizedOrigin = CorsPolicyBuilder.GetNormalizedOrigin(origin);
// Assert
Assert.Equal("http://www.example.com", normalizedOrigin);
}
[Theory]
[InlineData("http://www.Example.com:80", "http://www.example.com:80")]
[InlineData("https://www.Example.com:8080", "https://www.example.com:8080")]
public void GetNormalizedOrigin_PreservesPort_ForNonIdnHosts(string origin, string expected)
{
// Act
var normalizedOrigin = CorsPolicyBuilder.GetNormalizedOrigin(origin);
// Assert
Assert.Equal(expected, normalizedOrigin);
}
[Theory]
[InlineData("http://Bücher.example", "http://xn--bcher-kva.example")]
[InlineData("http://Bücher.example.com:83", "http://xn--bcher-kva.example.com:83")]
[InlineData("https://example.қаз", "https://example.xn--80ao21a")]
[InlineData("http://😉.fm", "http://xn--n28h.fm", Skip = "Fails on Win2k8 R2")]
// Note that in following case, the default port (443 for HTTPS) is not preserved.
[InlineData("https://www.example.இந்தியா:443", "https://www.example.xn--xkc2dl3a5ee0h")]
public void GetNormalizedOrigin_ReturnsPunyCodedOrigin(string origin, string expected)
{
// Act
var normalizedOrigin = CorsPolicyBuilder.GetNormalizedOrigin(origin);
// Assert
Assert.Equal(expected, normalizedOrigin);
}
}
}
}

View File

@ -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();
}
}
}

View File

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

View File

@ -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; }
}
}

View File

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

View File

@ -4,6 +4,8 @@
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified" />
</handlers>
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false" />
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false">
<environmentVariables />
</aspNetCore>
</system.webServer>
</configuration>

View File

@ -1,6 +1,6 @@
<Project>
<Project>
<PropertyGroup>
<VersionPrefix>2.1.1</VersionPrefix>
<VersionPrefix>2.2.0</VersionPrefix>
<VersionSuffix>rtm</VersionSuffix>
<PackageVersion Condition="'$(IsFinalBuild)' == 'true' AND '$(VersionSuffix)' == 'rtm' ">$(VersionPrefix)</PackageVersion>
<PackageVersion Condition="'$(IsFinalBuild)' == 'true' AND '$(VersionSuffix)' != 'rtm' ">$(VersionPrefix)-$(VersionSuffix)-final</PackageVersion>