Merge remote-tracking branch 'CORS/rybrande/release21ToSrc' into rybrande/Mondo2.1
This commit is contained in:
commit
af2f57386e
|
|
@ -0,0 +1,41 @@
|
|||
[Oo]bj/
|
||||
[Bb]in/
|
||||
TestResults/
|
||||
.nuget/
|
||||
*.sln.ide/
|
||||
_ReSharper.*/
|
||||
packages/
|
||||
artifacts/
|
||||
PublishProfiles/
|
||||
.vs/
|
||||
bower_components/
|
||||
node_modules/
|
||||
**/wwwroot/lib/
|
||||
debugSettings.json
|
||||
project.lock.json
|
||||
*.user
|
||||
*.suo
|
||||
*.cache
|
||||
*.docstates
|
||||
_ReSharper.*
|
||||
nuget.exe
|
||||
*net45.csproj
|
||||
*net451.csproj
|
||||
*k10.csproj
|
||||
*.psess
|
||||
*.vsp
|
||||
*.pidb
|
||||
*.userprefs
|
||||
*DS_Store
|
||||
*.ncrunchsolution
|
||||
*.*sdf
|
||||
*.ipch
|
||||
.settings
|
||||
*.sln.ide
|
||||
node_modules
|
||||
**/[Cc]ompiler/[Rr]esources/**/*.js
|
||||
*launchSettings.json
|
||||
.build/
|
||||
.testPublish/
|
||||
.vscode
|
||||
global.json
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.26817.0
|
||||
MinimumVisualStudioVersion = 15.0.26730.03
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{84FE6872-A610-4CEC-855F-A84CBF1F40FC}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
src\Directory.Build.props = src\Directory.Build.props
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{F32074C7-087C-46CC-A913-422BFD2D6E0A}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
test\Directory.Build.props = test\Directory.Build.props
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Cors", "src\Microsoft.AspNetCore.Cors\Microsoft.AspNetCore.Cors.csproj", "{41349FCD-D1C4-47A6-82D0-D16D00A8D59D}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Cors.Test", "test\Microsoft.AspNetCore.Cors.Test\Microsoft.AspNetCore.Cors.Test.csproj", "{F05BE96F-F869-4408-A480-96935B4835EE}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebSites", "WebSites", "{538380BF-0D4C-4E30-8F41-E75C4B1C01FA}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CorsMiddlewareWebSite", "test\WebSites\CorsMiddlewareWebSite\CorsMiddlewareWebSite.csproj", "{B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{960E0703-A8A5-44DF-AA87-B7C614683B3C}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleDestination", "samples\SampleDestination\SampleDestination.csproj", "{F6675DC1-AA21-453B-89B6-DA425FB9C3A5}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleOrigin", "samples\SampleOrigin\SampleOrigin.csproj", "{99460370-AE5D-4DC9-8DBF-04DF66D6B21D}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0CCC5C1B-F548-4A17-96F8-14C700093FA0}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.appveyor.yml = .appveyor.yml
|
||||
.gitattributes = .gitattributes
|
||||
.gitignore = .gitignore
|
||||
.travis.yml = .travis.yml
|
||||
build.cmd = build.cmd
|
||||
build.ps1 = build.ps1
|
||||
build.sh = build.sh
|
||||
CONTRIBUTING.md = CONTRIBUTING.md
|
||||
Directory.Build.props = Directory.Build.props
|
||||
Directory.Build.targets = Directory.Build.targets
|
||||
LICENSE.txt = LICENSE.txt
|
||||
NuGet.config = NuGet.config
|
||||
NuGetPackageVerifier.json = NuGetPackageVerifier.json
|
||||
README.md = README.md
|
||||
version.xml = version.xml
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{41349FCD-D1C4-47A6-82D0-D16D00A8D59D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{41349FCD-D1C4-47A6-82D0-D16D00A8D59D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{41349FCD-D1C4-47A6-82D0-D16D00A8D59D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{41349FCD-D1C4-47A6-82D0-D16D00A8D59D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F05BE96F-F869-4408-A480-96935B4835EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F05BE96F-F869-4408-A480-96935B4835EE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F05BE96F-F869-4408-A480-96935B4835EE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F05BE96F-F869-4408-A480-96935B4835EE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F6675DC1-AA21-453B-89B6-DA425FB9C3A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F6675DC1-AA21-453B-89B6-DA425FB9C3A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F6675DC1-AA21-453B-89B6-DA425FB9C3A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F6675DC1-AA21-453B-89B6-DA425FB9C3A5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{99460370-AE5D-4DC9-8DBF-04DF66D6B21D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{99460370-AE5D-4DC9-8DBF-04DF66D6B21D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{99460370-AE5D-4DC9-8DBF-04DF66D6B21D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{99460370-AE5D-4DC9-8DBF-04DF66D6B21D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{41349FCD-D1C4-47A6-82D0-D16D00A8D59D} = {84FE6872-A610-4CEC-855F-A84CBF1F40FC}
|
||||
{F05BE96F-F869-4408-A480-96935B4835EE} = {F32074C7-087C-46CC-A913-422BFD2D6E0A}
|
||||
{538380BF-0D4C-4E30-8F41-E75C4B1C01FA} = {F32074C7-087C-46CC-A913-422BFD2D6E0A}
|
||||
{B42D4844-FFF8-4EC2-88D1-3AE95234D9EB} = {538380BF-0D4C-4E30-8F41-E75C4B1C01FA}
|
||||
{F6675DC1-AA21-453B-89B6-DA425FB9C3A5} = {960E0703-A8A5-44DF-AA87-B7C614683B3C}
|
||||
{99460370-AE5D-4DC9-8DBF-04DF66D6B21D} = {960E0703-A8A5-44DF-AA87-B7C614683B3C}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {F9ED9C53-44CD-4853-9621-D028B7B6A431}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<Project>
|
||||
<Import
|
||||
Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), AspNetCoreSettings.props))\AspNetCoreSettings.props"
|
||||
Condition=" '$(CI)' != 'true' AND '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), AspNetCoreSettings.props))' != '' " />
|
||||
|
||||
<Import Project="version.props" />
|
||||
<Import Project="build\dependencies.props" />
|
||||
<Import Project="build\sources.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<Product>Microsoft ASP.NET Core</Product>
|
||||
<RepositoryUrl>https://github.com/aspnet/CORS</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<RepositoryRoot>$(MSBuildThisFileDirectory)</RepositoryRoot>
|
||||
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)build\Key.snk</AssemblyOriginatorKeyFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
<PublicSign Condition="'$(OS)' != 'Windows_NT'">true</PublicSign>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<Project>
|
||||
<PropertyGroup>
|
||||
<RuntimeFrameworkVersion Condition=" '$(TargetFramework)' == 'netcoreapp2.0' ">$(MicrosoftNETCoreApp20PackageVersion)</RuntimeFrameworkVersion>
|
||||
<RuntimeFrameworkVersion Condition=" '$(TargetFramework)' == 'netcoreapp2.1' ">$(MicrosoftNETCoreApp21PackageVersion)</RuntimeFrameworkVersion>
|
||||
<NETStandardImplicitPackageVersion Condition=" '$(TargetFramework)' == 'netstandard2.0' ">$(NETStandardLibrary20PackageVersion)</NETStandardImplicitPackageVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"Default": {
|
||||
"rules": [
|
||||
"DefaultCompositeRule"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
CORS
|
||||
===
|
||||
AppVeyor: [](https://ci.appveyor.com/project/aspnetci/CORS/branch/dev)
|
||||
|
||||
Travis: [](https://travis-ci.org/aspnet/CORS)
|
||||
|
||||
CORS repository includes the core implementation for CORS policy, utilized by the CORS middleware and MVC.
|
||||
|
||||
This project is part of ASP.NET Core. You can find samples, documentation and getting started instructions for ASP.NET Core at the [Home](https://github.com/aspnet/home) repo.
|
||||
Binary file not shown.
|
|
@ -0,0 +1,10 @@
|
|||
@Library('dotnet-ci') _
|
||||
|
||||
simpleNode('Ubuntu16.04', 'latest-or-auto-docker') {
|
||||
stage ('Checking out source') {
|
||||
checkout scm
|
||||
}
|
||||
stage ('Build') {
|
||||
sh './build.sh --ci'
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
@Library('dotnet-ci') _
|
||||
|
||||
simpleNode('OSX10.12','latest') {
|
||||
stage ('Checking out source') {
|
||||
checkout scm
|
||||
}
|
||||
stage ('Build') {
|
||||
sh './build.sh --ci'
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import org.dotnet.ci.pipelines.Pipeline
|
||||
|
||||
def windowsPipeline = Pipeline.createPipeline(this, 'build/buildpipeline/windows.groovy')
|
||||
def linuxPipeline = Pipeline.createPipeline(this, 'build/buildpipeline/linux.groovy')
|
||||
def osxPipeline = Pipeline.createPipeline(this, 'build/buildpipeline/osx.groovy')
|
||||
String configuration = 'Release'
|
||||
def parameters = [
|
||||
'Configuration': configuration
|
||||
]
|
||||
|
||||
windowsPipeline.triggerPipelineOnEveryGithubPR("Windows ${configuration} x64 Build", parameters)
|
||||
windowsPipeline.triggerPipelineOnGithubPush(parameters)
|
||||
|
||||
linuxPipeline.triggerPipelineOnEveryGithubPR("Ubuntu 16.04 ${configuration} Build", parameters)
|
||||
linuxPipeline.triggerPipelineOnGithubPush(parameters)
|
||||
|
||||
osxPipeline.triggerPipelineOnEveryGithubPR("OSX 10.12 ${configuration} Build", parameters)
|
||||
osxPipeline.triggerPipelineOnGithubPush(parameters)
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
@Library('dotnet-ci') _
|
||||
|
||||
// 'node' indicates to Jenkins that the enclosed block runs on a node that matches
|
||||
// the label 'windows-with-vs'
|
||||
simpleNode('Windows_NT','latest') {
|
||||
stage ('Checking out source') {
|
||||
checkout scm
|
||||
}
|
||||
stage ('Build') {
|
||||
bat '.\\run.cmd -CI default-build'
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<Project>
|
||||
<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>
|
||||
<MicrosoftNETTestSdkPackageVersion>15.6.1</MicrosoftNETTestSdkPackageVersion>
|
||||
<MoqPackageVersion>4.7.49</MoqPackageVersion>
|
||||
<NETStandardLibrary20PackageVersion>2.0.3</NETStandardLibrary20PackageVersion>
|
||||
<XunitAnalyzersPackageVersion>0.8.0</XunitAnalyzersPackageVersion>
|
||||
<XunitPackageVersion>2.3.1</XunitPackageVersion>
|
||||
<XunitRunnerVisualStudioPackageVersion>2.4.0-beta.1.build3945</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>
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<Project>
|
||||
<Import Project="dependencies.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- These properties are use by the automation that updates dependencies.props -->
|
||||
<LineupPackageId>Internal.AspNetCore.Universe.Lineup</LineupPackageId>
|
||||
<LineupPackageVersion>2.1.0-rc1-*</LineupPackageVersion>
|
||||
<LineupPackageRestoreSource>https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json</LineupPackageRestoreSource>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<DotNetCoreRuntime Include="$(MicrosoftNETCoreApp20PackageVersion)" />
|
||||
<DotNetCoreRuntime Include="$(MicrosoftNETCoreApp21PackageVersion)" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<Project>
|
||||
<Import Project="$(DotNetRestoreSourcePropsPath)" Condition="'$(DotNetRestoreSourcePropsPath)' != ''"/>
|
||||
|
||||
<PropertyGroup Label="RestoreSources">
|
||||
<RestoreSources>$(DotNetRestoreSources)</RestoreSources>
|
||||
<RestoreSources Condition="'$(DotNetBuildOffline)' != 'true' AND '$(AspNetUniverseBuildOffline)' != 'true' ">
|
||||
$(RestoreSources);
|
||||
https://dotnet.myget.org/F/dotnet-core/api/v3/index.json;
|
||||
https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json;
|
||||
https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json;
|
||||
</RestoreSources>
|
||||
<RestoreSources Condition="'$(DotNetBuildOffline)' != 'true'">
|
||||
$(RestoreSources);
|
||||
https://api.nuget.org/v3/index.json;
|
||||
</RestoreSources>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
# CORS Sample
|
||||
|
||||
This sample consists of a request origin (SampleOrigin) and a request destination (SampleDestination). Both have different domain names, to simulate a CORS request.
|
||||
|
||||
## Modify Hosts File
|
||||
To run this CORS sample, modify the hosts file to register the hostnames `destination.example.com` and `origin.example.com`.
|
||||
### Windows:
|
||||
Run a text editor (e.g. Notepad) as an Administrator. Open the hosts file on the path: "C:\Windows\System32\drivers\etc\hosts".
|
||||
|
||||
### Linux:
|
||||
On a Terminal window, type "sudo nano /etc/hosts" and enter your admin password when prompted.
|
||||
|
||||
In the hosts file, add the following to the bottom of the file:
|
||||
|
||||
```
|
||||
127.0.0.1 destination.example.com
|
||||
127.0.0.1 origin.example.com
|
||||
```
|
||||
|
||||
Save the file and close it. Then clear your browser history.
|
||||
|
||||
## Run the sample
|
||||
The SampleOrigin application will use port 5001, and SampleDestination will use 5000. Please ensure there are no other processes using those ports before running the CORS sample.
|
||||
|
||||
* In a command prompt window, open the directory where you cloned the repository, and open the SampleDestination directory. Run the command: dotnet run
|
||||
* Repeat the above step in the SampleOrigin directory
|
||||
* Open a browser window and go to `http://origin.example.com:5001`
|
||||
* Input a method and header to create a CORS request or use one of the example buttons to see CORS in action
|
||||
|
||||
As an example, apart from `GET`, `HEAD` and `POST` requests, `PUT` requests are allowed in the CORS policy on SampleDestination. Any others, like `DELETE`, `OPTIONS` etc. are not allowed and throw an error.
|
||||
`Cache-Control` has been added as an allowed header to the sample. Any other headers are not allowed and throw an error. You may leave the header name and value blank.
|
||||
|
||||
To edit the policy, please see `app.UseCors()` method in the `Startup.cs` file of SampleDestination.
|
||||
|
||||
**If using Visual Studio to launch the request origin:**
|
||||
Open Visual Studio and in the `launchSettings.json` file for the SampleOrigin project, change the `launchUrl` under SampleOrigin to `http://origin.example.com:5001`.
|
||||
Using the dropdown near the Start button, choose SampleOrigin before pressing Start to ensure that it uses Kestrel and not IIS Express.
|
||||
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.IO;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace SampleDestination
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var host = new WebHostBuilder()
|
||||
.UseKestrel()
|
||||
.UseUrls("http://*:5000")
|
||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||
.ConfigureLogging(factory => factory.AddConsole())
|
||||
.UseStartup<Startup>()
|
||||
.Build();
|
||||
|
||||
host.Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netcoreapp2.1;net461</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Cors\Microsoft.AspNetCore.Cors.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(MicrosoftAspNetCoreServerKestrelPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace SampleDestination
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddCors();
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
|
||||
{
|
||||
app.UseCors(policy => policy
|
||||
.WithOrigins("http://origin.example.com:5001")
|
||||
.WithMethods("PUT")
|
||||
.WithHeaders("Cache-Control"));
|
||||
|
||||
app.Run(async context =>
|
||||
{
|
||||
var responseHeaders = context.Response.Headers;
|
||||
context.Response.ContentType = "text/plain";
|
||||
foreach (var responseHeader in responseHeaders)
|
||||
{
|
||||
await context.Response.WriteAsync("\n" + responseHeader.Key + ": " + responseHeader.Value);
|
||||
}
|
||||
|
||||
await context.Response.WriteAsync("\nStatus code of your request: " + context.Response.StatusCode.ToString());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.IO;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace SampleOrigin
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var host = new WebHostBuilder()
|
||||
.UseKestrel()
|
||||
.UseUrls("http://*:5001")
|
||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||
.ConfigureLogging(factory => factory.AddConsole())
|
||||
.UseStartup<Startup>()
|
||||
.Build();
|
||||
|
||||
host.Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netcoreapp2.1;net461</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(MicrosoftAspNetCoreServerKestrelPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace SampleOrigin
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
|
||||
{
|
||||
app.Run(context =>
|
||||
{
|
||||
var fileInfoProvider = env.WebRootFileProvider;
|
||||
var fileInfo = fileInfoProvider.GetFileInfo("/Index.html");
|
||||
context.Response.ContentType = "text/html";
|
||||
return context.Response.SendFileAsync(fileInfo);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<style>
|
||||
p {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.button {
|
||||
border: none;
|
||||
color: white;
|
||||
padding: 10px 15px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
margin: 5px 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.green {
|
||||
background-color: #4CAF50;
|
||||
}
|
||||
|
||||
.red {
|
||||
background-color: indianred;
|
||||
}
|
||||
|
||||
.gray {
|
||||
background-color: gray;
|
||||
}
|
||||
</style>
|
||||
<title>CORS Sample</title>
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript">
|
||||
// Make the CORS request.
|
||||
function makeCORSRequest(method, headerName, headerValue) {
|
||||
// Destination server with CORS enabled.
|
||||
var url = 'http://destination.example.com:5000/';
|
||||
var request = new XMLHttpRequest();
|
||||
request.open(method, url, true);
|
||||
if (headerName && headerValue) {
|
||||
request.setRequestHeader(headerName, headerValue);
|
||||
}
|
||||
|
||||
if (!request) {
|
||||
alert('CORS not supported');
|
||||
return;
|
||||
}
|
||||
|
||||
// Response handlers.
|
||||
request.onload = function () {
|
||||
var text = request.responseText;
|
||||
alert('Response from CORS ' + method + ' request to ' + url + ': ' + text);
|
||||
};
|
||||
|
||||
request.onerror = function () {
|
||||
alert('There was an error making the request for method ' + method);
|
||||
};
|
||||
|
||||
request.send();
|
||||
}
|
||||
</script>
|
||||
|
||||
<p>CORS Sample</p>
|
||||
Method: <input type="text" id="methodName" /><br /><br />
|
||||
Header Name: <input type="text" id="headerName" /> Header Value: <input type="text" id="headerValue" /><br /><br />
|
||||
|
||||
<script>
|
||||
document.getElementById('headerValue')
|
||||
.addEventListener("keyup", function (event) {
|
||||
event.preventDefault();
|
||||
if (event.keyCode == 13) {
|
||||
document.getElementById("CORS").click();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<button class="button gray" id="CORS" type="submit" onclick="makeCORSRequest(document.getElementById('methodName').value, document.getElementById('headerName').value, document.getElementById('headerValue').value);">Make a CORS Request</button><br /><br /><br /><br />
|
||||
|
||||
Method DELETE is not allowed:<button class="button red" id="InvalidMethodCORS" type="submit" onclick="makeCORSRequest('DELETE', 'Cache-Control', 'no-cache');">Invalid Method CORS Request</button>
|
||||
Method PUT is allowed:<button class="button green" id="ValidMethodCORS" type="submit" onclick="makeCORSRequest('PUT', 'Cache-Control', 'no-cache');">Valid Method CORS Request</button><br /><br />
|
||||
|
||||
Header 'Max-Forwards' not supported:<button class="button red" id="InvalidHeaderCORS" type="submit" onclick="makeCORSRequest('PUT', 'Max-Forwards', '2');">Invalid Header CORS Request</button>
|
||||
Header 'Cache-Control' is supported:<button class="button green" id="ValidHeaderCORS" type="submit" onclick="makeCORSRequest('PUT', 'Cache-Control', 'no-cache');">Valid Header CORS Request</button><br /><br />
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<Project>
|
||||
<Import Project="..\Directory.Build.props" />
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Internal.AspNetCore.Sdk" PrivateAssets="All" Version="$(InternalAspNetCoreSdkPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Cors.Infrastructure;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods for setting up cross-origin resource sharing services in an <see cref="IServiceCollection" />.
|
||||
/// </summary>
|
||||
public static class CorsServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds cross-origin resource sharing services to the specified <see cref="IServiceCollection" />.
|
||||
/// </summary>
|
||||
/// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
|
||||
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
|
||||
public static IServiceCollection AddCors(this IServiceCollection services)
|
||||
{
|
||||
if (services == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(services));
|
||||
}
|
||||
|
||||
services.AddOptions();
|
||||
|
||||
services.TryAdd(ServiceDescriptor.Transient<ICorsService, CorsService>());
|
||||
services.TryAdd(ServiceDescriptor.Transient<ICorsPolicyProvider, DefaultCorsPolicyProvider>());
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds cross-origin resource sharing services to the specified <see cref="IServiceCollection" />.
|
||||
/// </summary>
|
||||
/// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
|
||||
/// <param name="setupAction">An <see cref="Action{CorsOptions}"/> to configure the provided <see cref="CorsOptions"/>.</param>
|
||||
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
|
||||
public static IServiceCollection AddCors(this IServiceCollection services, Action<CorsOptions> setupAction)
|
||||
{
|
||||
if (services == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(services));
|
||||
}
|
||||
|
||||
if (setupAction == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(setupAction));
|
||||
}
|
||||
|
||||
services.AddCors();
|
||||
services.Configure(setupAction);
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Cors.Infrastructure;
|
||||
|
||||
namespace Microsoft.AspNetCore.Cors
|
||||
{
|
||||
/// <inheritdoc />
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
|
||||
public class DisableCorsAttribute : Attribute, IDisableCorsAttribute
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Cors.Infrastructure;
|
||||
|
||||
namespace Microsoft.AspNetCore.Cors
|
||||
{
|
||||
/// <inheritdoc />
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
||||
public class EnableCorsAttribute : Attribute, IEnableCorsAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="EnableCorsAttribute"/> with the default policy
|
||||
/// name defined by <see cref="CorsOptions.DefaultPolicyName"/>.
|
||||
/// </summary>
|
||||
public EnableCorsAttribute()
|
||||
: this(policyName: null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="EnableCorsAttribute"/> with the supplied policy name.
|
||||
/// </summary>
|
||||
/// <param name="policyName">The name of the policy to be applied.</param>
|
||||
public EnableCorsAttribute(string policyName)
|
||||
{
|
||||
PolicyName = policyName;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string PolicyName { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Microsoft.AspNetCore.Cors.Infrastructure
|
||||
{
|
||||
/// <summary>
|
||||
/// CORS-related constants.
|
||||
/// </summary>
|
||||
public static class CorsConstants
|
||||
{
|
||||
/// <summary>
|
||||
/// The HTTP method for the CORS preflight request.
|
||||
/// </summary>
|
||||
public static readonly string PreflightHttpMethod = HttpMethods.Options;
|
||||
|
||||
/// <summary>
|
||||
/// The Origin request header.
|
||||
/// </summary>
|
||||
public static readonly string Origin = HeaderNames.Origin;
|
||||
|
||||
/// <summary>
|
||||
/// The value for the Access-Control-Allow-Origin response header to allow all origins.
|
||||
/// </summary>
|
||||
public static readonly string AnyOrigin = "*";
|
||||
|
||||
/// <summary>
|
||||
/// The Access-Control-Request-Method request header.
|
||||
/// </summary>
|
||||
public static readonly string AccessControlRequestMethod = HeaderNames.AccessControlRequestMethod;
|
||||
|
||||
/// <summary>
|
||||
/// The Access-Control-Request-Headers request header.
|
||||
/// </summary>
|
||||
public static readonly string AccessControlRequestHeaders = HeaderNames.AccessControlRequestHeaders;
|
||||
|
||||
/// <summary>
|
||||
/// The Access-Control-Allow-Origin response header.
|
||||
/// </summary>
|
||||
public static readonly string AccessControlAllowOrigin = HeaderNames.AccessControlAllowOrigin;
|
||||
|
||||
/// <summary>
|
||||
/// The Access-Control-Allow-Headers response header.
|
||||
/// </summary>
|
||||
public static readonly string AccessControlAllowHeaders = HeaderNames.AccessControlAllowHeaders;
|
||||
|
||||
/// <summary>
|
||||
/// The Access-Control-Expose-Headers response header.
|
||||
/// </summary>
|
||||
public static readonly string AccessControlExposeHeaders = HeaderNames.AccessControlExposeHeaders;
|
||||
|
||||
/// <summary>
|
||||
/// The Access-Control-Allow-Methods response header.
|
||||
/// </summary>
|
||||
public static readonly string AccessControlAllowMethods = HeaderNames.AccessControlAllowMethods;
|
||||
|
||||
/// <summary>
|
||||
/// The Access-Control-Allow-Credentials response header.
|
||||
/// </summary>
|
||||
public static readonly string AccessControlAllowCredentials = HeaderNames.AccessControlAllowCredentials;
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
// 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.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.Cors.Infrastructure
|
||||
{
|
||||
/// <summary>
|
||||
/// An ASP.NET middleware for handling CORS.
|
||||
/// </summary>
|
||||
public class CorsMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly ICorsService _corsService;
|
||||
private readonly ICorsPolicyProvider _corsPolicyProvider;
|
||||
private readonly CorsPolicy _policy;
|
||||
private readonly string _corsPolicyName;
|
||||
|
||||
/// <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>
|
||||
public CorsMiddleware(
|
||||
RequestDelegate next,
|
||||
ICorsService corsService,
|
||||
ICorsPolicyProvider policyProvider)
|
||||
: this(next, corsService, policyProvider, 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="policyName">An optional name of the policy to be fetched.</param>
|
||||
public CorsMiddleware(
|
||||
RequestDelegate next,
|
||||
ICorsService corsService,
|
||||
ICorsPolicyProvider policyProvider,
|
||||
string policyName)
|
||||
{
|
||||
if (next == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(next));
|
||||
}
|
||||
|
||||
if (corsService == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(corsService));
|
||||
}
|
||||
|
||||
if (policyProvider == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(policyProvider));
|
||||
}
|
||||
|
||||
_next = next;
|
||||
_corsService = corsService;
|
||||
_corsPolicyProvider = policyProvider;
|
||||
_corsPolicyName = 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>
|
||||
public CorsMiddleware(
|
||||
RequestDelegate next,
|
||||
ICorsService corsService,
|
||||
CorsPolicy policy)
|
||||
{
|
||||
if (next == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(next));
|
||||
}
|
||||
|
||||
if (corsService == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(corsService));
|
||||
}
|
||||
|
||||
if (policy == null)
|
||||
{
|
||||
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))
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await _next(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Cors.Infrastructure;
|
||||
|
||||
namespace Microsoft.AspNetCore.Builder
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IApplicationBuilder"/> extensions for adding CORS middleware support.
|
||||
/// </summary>
|
||||
public static class CorsMiddlewareExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a CORS middleware to your web application pipeline to allow cross domain requests.
|
||||
/// </summary>
|
||||
/// <param name="app">The IApplicationBuilder passed to your Configure method</param>
|
||||
/// <returns>The original app parameter</returns>
|
||||
public static IApplicationBuilder UseCors(this IApplicationBuilder app)
|
||||
{
|
||||
if (app == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(app));
|
||||
}
|
||||
|
||||
return app.UseMiddleware<CorsMiddleware>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a CORS middleware to your web application pipeline to allow cross domain requests.
|
||||
/// </summary>
|
||||
/// <param name="app">The IApplicationBuilder passed to your Configure method</param>
|
||||
/// <param name="policyName">The policy name of a configured policy.</param>
|
||||
/// <returns>The original app parameter</returns>
|
||||
public static IApplicationBuilder UseCors(this IApplicationBuilder app, string policyName)
|
||||
{
|
||||
if (app == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(app));
|
||||
}
|
||||
|
||||
return app.UseMiddleware<CorsMiddleware>(policyName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a CORS middleware to your web application pipeline to allow cross domain requests.
|
||||
/// </summary>
|
||||
/// <param name="app">The IApplicationBuilder passed to your Configure method.</param>
|
||||
/// <param name="configurePolicy">A delegate which can use a policy builder to build a policy.</param>
|
||||
/// <returns>The original app parameter</returns>
|
||||
public static IApplicationBuilder UseCors(
|
||||
this IApplicationBuilder app,
|
||||
Action<CorsPolicyBuilder> configurePolicy)
|
||||
{
|
||||
if (app == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(app));
|
||||
}
|
||||
|
||||
if (configurePolicy == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(configurePolicy));
|
||||
}
|
||||
|
||||
var policyBuilder = new CorsPolicyBuilder();
|
||||
configurePolicy(policyBuilder);
|
||||
return app.UseMiddleware<CorsMiddleware>(policyBuilder.Build());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
// 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.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Cors.Infrastructure
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides programmatic configuration for Cors.
|
||||
/// </summary>
|
||||
public class CorsOptions
|
||||
{
|
||||
private string _defaultPolicyName = "__DefaultCorsPolicy";
|
||||
private IDictionary<string, CorsPolicy> PolicyMap { get; } = new Dictionary<string, CorsPolicy>();
|
||||
|
||||
public string DefaultPolicyName
|
||||
{
|
||||
get
|
||||
{
|
||||
return _defaultPolicyName;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
_defaultPolicyName = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new policy and sets it as the default.
|
||||
/// </summary>
|
||||
/// <param name="policy">The <see cref="CorsPolicy"/> policy to be added.</param>
|
||||
public void AddDefaultPolicy(CorsPolicy policy)
|
||||
{
|
||||
if (policy == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(policy));
|
||||
}
|
||||
|
||||
AddPolicy(DefaultPolicyName, policy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new policy and sets it as the default.
|
||||
/// </summary>
|
||||
/// <param name="configurePolicy">A delegate which can use a policy builder to build a policy.</param>
|
||||
public void AddDefaultPolicy(Action<CorsPolicyBuilder> configurePolicy)
|
||||
{
|
||||
if (configurePolicy == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(configurePolicy));
|
||||
}
|
||||
|
||||
AddPolicy(DefaultPolicyName, configurePolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new policy.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the policy.</param>
|
||||
/// <param name="policy">The <see cref="CorsPolicy"/> policy to be added.</param>
|
||||
public void AddPolicy(string name, CorsPolicy policy)
|
||||
{
|
||||
if (name == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
if (policy == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(policy));
|
||||
}
|
||||
|
||||
PolicyMap[name] = policy;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new policy.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the policy.</param>
|
||||
/// <param name="configurePolicy">A delegate which can use a policy builder to build a policy.</param>
|
||||
public void AddPolicy(string name, Action<CorsPolicyBuilder> configurePolicy)
|
||||
{
|
||||
if (name == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
if (configurePolicy == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(configurePolicy));
|
||||
}
|
||||
|
||||
var policyBuilder = new CorsPolicyBuilder();
|
||||
configurePolicy(policyBuilder);
|
||||
PolicyMap[name] = policyBuilder.Build();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the policy based on the <paramref name="name"/>
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the policy to lookup.</param>
|
||||
/// <returns>The <see cref="CorsPolicy"/> if the policy was added.<c>null</c> otherwise.</returns>
|
||||
public CorsPolicy GetPolicy(string name)
|
||||
{
|
||||
if (name == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
return PolicyMap.ContainsKey(name) ? PolicyMap[name] : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,164 @@
|
|||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.AspNetCore.Cors.Infrastructure
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the policy for Cross-Origin requests based on the CORS specifications.
|
||||
/// </summary>
|
||||
public class CorsPolicy
|
||||
{
|
||||
private TimeSpan? _preflightMaxAge;
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor for a CorsPolicy.
|
||||
/// </summary>
|
||||
public CorsPolicy()
|
||||
{
|
||||
IsOriginAllowed = DefaultIsOriginAllowed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating if all headers are allowed.
|
||||
/// </summary>
|
||||
public bool AllowAnyHeader
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Headers == null || Headers.Count != 1 || Headers.Count == 1 && Headers[0] != "*")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating if all methods are allowed.
|
||||
/// </summary>
|
||||
public bool AllowAnyMethod
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Methods == null || Methods.Count != 1 || Methods.Count == 1 && Methods[0] != "*")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating if all origins are allowed.
|
||||
/// </summary>
|
||||
public bool AllowAnyOrigin
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Origins == null || Origins.Count != 1 || Origins.Count == 1 && Origins[0] != "*")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a function which evaluates whether an origin is allowed.
|
||||
/// </summary>
|
||||
public Func<string, bool> IsOriginAllowed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the headers that the resource might use and can be exposed.
|
||||
/// </summary>
|
||||
public IList<string> ExposedHeaders { get; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the headers that are supported by the resource.
|
||||
/// </summary>
|
||||
public IList<string> Headers { get; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the methods that are supported by the resource.
|
||||
/// </summary>
|
||||
public IList<string> Methods { get; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the origins that are allowed to access the resource.
|
||||
/// </summary>
|
||||
public IList<string> Origins { get; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="TimeSpan"/> for which the results of a preflight request can be cached.
|
||||
/// </summary>
|
||||
public TimeSpan? PreflightMaxAge
|
||||
{
|
||||
get
|
||||
{
|
||||
return _preflightMaxAge;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value < TimeSpan.Zero)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(value), Resources.PreflightMaxAgeOutOfRange);
|
||||
}
|
||||
|
||||
_preflightMaxAge = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the resource supports user credentials in the request.
|
||||
/// </summary>
|
||||
public bool SupportsCredentials { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="System.String" /> that represents this instance.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="System.String" /> that represents this instance.
|
||||
/// </returns>
|
||||
public override string ToString()
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("AllowAnyHeader: ");
|
||||
builder.Append(AllowAnyHeader);
|
||||
builder.Append(", AllowAnyMethod: ");
|
||||
builder.Append(AllowAnyMethod);
|
||||
builder.Append(", AllowAnyOrigin: ");
|
||||
builder.Append(AllowAnyOrigin);
|
||||
builder.Append(", PreflightMaxAge: ");
|
||||
builder.Append(PreflightMaxAge.HasValue ?
|
||||
PreflightMaxAge.Value.TotalSeconds.ToString() : "null");
|
||||
builder.Append(", SupportsCredentials: ");
|
||||
builder.Append(SupportsCredentials);
|
||||
builder.Append(", Origins: {");
|
||||
builder.Append(string.Join(",", Origins));
|
||||
builder.Append("}");
|
||||
builder.Append(", Methods: {");
|
||||
builder.Append(string.Join(",", Methods));
|
||||
builder.Append("}");
|
||||
builder.Append(", Headers: {");
|
||||
builder.Append(string.Join(",", Headers));
|
||||
builder.Append("}");
|
||||
builder.Append(", ExposedHeaders: {");
|
||||
builder.Append(string.Join(",", ExposedHeaders));
|
||||
builder.Append("}");
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private bool DefaultIsOriginAllowed(string origin)
|
||||
{
|
||||
return Origins.Contains(origin, StringComparer.Ordinal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,225 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Cors.Infrastructure
|
||||
{
|
||||
/// <summary>
|
||||
/// Exposes methods to build a policy.
|
||||
/// </summary>
|
||||
public class CorsPolicyBuilder
|
||||
{
|
||||
private readonly CorsPolicy _policy = new CorsPolicy();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="CorsPolicyBuilder"/>.
|
||||
/// </summary>
|
||||
/// <param name="origins">list of origins which can be added.</param>
|
||||
public CorsPolicyBuilder(params string[] origins)
|
||||
{
|
||||
WithOrigins(origins);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="CorsPolicyBuilder"/>.
|
||||
/// </summary>
|
||||
/// <param name="policy">The policy which will be used to intialize the builder.</param>
|
||||
public CorsPolicyBuilder(CorsPolicy policy)
|
||||
{
|
||||
Combine(policy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified <paramref name="origins"/> to the policy.
|
||||
/// </summary>
|
||||
/// <param name="origins">The origins that are allowed.</param>
|
||||
/// <returns>The current policy builder.</returns>
|
||||
public CorsPolicyBuilder WithOrigins(params string[] origins)
|
||||
{
|
||||
foreach (var req in origins)
|
||||
{
|
||||
_policy.Origins.Add(req);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified <paramref name="headers"/> to the policy.
|
||||
/// </summary>
|
||||
/// <param name="headers">The headers which need to be allowed in the request.</param>
|
||||
/// <returns>The current policy builder.</returns>
|
||||
public CorsPolicyBuilder WithHeaders(params string[] headers)
|
||||
{
|
||||
foreach (var req in headers)
|
||||
{
|
||||
_policy.Headers.Add(req);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified <paramref name="exposedHeaders"/> to the policy.
|
||||
/// </summary>
|
||||
/// <param name="exposedHeaders">The headers which need to be exposed to the client.</param>
|
||||
/// <returns>The current policy builder.</returns>
|
||||
public CorsPolicyBuilder WithExposedHeaders(params string[] exposedHeaders)
|
||||
{
|
||||
foreach (var req in exposedHeaders)
|
||||
{
|
||||
_policy.ExposedHeaders.Add(req);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified <paramref name="methods"/> to the policy.
|
||||
/// </summary>
|
||||
/// <param name="methods">The methods which need to be added to the policy.</param>
|
||||
/// <returns>The current policy builder.</returns>
|
||||
public CorsPolicyBuilder WithMethods(params string[] methods)
|
||||
{
|
||||
foreach (var req in methods)
|
||||
{
|
||||
_policy.Methods.Add(req);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the policy to allow credentials.
|
||||
/// </summary>
|
||||
/// <returns>The current policy builder.</returns>
|
||||
public CorsPolicyBuilder AllowCredentials()
|
||||
{
|
||||
_policy.SupportsCredentials = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the policy to not allow credentials.
|
||||
/// </summary>
|
||||
/// <returns>The current policy builder.</returns>
|
||||
public CorsPolicyBuilder DisallowCredentials()
|
||||
{
|
||||
_policy.SupportsCredentials = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the policy allows any origin.
|
||||
/// </summary>
|
||||
/// <returns>The current policy builder.</returns>
|
||||
public CorsPolicyBuilder AllowAnyOrigin()
|
||||
{
|
||||
_policy.Origins.Clear();
|
||||
_policy.Origins.Add(CorsConstants.AnyOrigin);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the policy allows any method.
|
||||
/// </summary>
|
||||
/// <returns>The current policy builder.</returns>
|
||||
public CorsPolicyBuilder AllowAnyMethod()
|
||||
{
|
||||
_policy.Methods.Clear();
|
||||
_policy.Methods.Add("*");
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the policy allows any header.
|
||||
/// </summary>
|
||||
/// <returns>The current policy builder.</returns>
|
||||
public CorsPolicyBuilder AllowAnyHeader()
|
||||
{
|
||||
_policy.Headers.Clear();
|
||||
_policy.Headers.Add("*");
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the preflightMaxAge for the underlying policy.
|
||||
/// </summary>
|
||||
/// <param name="preflightMaxAge">A positive <see cref="TimeSpan"/> indicating the time a preflight
|
||||
/// request can be cached.</param>
|
||||
/// <returns>The current policy builder.</returns>
|
||||
public CorsPolicyBuilder SetPreflightMaxAge(TimeSpan preflightMaxAge)
|
||||
{
|
||||
_policy.PreflightMaxAge = preflightMaxAge;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the specified <paramref name="isOriginAllowed"/> for the underlying policy.
|
||||
/// </summary>
|
||||
/// <param name="isOriginAllowed">The function used by the policy to evaluate if an origin is allowed.</param>
|
||||
/// <returns>The current policy builder.</returns>
|
||||
public CorsPolicyBuilder SetIsOriginAllowed(Func<string, bool> isOriginAllowed)
|
||||
{
|
||||
_policy.IsOriginAllowed = isOriginAllowed;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="CorsPolicy.IsOriginAllowed"/> property of the policy to be a function
|
||||
/// that allows origins to match a configured wildcarded domain when evaluating if the
|
||||
/// origin is allowed.
|
||||
/// </summary>
|
||||
/// <returns>The current policy builder.</returns>
|
||||
public CorsPolicyBuilder SetIsOriginAllowedToAllowWildcardSubdomains()
|
||||
{
|
||||
_policy.IsOriginAllowed = _policy.IsOriginAnAllowedSubdomain;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a new <see cref="CorsPolicy"/> using the entries added.
|
||||
/// </summary>
|
||||
/// <returns>The constructed <see cref="CorsPolicy"/>.</returns>
|
||||
public CorsPolicy Build()
|
||||
{
|
||||
return _policy;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Combines the given <paramref name="policy"/> to the existing properties in the builder.
|
||||
/// </summary>
|
||||
/// <param name="policy">The policy which needs to be combined.</param>
|
||||
/// <returns>The current policy builder.</returns>
|
||||
private CorsPolicyBuilder Combine(CorsPolicy policy)
|
||||
{
|
||||
if (policy == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(policy));
|
||||
}
|
||||
|
||||
WithOrigins(policy.Origins.ToArray());
|
||||
WithHeaders(policy.Headers.ToArray());
|
||||
WithExposedHeaders(policy.ExposedHeaders.ToArray());
|
||||
WithMethods(policy.Methods.ToArray());
|
||||
SetIsOriginAllowed(policy.IsOriginAllowed);
|
||||
|
||||
if (policy.PreflightMaxAge.HasValue)
|
||||
{
|
||||
SetPreflightMaxAge(policy.PreflightMaxAge.Value);
|
||||
}
|
||||
|
||||
if (policy.SupportsCredentials)
|
||||
{
|
||||
AllowCredentials();
|
||||
}
|
||||
else
|
||||
{
|
||||
DisallowCredentials();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Cors.Infrastructure
|
||||
{
|
||||
internal static class CorsPolicyExtensions
|
||||
{
|
||||
private const string _WildcardSubdomain = "*.";
|
||||
|
||||
public static bool IsOriginAnAllowedSubdomain(this CorsPolicy policy, string origin)
|
||||
{
|
||||
if (policy.Origins.Contains(origin))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Uri.TryCreate(origin, UriKind.Absolute, out var originUri))
|
||||
{
|
||||
return policy.Origins
|
||||
.Where(o => o.Contains($"://{_WildcardSubdomain}"))
|
||||
.Select(CreateDomainUri)
|
||||
.Any(domain => UriHelpers.IsSubdomainOf(originUri, domain));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Uri CreateDomainUri(string origin)
|
||||
{
|
||||
return new Uri(origin.Replace(_WildcardSubdomain, string.Empty), UriKind.Absolute);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
// 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.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.AspNetCore.Cors.Infrastructure
|
||||
{
|
||||
/// <summary>
|
||||
/// Results returned by <see cref="ICorsService"/>.
|
||||
/// </summary>
|
||||
public class CorsResult
|
||||
{
|
||||
private TimeSpan? _preflightMaxAge;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the allowed origin.
|
||||
/// </summary>
|
||||
public string AllowedOrigin { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the resource supports user credentials.
|
||||
/// </summary>
|
||||
public bool SupportsCredentials { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the allowed methods.
|
||||
/// </summary>
|
||||
public IList<string> AllowedMethods { get; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the allowed headers.
|
||||
/// </summary>
|
||||
public IList<string> AllowedHeaders { get; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the allowed headers that can be exposed on the response.
|
||||
/// </summary>
|
||||
public IList<string> AllowedExposedHeaders { get; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating if a 'Vary' header with the value 'Origin' is required.
|
||||
/// </summary>
|
||||
public bool VaryByOrigin { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="TimeSpan"/> for which the results of a preflight request can be cached.
|
||||
/// </summary>
|
||||
public TimeSpan? PreflightMaxAge
|
||||
{
|
||||
get
|
||||
{
|
||||
return _preflightMaxAge;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value < TimeSpan.Zero)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(value), Resources.PreflightMaxAgeOutOfRange);
|
||||
}
|
||||
_preflightMaxAge = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="System.String" /> that represents this instance.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="System.String" /> that represents this instance.
|
||||
/// </returns>
|
||||
public override string ToString()
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("AllowCredentials: ");
|
||||
builder.Append(SupportsCredentials);
|
||||
builder.Append(", PreflightMaxAge: ");
|
||||
builder.Append(PreflightMaxAge.HasValue ?
|
||||
PreflightMaxAge.Value.TotalSeconds.ToString() : "null");
|
||||
builder.Append(", AllowOrigin: ");
|
||||
builder.Append(AllowedOrigin);
|
||||
builder.Append(", AllowExposedHeaders: {");
|
||||
builder.Append(string.Join(",", AllowedExposedHeaders));
|
||||
builder.Append("}");
|
||||
builder.Append(", AllowHeaders: {");
|
||||
builder.Append(string.Join(",", AllowedHeaders));
|
||||
builder.Append("}");
|
||||
builder.Append(", AllowMethods: {");
|
||||
builder.Append(string.Join(",", AllowedMethods));
|
||||
builder.Append("}");
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,313 @@
|
|||
// 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.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Cors.Internal;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.Cors.Infrastructure
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of <see cref="ICorsService"/>.
|
||||
/// </summary>
|
||||
public class CorsService : ICorsService
|
||||
{
|
||||
private readonly CorsOptions _options;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="CorsService"/>.
|
||||
/// </summary>
|
||||
/// <param name="options">The option model representing <see cref="CorsOptions"/>.</param>
|
||||
public CorsService(IOptions<CorsOptions> options)
|
||||
: this(options, loggerFactory: null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="CorsService"/>.
|
||||
/// </summary>
|
||||
/// <param name="options">The option model representing <see cref="CorsOptions"/>.</param>
|
||||
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
|
||||
public CorsService(IOptions<CorsOptions> options, ILoggerFactory loggerFactory)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
_options = options.Value;
|
||||
_logger = loggerFactory?.CreateLogger<CorsService>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a policy using the <paramref name="policyName"/> and then evaluates the policy using the passed in
|
||||
/// <paramref name="context"/>.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="policyName"></param>
|
||||
/// <returns>A <see cref="CorsResult"/> which contains the result of policy evaluation and can be
|
||||
/// used by the caller to set appropriate response headers.</returns>
|
||||
public CorsResult EvaluatePolicy(HttpContext context, string policyName)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
var policy = _options.GetPolicy(policyName);
|
||||
return EvaluatePolicy(context, policy);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public CorsResult EvaluatePolicy(HttpContext context, CorsPolicy policy)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (policy == null)
|
||||
{
|
||||
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))
|
||||
{
|
||||
_logger?.IsPreflightRequest();
|
||||
EvaluatePreflightRequest(context, policy, corsResult);
|
||||
}
|
||||
else
|
||||
{
|
||||
EvaluateRequest(context, policy, corsResult);
|
||||
}
|
||||
|
||||
return corsResult;
|
||||
}
|
||||
|
||||
public virtual void EvaluateRequest(HttpContext context, CorsPolicy policy, CorsResult result)
|
||||
{
|
||||
var origin = context.Request.Headers[CorsConstants.Origin];
|
||||
if (!IsOriginAllowed(policy, origin))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AddOriginToResult(origin, policy, result);
|
||||
result.SupportsCredentials = policy.SupportsCredentials;
|
||||
AddHeaderValues(result.AllowedExposedHeaders, policy.ExposedHeaders);
|
||||
_logger?.PolicySuccess();
|
||||
}
|
||||
|
||||
public virtual void EvaluatePreflightRequest(HttpContext context, CorsPolicy policy, CorsResult result)
|
||||
{
|
||||
var origin = context.Request.Headers[CorsConstants.Origin];
|
||||
if (!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();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void ApplyResult(CorsResult result, HttpResponse response)
|
||||
{
|
||||
if (result == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(result));
|
||||
}
|
||||
|
||||
if (response == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(response));
|
||||
}
|
||||
|
||||
var headers = response.Headers;
|
||||
|
||||
if (result.AllowedOrigin != null)
|
||||
{
|
||||
headers[CorsConstants.AccessControlAllowOrigin] = result.AllowedOrigin;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (headerValues == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var current in headerValues)
|
||||
{
|
||||
target.Add(current);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsOriginAllowed(CorsPolicy policy, StringValues origin)
|
||||
{
|
||||
if (StringValues.IsNullOrEmpty(origin))
|
||||
{
|
||||
_logger?.RequestDoesNotHaveOriginHeader();
|
||||
return false;
|
||||
}
|
||||
|
||||
_logger?.RequestHasOriginHeader(origin);
|
||||
if (policy.AllowAnyOrigin || policy.IsOriginAllowed(origin))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
_logger?.PolicyFailure();
|
||||
_logger?.OriginNotAllowed(origin);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
// 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.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Cors.Infrastructure
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class DefaultCorsPolicyProvider : ICorsPolicyProvider
|
||||
{
|
||||
private readonly CorsOptions _options;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="DefaultCorsPolicyProvider"/>.
|
||||
/// </summary>
|
||||
/// <param name="options">The options configured for the application.</param>
|
||||
public DefaultCorsPolicyProvider(IOptions<CorsOptions> options)
|
||||
{
|
||||
_options = options.Value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<CorsPolicy> GetPolicyAsync(HttpContext context, string policyName)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
return Task.FromResult(_options.GetPolicy(policyName ?? _options.DefaultPolicyName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// 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.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Cors.Infrastructure
|
||||
{
|
||||
/// <summary>
|
||||
/// A type which can provide a <see cref="CorsPolicy"/> for a particular <see cref="HttpContext"/>.
|
||||
/// </summary>
|
||||
public interface ICorsPolicyProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a <see cref="CorsPolicy"/> from the given <paramref name="context"/>
|
||||
/// </summary>
|
||||
/// <param name="context">The <see cref="HttpContext"/> associated with this call.</param>
|
||||
/// <param name="policyName">An optional policy name to look for.</param>
|
||||
/// <returns>A <see cref="CorsPolicy"/></returns>
|
||||
Task<CorsPolicy> GetPolicyAsync(HttpContext context, string policyName);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Cors.Infrastructure
|
||||
{
|
||||
/// <summary>
|
||||
/// A type which can evaluate a policy for a particular <see cref="HttpContext"/>.
|
||||
/// </summary>
|
||||
public interface ICorsService
|
||||
{
|
||||
/// <summary>
|
||||
/// Evaluates the given <paramref name="policy"/> using the passed in <paramref name="context"/>.
|
||||
/// </summary>
|
||||
/// <param name="context">The <see cref="HttpContext"/> associated with the call.</param>
|
||||
/// <param name="policy">The <see cref="CorsPolicy"/> which needs to be evaluated.</param>
|
||||
/// <returns>A <see cref="CorsResult"/> which contains the result of policy evaluation and can be
|
||||
/// used by the caller to set appropriate response headers.</returns>
|
||||
CorsResult EvaluatePolicy(HttpContext context, CorsPolicy policy);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Adds CORS-specific response headers to the given <paramref name="response"/>.
|
||||
/// </summary>
|
||||
/// <param name="result">The <see cref="CorsResult"/> used to read the allowed values.</param>
|
||||
/// <param name="response">The <see cref="HttpResponse"/> associated with the current call.</param>
|
||||
void ApplyResult(CorsResult result, HttpResponse response);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Cors.Infrastructure
|
||||
{
|
||||
/// <summary>
|
||||
/// An interface which can be used to identify a type which provides metdata to disable cors for a resource.
|
||||
/// </summary>
|
||||
public interface IDisableCorsAttribute
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Cors.Infrastructure
|
||||
{
|
||||
/// <summary>
|
||||
/// An interface which can be used to identify a type which provides metadata needed for enabling CORS support.
|
||||
/// </summary>
|
||||
public interface IEnableCorsAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the policy which needs to be applied.
|
||||
/// </summary>
|
||||
string PolicyName { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Cors.Infrastructure
|
||||
{
|
||||
internal static class UriHelpers
|
||||
{
|
||||
public static bool IsSubdomainOf(Uri subdomain, Uri domain)
|
||||
{
|
||||
return subdomain.IsAbsoluteUri
|
||||
&& domain.IsAbsoluteUri
|
||||
&& subdomain.Scheme == domain.Scheme
|
||||
&& subdomain.Port == domain.Port
|
||||
&& subdomain.Host.EndsWith($".{domain.Host}", StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Cors.Internal
|
||||
{
|
||||
internal static class CORSLoggerExtensions
|
||||
{
|
||||
private static readonly Action<ILogger, Exception> _isPreflightRequest;
|
||||
private static readonly Action<ILogger, string, Exception> _requestHasOriginHeader;
|
||||
private static readonly Action<ILogger, Exception> _requestDoesNotHaveOriginHeader;
|
||||
private static readonly Action<ILogger, Exception> _policySuccess;
|
||||
private static readonly Action<ILogger, Exception> _policyFailure;
|
||||
private static readonly Action<ILogger, string, Exception> _originNotAllowed;
|
||||
private static readonly Action<ILogger, string, Exception> _accessControlMethodNotAllowed;
|
||||
private static readonly Action<ILogger, string, Exception> _requestHeaderNotAllowed;
|
||||
|
||||
static CORSLoggerExtensions()
|
||||
{
|
||||
_isPreflightRequest = LoggerMessage.Define(
|
||||
LogLevel.Debug,
|
||||
1,
|
||||
"The request is a preflight request.");
|
||||
|
||||
_requestHasOriginHeader = LoggerMessage.Define<string>(
|
||||
LogLevel.Debug,
|
||||
2,
|
||||
"The request has an origin header: '{origin}'.");
|
||||
|
||||
_requestDoesNotHaveOriginHeader = LoggerMessage.Define(
|
||||
LogLevel.Debug,
|
||||
3,
|
||||
"The request does not have an origin header.");
|
||||
|
||||
_policySuccess = LoggerMessage.Define(
|
||||
LogLevel.Information,
|
||||
4,
|
||||
"Policy execution successful.");
|
||||
|
||||
_policyFailure = LoggerMessage.Define(
|
||||
LogLevel.Information,
|
||||
5,
|
||||
"Policy execution failed.");
|
||||
|
||||
_originNotAllowed = LoggerMessage.Define<string>(
|
||||
LogLevel.Information,
|
||||
6,
|
||||
"Request origin {origin} does not have permission to access the resource.");
|
||||
|
||||
_accessControlMethodNotAllowed = LoggerMessage.Define<string>(
|
||||
LogLevel.Information,
|
||||
7,
|
||||
"Request method {accessControlRequestMethod} not allowed in CORS policy.");
|
||||
|
||||
_requestHeaderNotAllowed = LoggerMessage.Define<string>(
|
||||
LogLevel.Information,
|
||||
8,
|
||||
"Request header '{requestHeader}' not allowed in CORS policy.");
|
||||
}
|
||||
|
||||
public static void IsPreflightRequest(this ILogger logger)
|
||||
{
|
||||
_isPreflightRequest(logger, null);
|
||||
}
|
||||
|
||||
public static void RequestHasOriginHeader(this ILogger logger, string origin)
|
||||
{
|
||||
_requestHasOriginHeader(logger, origin, null);
|
||||
}
|
||||
|
||||
public static void RequestDoesNotHaveOriginHeader(this ILogger logger)
|
||||
{
|
||||
_requestDoesNotHaveOriginHeader(logger, null);
|
||||
}
|
||||
|
||||
public static void PolicySuccess(this ILogger logger)
|
||||
{
|
||||
_policySuccess(logger, null);
|
||||
}
|
||||
|
||||
public static void PolicyFailure(this ILogger logger)
|
||||
{
|
||||
_policyFailure(logger, null);
|
||||
}
|
||||
|
||||
public static void OriginNotAllowed(this ILogger logger, string origin)
|
||||
{
|
||||
_originNotAllowed(logger, origin, null);
|
||||
}
|
||||
|
||||
public static void AccessControlMethodNotAllowed(this ILogger logger, string accessControlMethod)
|
||||
{
|
||||
_accessControlMethodNotAllowed(logger, accessControlMethod, null);
|
||||
}
|
||||
|
||||
public static void RequestHeaderNotAllowed(this ILogger logger, string requestHeader)
|
||||
{
|
||||
_requestHeaderNotAllowed(logger, requestHeader, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>CORS middleware and policy for ASP.NET Core to enable cross-origin resource sharing.
|
||||
Commonly used types:
|
||||
Microsoft.AspNetCore.Cors.DisableCorsAttribute
|
||||
Microsoft.AspNetCore.Cors.EnableCorsAttribute</Description>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<PackageTags>aspnetcore;cors</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="$(MicrosoftAspNetCoreHttpExtensionsPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="$(MicrosoftExtensionsConfigurationAbstractionsPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="$(MicrosoftExtensionsDependencyInjectionAbstractionsPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="$(MicrosoftExtensionsLoggingAbstractionsPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="$(MicrosoftExtensionsOptionsPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
// 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.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Cors.Test,PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.AspNetCore.Cors {
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.AspNetCore.Cors.Resources", typeof(Resources).GetTypeInfo().Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to PreflightMaxAge must be greater than or equal to 0..
|
||||
/// </summary>
|
||||
internal static string PreflightMaxAgeOutOfRange {
|
||||
get {
|
||||
return ResourceManager.GetString("PreflightMaxAgeOutOfRange", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="PreflightMaxAgeOutOfRange" xml:space="preserve">
|
||||
<value>PreflightMaxAge must be greater than or equal to 0.</value>
|
||||
</data>
|
||||
</root>
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,15 @@
|
|||
<Project>
|
||||
<Import Project="..\Directory.Build.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<DeveloperBuildTestTfms>netcoreapp2.1</DeveloperBuildTestTfms>
|
||||
<StandardTestTfms>$(DeveloperBuildTestTfms)</StandardTestTfms>
|
||||
<StandardTestTfms Condition=" '$(DeveloperBuild)' != 'true' ">$(StandardTestTfms);netcoreapp2.0</StandardTestTfms>
|
||||
<StandardTestTfms Condition=" '$(DeveloperBuild)' != 'true' AND '$(OS)' == 'Windows_NT' ">$(StandardTestTfms);net461</StandardTestTfms>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Internal.AspNetCore.Sdk" PrivateAssets="All" Version="$(InternalAspNetCoreSdkPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,353 @@
|
|||
// 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.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Cors.Infrastructure
|
||||
{
|
||||
public class CorsMiddlewareTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("PuT")]
|
||||
[InlineData("PUT")]
|
||||
public async Task CorsRequest_MatchesPolicy_OnCaseInsensitiveAccessControlRequestMethod(string accessControlRequestMethod)
|
||||
{
|
||||
// Arrange
|
||||
var hostBuilder = new WebHostBuilder()
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UseCors(builder =>
|
||||
builder.WithOrigins("http://localhost:5001")
|
||||
.WithMethods("PUT"));
|
||||
app.Run(async context =>
|
||||
{
|
||||
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, "http://localhost:5001")
|
||||
.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());
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CorsRequest_MatchPolicy_SetsResponseHeaders()
|
||||
{
|
||||
// Arrange
|
||||
var hostBuilder = new WebHostBuilder()
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UseCors(builder =>
|
||||
builder.WithOrigins("http://localhost:5001")
|
||||
.WithMethods("PUT")
|
||||
.WithHeaders("Header1")
|
||||
.WithExposedHeaders("AllowedHeader"));
|
||||
app.Run(async context =>
|
||||
{
|
||||
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, "http://localhost:5001")
|
||||
.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("AllowedHeader", response.Headers.GetValues(CorsConstants.AccessControlExposeHeaders).FirstOrDefault());
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("OpTions")]
|
||||
[InlineData("OPTIONS")]
|
||||
public async Task PreFlight_MatchesPolicy_OnCaseInsensitiveOptionsMethod(string preflightMethod)
|
||||
{
|
||||
// Arrange
|
||||
var policy = new CorsPolicy();
|
||||
policy.Origins.Add("http://localhost:5001");
|
||||
policy.Methods.Add("PUT");
|
||||
|
||||
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, "http://localhost:5001")
|
||||
.SendAsync(preflightMethod);
|
||||
|
||||
// Assert
|
||||
response.EnsureSuccessStatusCode();
|
||||
Assert.Single(response.Headers);
|
||||
Assert.Equal("http://localhost:5001", response.Headers.GetValues(CorsConstants.AccessControlAllowOrigin).FirstOrDefault());
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PreFlight_MatchesPolicy_SetsResponseHeaders()
|
||||
{
|
||||
// Arrange
|
||||
var policy = new CorsPolicy();
|
||||
policy.Origins.Add("http://localhost:5001");
|
||||
policy.Methods.Add("PUT");
|
||||
policy.Headers.Add("Header1");
|
||||
policy.ExposedHeaders.Add("AllowedHeader");
|
||||
|
||||
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, "http://localhost:5001")
|
||||
.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());
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PreFlightRequest_DoesNotMatchPolicy_DoesNotSetHeaders()
|
||||
{
|
||||
// Arrange
|
||||
var hostBuilder = new WebHostBuilder()
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UseCors(builder =>
|
||||
builder.WithOrigins("http://localhost:5001")
|
||||
.WithMethods("PUT")
|
||||
.WithHeaders("Header1")
|
||||
.WithExposedHeaders("AllowedHeader"));
|
||||
app.Run(async context =>
|
||||
{
|
||||
await context.Response.WriteAsync("Cross origin response");
|
||||
});
|
||||
})
|
||||
.ConfigureServices(services => services.AddCors());
|
||||
|
||||
using (var server = new TestServer(hostBuilder))
|
||||
{
|
||||
// Act
|
||||
// Preflight request.
|
||||
var response = await server.CreateRequest("/")
|
||||
.AddHeader(CorsConstants.Origin, "http://localhost:5002")
|
||||
.AddHeader(CorsConstants.AccessControlRequestMethod, "PUT")
|
||||
.SendAsync(CorsConstants.PreflightHttpMethod);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
|
||||
Assert.Empty(response.Headers);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CorsRequest_DoesNotMatchPolicy_DoesNotSetHeaders()
|
||||
{
|
||||
// Arrange
|
||||
var hostBuilder = new WebHostBuilder()
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UseCors(builder =>
|
||||
builder.WithOrigins("http://localhost:5001")
|
||||
.WithMethods("PUT")
|
||||
.WithHeaders("Header1")
|
||||
.WithExposedHeaders("AllowedHeader"));
|
||||
app.Run(async context =>
|
||||
{
|
||||
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, "http://localhost:5002")
|
||||
.SendAsync("PUT");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Empty(response.Headers);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Uses_PolicyProvider_AsFallback()
|
||||
{
|
||||
// Arrange
|
||||
var corsService = Mock.Of<ICorsService>();
|
||||
var mockProvider = new Mock<ICorsPolicyProvider>();
|
||||
mockProvider.Setup(o => o.GetPolicyAsync(It.IsAny<HttpContext>(), It.IsAny<string>()))
|
||||
.Returns(Task.FromResult<CorsPolicy>(null))
|
||||
.Verifiable();
|
||||
|
||||
var middleware = new CorsMiddleware(
|
||||
Mock.Of<RequestDelegate>(),
|
||||
corsService,
|
||||
mockProvider.Object,
|
||||
policyName: null);
|
||||
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.Request.Headers.Add(CorsConstants.Origin, new[] { "http://example.com" });
|
||||
|
||||
// Act
|
||||
await middleware.Invoke(httpContext);
|
||||
|
||||
// Assert
|
||||
mockProvider.Verify(
|
||||
o => o.GetPolicyAsync(It.IsAny<HttpContext>(), It.IsAny<string>()),
|
||||
Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DoesNotSetHeaders_ForNoPolicy()
|
||||
{
|
||||
// Arrange
|
||||
var corsService = Mock.Of<ICorsService>();
|
||||
var mockProvider = new Mock<ICorsPolicyProvider>();
|
||||
mockProvider.Setup(o => o.GetPolicyAsync(It.IsAny<HttpContext>(), It.IsAny<string>()))
|
||||
.Returns(Task.FromResult<CorsPolicy>(null))
|
||||
.Verifiable();
|
||||
|
||||
var middleware = new CorsMiddleware(
|
||||
Mock.Of<RequestDelegate>(),
|
||||
corsService,
|
||||
mockProvider.Object,
|
||||
policyName: null);
|
||||
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.Request.Headers.Add(CorsConstants.Origin, new[] { "http://example.com" });
|
||||
|
||||
// Act
|
||||
await middleware.Invoke(httpContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(200, httpContext.Response.StatusCode);
|
||||
Assert.Empty(httpContext.Response.Headers);
|
||||
mockProvider.Verify(
|
||||
o => o.GetPolicyAsync(It.IsAny<HttpContext>(), It.IsAny<string>()),
|
||||
Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PreFlight_MatchesDefaultPolicy_SetsResponseHeaders()
|
||||
{
|
||||
// Arrange
|
||||
var hostBuilder = new WebHostBuilder()
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UseCors();
|
||||
app.Run(async context =>
|
||||
{
|
||||
await context.Response.WriteAsync("Cross origin response");
|
||||
});
|
||||
})
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddCors(options =>
|
||||
{
|
||||
options.AddDefaultPolicy(policyBuilder =>
|
||||
{
|
||||
policyBuilder
|
||||
.WithOrigins("http://localhost:5001")
|
||||
.WithMethods("PUT")
|
||||
.WithHeaders("Header1")
|
||||
.WithExposedHeaders("AllowedHeader")
|
||||
.Build();
|
||||
});
|
||||
options.AddPolicy("policy2", policyBuilder =>
|
||||
{
|
||||
policyBuilder
|
||||
.WithOrigins("http://localhost:5002")
|
||||
.Build();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
using (var server = new TestServer(hostBuilder))
|
||||
{
|
||||
// Act
|
||||
// Preflight request.
|
||||
var response = await server.CreateRequest("/")
|
||||
.AddHeader(CorsConstants.Origin, "http://localhost:5001")
|
||||
.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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
// 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 Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Cors.Infrastructure
|
||||
{
|
||||
public class CorsOptionsTest
|
||||
{
|
||||
[Fact]
|
||||
public void AddDefaultPolicy_SetsDefaultPolicyName()
|
||||
{
|
||||
// Arrange
|
||||
var corsOptions = new CorsOptions();
|
||||
var expectedPolicy = new CorsPolicy();
|
||||
|
||||
// Act
|
||||
corsOptions.AddPolicy("policy1", new CorsPolicy());
|
||||
corsOptions.AddDefaultPolicy(expectedPolicy);
|
||||
corsOptions.AddPolicy("policy3", new CorsPolicy());
|
||||
|
||||
// Assert
|
||||
var actualPolicy = corsOptions.GetPolicy(corsOptions.DefaultPolicyName);
|
||||
Assert.Same(expectedPolicy, actualPolicy);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddDefaultPolicy_OverridesDefaultPolicyName()
|
||||
{
|
||||
// Arrange
|
||||
var corsOptions = new CorsOptions();
|
||||
var expectedPolicy = new CorsPolicy();
|
||||
|
||||
// Act
|
||||
corsOptions.AddDefaultPolicy(new CorsPolicy());
|
||||
corsOptions.AddDefaultPolicy(expectedPolicy);
|
||||
|
||||
// Assert
|
||||
var actualPolicy = corsOptions.GetPolicy(corsOptions.DefaultPolicyName);
|
||||
Assert.Same(expectedPolicy, actualPolicy);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddDefaultPolicy_UsingPolicyBuilder_SetsDefaultPolicyName()
|
||||
{
|
||||
// Arrange
|
||||
var corsOptions = new CorsOptions();
|
||||
CorsPolicy expectedPolicy = null;
|
||||
|
||||
// Act
|
||||
corsOptions.AddPolicy("policy1", policyBuilder =>
|
||||
{
|
||||
policyBuilder.AllowAnyOrigin().Build();
|
||||
});
|
||||
corsOptions.AddDefaultPolicy(policyBuilder =>
|
||||
{
|
||||
expectedPolicy = policyBuilder.AllowAnyOrigin().Build();
|
||||
});
|
||||
corsOptions.AddPolicy("policy3", new CorsPolicy());
|
||||
|
||||
// Assert
|
||||
var actualPolicy = corsOptions.GetPolicy(corsOptions.DefaultPolicyName);
|
||||
Assert.Same(expectedPolicy, actualPolicy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,292 @@
|
|||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Cors.Infrastructure
|
||||
{
|
||||
public class CorsPolicyBuilderTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_WithPolicy_AddsTheGivenPolicy()
|
||||
{
|
||||
// Arrange
|
||||
Func<string, bool> isOriginAllowed = origin => true;
|
||||
var originalPolicy = new CorsPolicy();
|
||||
originalPolicy.Origins.Add("http://existing.com");
|
||||
originalPolicy.Headers.Add("Existing");
|
||||
originalPolicy.Methods.Add("GET");
|
||||
originalPolicy.ExposedHeaders.Add("ExistingExposed");
|
||||
originalPolicy.SupportsCredentials = true;
|
||||
originalPolicy.PreflightMaxAge = TimeSpan.FromSeconds(12);
|
||||
originalPolicy.IsOriginAllowed = isOriginAllowed;
|
||||
|
||||
// Act
|
||||
var builder = new CorsPolicyBuilder(originalPolicy);
|
||||
|
||||
// Assert
|
||||
var corsPolicy = builder.Build();
|
||||
|
||||
Assert.False(corsPolicy.AllowAnyHeader);
|
||||
Assert.False(corsPolicy.AllowAnyMethod);
|
||||
Assert.False(corsPolicy.AllowAnyOrigin);
|
||||
Assert.True(corsPolicy.SupportsCredentials);
|
||||
Assert.NotSame(originalPolicy.Headers, corsPolicy.Headers);
|
||||
Assert.Equal(originalPolicy.Headers, corsPolicy.Headers);
|
||||
Assert.NotSame(originalPolicy.Methods, corsPolicy.Methods);
|
||||
Assert.Equal(originalPolicy.Methods, corsPolicy.Methods);
|
||||
Assert.NotSame(originalPolicy.Origins, corsPolicy.Origins);
|
||||
Assert.Equal(originalPolicy.Origins, corsPolicy.Origins);
|
||||
Assert.NotSame(originalPolicy.ExposedHeaders, corsPolicy.ExposedHeaders);
|
||||
Assert.Equal(originalPolicy.ExposedHeaders, corsPolicy.ExposedHeaders);
|
||||
Assert.Equal(TimeSpan.FromSeconds(12), corsPolicy.PreflightMaxAge);
|
||||
Assert.Same(originalPolicy.IsOriginAllowed, corsPolicy.IsOriginAllowed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConstructorWithPolicy_HavingNullPreflightMaxAge_AddsTheGivenPolicy()
|
||||
{
|
||||
// Arrange
|
||||
var originalPolicy = new CorsPolicy();
|
||||
originalPolicy.Origins.Add("http://existing.com");
|
||||
|
||||
// Act
|
||||
var builder = new CorsPolicyBuilder(originalPolicy);
|
||||
|
||||
// Assert
|
||||
var corsPolicy = builder.Build();
|
||||
|
||||
Assert.Null(corsPolicy.PreflightMaxAge);
|
||||
Assert.False(corsPolicy.AllowAnyHeader);
|
||||
Assert.False(corsPolicy.AllowAnyMethod);
|
||||
Assert.False(corsPolicy.AllowAnyOrigin);
|
||||
Assert.NotSame(originalPolicy.Origins, corsPolicy.Origins);
|
||||
Assert.Equal(originalPolicy.Origins, corsPolicy.Origins);
|
||||
Assert.Empty(corsPolicy.Headers);
|
||||
Assert.Empty(corsPolicy.Methods);
|
||||
Assert.Empty(corsPolicy.ExposedHeaders);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithNoOrigin()
|
||||
{
|
||||
// Arrange & Act
|
||||
var builder = new CorsPolicyBuilder();
|
||||
|
||||
// Assert
|
||||
var corsPolicy = builder.Build();
|
||||
Assert.False(corsPolicy.AllowAnyHeader);
|
||||
Assert.False(corsPolicy.AllowAnyMethod);
|
||||
Assert.False(corsPolicy.AllowAnyOrigin);
|
||||
Assert.False(corsPolicy.SupportsCredentials);
|
||||
Assert.Empty(corsPolicy.ExposedHeaders);
|
||||
Assert.Empty(corsPolicy.Headers);
|
||||
Assert.Empty(corsPolicy.Methods);
|
||||
Assert.Empty(corsPolicy.Origins);
|
||||
Assert.Null(corsPolicy.PreflightMaxAge);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData("http://example.com,http://example2.com")]
|
||||
public void Constructor_WithParamsOrigin_InitializesOrigin(string origin)
|
||||
{
|
||||
// Arrange
|
||||
var origins = origin.Split(',');
|
||||
|
||||
// Act
|
||||
var builder = new CorsPolicyBuilder(origins);
|
||||
|
||||
// Assert
|
||||
var corsPolicy = builder.Build();
|
||||
Assert.False(corsPolicy.AllowAnyHeader);
|
||||
Assert.False(corsPolicy.AllowAnyMethod);
|
||||
Assert.False(corsPolicy.AllowAnyOrigin);
|
||||
Assert.False(corsPolicy.SupportsCredentials);
|
||||
Assert.Empty(corsPolicy.ExposedHeaders);
|
||||
Assert.Empty(corsPolicy.Headers);
|
||||
Assert.Empty(corsPolicy.Methods);
|
||||
Assert.Equal(origins.ToList(), corsPolicy.Origins);
|
||||
Assert.Null(corsPolicy.PreflightMaxAge);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WithOrigins_AddsOrigins()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new CorsPolicyBuilder();
|
||||
|
||||
// Act
|
||||
builder.WithOrigins("http://example.com", "http://example2.com");
|
||||
|
||||
// Assert
|
||||
var corsPolicy = builder.Build();
|
||||
Assert.False(corsPolicy.AllowAnyOrigin);
|
||||
Assert.Equal(new List<string>() { "http://example.com", "http://example2.com" }, corsPolicy.Origins);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllowAnyOrigin_AllowsAny()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new CorsPolicyBuilder();
|
||||
|
||||
// Act
|
||||
builder.AllowAnyOrigin();
|
||||
|
||||
// Assert
|
||||
var corsPolicy = builder.Build();
|
||||
Assert.True(corsPolicy.AllowAnyOrigin);
|
||||
Assert.Equal(new List<string>() { "*" }, corsPolicy.Origins);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetIsOriginAllowed_AddsIsOriginAllowed()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new CorsPolicyBuilder();
|
||||
Func<string, bool> isOriginAllowed = origin => true;
|
||||
|
||||
// Act
|
||||
builder.SetIsOriginAllowed(isOriginAllowed);
|
||||
|
||||
// Assert
|
||||
var corsPolicy = builder.Build();
|
||||
Assert.Same(corsPolicy.IsOriginAllowed, isOriginAllowed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetIsOriginAllowedToAllowWildcardSubdomains_AllowsWildcardSubdomains()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new CorsPolicyBuilder("http://*.example.com");
|
||||
|
||||
// Act
|
||||
builder.SetIsOriginAllowedToAllowWildcardSubdomains();
|
||||
|
||||
// Assert
|
||||
var corsPolicy = builder.Build();
|
||||
Assert.True(corsPolicy.IsOriginAllowed("http://test.example.com"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WithMethods_AddsMethods()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new CorsPolicyBuilder();
|
||||
|
||||
// Act
|
||||
builder.WithMethods("PUT", "GET");
|
||||
|
||||
// Assert
|
||||
var corsPolicy = builder.Build();
|
||||
Assert.False(corsPolicy.AllowAnyOrigin);
|
||||
Assert.Equal(new List<string>() { "PUT", "GET" }, corsPolicy.Methods);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllowAnyMethod_AllowsAny()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new CorsPolicyBuilder();
|
||||
|
||||
// Act
|
||||
builder.AllowAnyMethod();
|
||||
|
||||
// Assert
|
||||
var corsPolicy = builder.Build();
|
||||
Assert.True(corsPolicy.AllowAnyMethod);
|
||||
Assert.Equal(new List<string>() { "*" }, corsPolicy.Methods);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WithHeaders_AddsHeaders()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new CorsPolicyBuilder();
|
||||
|
||||
// Act
|
||||
builder.WithHeaders("example1", "example2");
|
||||
|
||||
// Assert
|
||||
var corsPolicy = builder.Build();
|
||||
Assert.False(corsPolicy.AllowAnyHeader);
|
||||
Assert.Equal(new List<string>() { "example1", "example2" }, corsPolicy.Headers);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllowAnyHeaders_AllowsAny()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new CorsPolicyBuilder();
|
||||
|
||||
// Act
|
||||
builder.AllowAnyHeader();
|
||||
|
||||
// Assert
|
||||
var corsPolicy = builder.Build();
|
||||
Assert.True(corsPolicy.AllowAnyHeader);
|
||||
Assert.Equal(new List<string>() { "*" }, corsPolicy.Headers);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WithExposedHeaders_AddsExposedHeaders()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new CorsPolicyBuilder();
|
||||
|
||||
// Act
|
||||
builder.WithExposedHeaders("exposed1", "exposed2");
|
||||
|
||||
// Assert
|
||||
var corsPolicy = builder.Build();
|
||||
Assert.Equal(new List<string>() { "exposed1", "exposed2" }, corsPolicy.ExposedHeaders);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetPreFlightMaxAge_SetsThePreFlightAge()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new CorsPolicyBuilder();
|
||||
|
||||
// Act
|
||||
builder.SetPreflightMaxAge(TimeSpan.FromSeconds(12));
|
||||
|
||||
// Assert
|
||||
var corsPolicy = builder.Build();
|
||||
Assert.Equal(TimeSpan.FromSeconds(12), corsPolicy.PreflightMaxAge);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllowCredential_SetsSupportsCredentials_ToTrue()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new CorsPolicyBuilder();
|
||||
|
||||
// Act
|
||||
builder.AllowCredentials();
|
||||
|
||||
// Assert
|
||||
var corsPolicy = builder.Build();
|
||||
Assert.True(corsPolicy.SupportsCredentials);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void DisallowCredential_SetsSupportsCredentials_ToFalse()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new CorsPolicyBuilder();
|
||||
|
||||
// Act
|
||||
builder.DisallowCredentials();
|
||||
|
||||
// Assert
|
||||
var corsPolicy = builder.Build();
|
||||
Assert.False(corsPolicy.SupportsCredentials);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
// 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 Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Cors.Infrastructure
|
||||
{
|
||||
public sealed class CorsPolicyExtensionsTest
|
||||
{
|
||||
[Fact]
|
||||
public void IsOriginAnAllowedSubdomain_ReturnsTrueIfPolicyContainsOrigin()
|
||||
{
|
||||
// Arrange
|
||||
const string origin = "http://sub.domain";
|
||||
var policy = new CorsPolicy();
|
||||
policy.Origins.Add(origin);
|
||||
|
||||
// Act
|
||||
var actual = policy.IsOriginAnAllowedSubdomain(origin);
|
||||
|
||||
// Assert
|
||||
Assert.True(actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("null")]
|
||||
[InlineData("http://")]
|
||||
[InlineData("http://*")]
|
||||
[InlineData("http://.domain")]
|
||||
[InlineData("http://.domain/hello")]
|
||||
public void IsOriginAnAllowedSubdomain_ReturnsFalseIfOriginIsMalformedUri(string malformedOrigin)
|
||||
{
|
||||
// Arrange
|
||||
var policy = new CorsPolicy();
|
||||
policy.Origins.Add("http://*.domain");
|
||||
|
||||
// Act
|
||||
var actual = policy.IsOriginAnAllowedSubdomain(malformedOrigin);
|
||||
|
||||
// Assert
|
||||
Assert.False(actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("http://sub.domain", "http://*.domain")]
|
||||
[InlineData("http://sub.sub.domain", "http://*.domain")]
|
||||
[InlineData("http://sub.sub.domain", "http://*.sub.domain")]
|
||||
[InlineData("http://sub.domain:4567", "http://*.domain:4567")]
|
||||
public void IsOriginAnAllowedSubdomain_ReturnsTrue_WhenASubdomain(string origin, string allowedOrigin)
|
||||
{
|
||||
// Arrange
|
||||
var policy = new CorsPolicy();
|
||||
policy.Origins.Add(allowedOrigin);
|
||||
|
||||
// Act
|
||||
var isAllowed = policy.IsOriginAnAllowedSubdomain(origin);
|
||||
|
||||
// Assert
|
||||
Assert.True(isAllowed);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("http://domain", "http://*.domain")]
|
||||
[InlineData("http://sub.domain", "http://domain")]
|
||||
[InlineData("http://sub.domain:1234", "http://*.domain:5678")]
|
||||
[InlineData("http://sub.domain", "http://domain.*")]
|
||||
[InlineData("http://sub.sub.domain", "http://sub.*.domain")]
|
||||
[InlineData("http://sub.domain.hacker", "http://*.domain")]
|
||||
[InlineData("https://sub.domain", "http://*.domain")]
|
||||
public void IsOriginAnAllowedSubdomain_ReturnsFalse_WhenNotASubdomain(string origin, string allowedOrigin)
|
||||
{
|
||||
// Arrange
|
||||
var policy = new CorsPolicy();
|
||||
policy.Origins.Add(allowedOrigin);
|
||||
|
||||
// Act
|
||||
var isAllowed = policy.IsOriginAnAllowedSubdomain(origin);
|
||||
|
||||
// Assert
|
||||
Assert.False(isAllowed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
// 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 Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Cors.Infrastructure
|
||||
{
|
||||
public class CorsPolicyTest
|
||||
{
|
||||
[Fact]
|
||||
public void Default_Constructor()
|
||||
{
|
||||
// Arrange & Act
|
||||
var corsPolicy = new CorsPolicy();
|
||||
|
||||
// Assert
|
||||
Assert.False(corsPolicy.AllowAnyHeader);
|
||||
Assert.False(corsPolicy.AllowAnyMethod);
|
||||
Assert.False(corsPolicy.AllowAnyOrigin);
|
||||
Assert.False(corsPolicy.SupportsCredentials);
|
||||
Assert.Empty(corsPolicy.ExposedHeaders);
|
||||
Assert.Empty(corsPolicy.Headers);
|
||||
Assert.Empty(corsPolicy.Methods);
|
||||
Assert.Empty(corsPolicy.Origins);
|
||||
Assert.Null(corsPolicy.PreflightMaxAge);
|
||||
Assert.NotNull(corsPolicy.IsOriginAllowed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SettingNegativePreflightMaxAge_Throws()
|
||||
{
|
||||
// Arrange
|
||||
var policy = new CorsPolicy();
|
||||
|
||||
// Act
|
||||
var exception = Assert.Throws<ArgumentOutOfRangeException>(() =>
|
||||
{
|
||||
policy.PreflightMaxAge = TimeSpan.FromSeconds(-12);
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
$"PreflightMaxAge must be greater than or equal to 0.{Environment.NewLine}Parameter name: value",
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToString_ReturnsThePropertyValues()
|
||||
{
|
||||
// Arrange
|
||||
var corsPolicy = new CorsPolicy
|
||||
{
|
||||
PreflightMaxAge = TimeSpan.FromSeconds(12),
|
||||
SupportsCredentials = true
|
||||
};
|
||||
corsPolicy.Headers.Add("foo");
|
||||
corsPolicy.Headers.Add("bar");
|
||||
corsPolicy.Origins.Add("http://example.com");
|
||||
corsPolicy.Origins.Add("http://example.org");
|
||||
corsPolicy.Methods.Add("GET");
|
||||
|
||||
// Act
|
||||
var policyString = corsPolicy.ToString();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
@"AllowAnyHeader: False, AllowAnyMethod: False, AllowAnyOrigin: False, PreflightMaxAge: 12,"+
|
||||
" SupportsCredentials: True, Origins: {http://example.com,http://example.org}, Methods: {GET},"+
|
||||
" Headers: {foo,bar}, ExposedHeaders: {}",
|
||||
policyString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
// 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 Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Cors.Infrastructure
|
||||
{
|
||||
public class CorsResultTest
|
||||
{
|
||||
[Fact]
|
||||
public void Default_Constructor()
|
||||
{
|
||||
// Arrange & Act
|
||||
var result = new CorsResult();
|
||||
|
||||
// Assert
|
||||
Assert.Empty(result.AllowedHeaders);
|
||||
Assert.Empty(result.AllowedExposedHeaders);
|
||||
Assert.Empty(result.AllowedMethods);
|
||||
Assert.False(result.SupportsCredentials);
|
||||
Assert.Null(result.AllowedOrigin);
|
||||
Assert.Null(result.PreflightMaxAge);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SettingNegativePreflightMaxAge_Throws()
|
||||
{
|
||||
// Arrange
|
||||
var result = new CorsResult();
|
||||
|
||||
// Act
|
||||
var exception = Assert.Throws<ArgumentOutOfRangeException>(() =>
|
||||
{
|
||||
result.PreflightMaxAge = TimeSpan.FromSeconds(-1);
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
$"PreflightMaxAge must be greater than or equal to 0.{Environment.NewLine}Parameter name: value",
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToString_ReturnsThePropertyValues()
|
||||
{
|
||||
// Arrange
|
||||
var corsResult = new CorsResult
|
||||
{
|
||||
SupportsCredentials = true,
|
||||
PreflightMaxAge = TimeSpan.FromSeconds(30),
|
||||
AllowedOrigin = "*"
|
||||
};
|
||||
corsResult.AllowedExposedHeaders.Add("foo");
|
||||
corsResult.AllowedHeaders.Add("bar");
|
||||
corsResult.AllowedHeaders.Add("baz");
|
||||
corsResult.AllowedMethods.Add("GET");
|
||||
|
||||
// Act
|
||||
var result = corsResult.ToString();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
@"AllowCredentials: True, PreflightMaxAge: 30, AllowOrigin: *," +
|
||||
" AllowExposedHeaders: {foo}, AllowHeaders: {bar,baz}, AllowMethods: {GET}",
|
||||
result);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -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;
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
// 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.Http;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Cors.Infrastructure
|
||||
{
|
||||
public class DefaultPolicyProviderTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task UsesTheDefaultPolicyName()
|
||||
{
|
||||
// Arrange
|
||||
var options = new CorsOptions();
|
||||
var policy = new CorsPolicy();
|
||||
options.AddPolicy(options.DefaultPolicyName, policy);
|
||||
|
||||
var corsOptions = new TestCorsOptions
|
||||
{
|
||||
Value = options
|
||||
};
|
||||
var policyProvider = new DefaultCorsPolicyProvider(corsOptions);
|
||||
|
||||
// Act
|
||||
var actualPolicy = await policyProvider.GetPolicyAsync(new DefaultHttpContext(), policyName: null);
|
||||
|
||||
// Assert
|
||||
Assert.Same(policy, actualPolicy);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData("policyName")]
|
||||
public async Task GetsNamedPolicy(string policyName)
|
||||
{
|
||||
// Arrange
|
||||
var options = new CorsOptions();
|
||||
var policy = new CorsPolicy();
|
||||
options.AddPolicy(policyName, policy);
|
||||
|
||||
var corsOptions = new TestCorsOptions
|
||||
{
|
||||
Value = options
|
||||
};
|
||||
var policyProvider = new DefaultCorsPolicyProvider(corsOptions);
|
||||
|
||||
// Act
|
||||
var actualPolicy = await policyProvider.GetPolicyAsync(new DefaultHttpContext(), policyName);
|
||||
|
||||
// Assert
|
||||
Assert.Same(policy, actualPolicy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\WebSites\CorsMiddlewareWebSite\CorsMiddlewareWebSite.csproj" />
|
||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Cors\Microsoft.AspNetCore.Cors.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="$(MicrosoftAspNetCoreTestHostPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Testing" Version="$(MicrosoftExtensionsLoggingTestingPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPackageVersion)" />
|
||||
<PackageReference Include="Moq" Version="$(MoqPackageVersion)" />
|
||||
<PackageReference Include="xunit.analyzers" Version="$(XunitAnalyzersPackageVersion)" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitRunnerVisualstudioPackageVersion)" />
|
||||
<PackageReference Include="xunit" Version="$(XunitPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// 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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
// 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.Collections.Generic;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Cors.Infrastructure
|
||||
{
|
||||
public sealed class UriHelpersTests
|
||||
{
|
||||
[Theory]
|
||||
[MemberData(nameof(IsSubdomainOfTestData))]
|
||||
public void TestIsSubdomainOf(Uri subdomain, Uri domain)
|
||||
{
|
||||
// Act
|
||||
bool isSubdomain = UriHelpers.IsSubdomainOf(subdomain, domain);
|
||||
|
||||
// Assert
|
||||
Assert.True(isSubdomain);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(IsNotSubdomainOfTestData))]
|
||||
public void TestIsSubdomainOf_ReturnsFalse_WhenNotSubdomain(Uri subdomain, Uri domain)
|
||||
{
|
||||
// Act
|
||||
bool isSubdomain = UriHelpers.IsSubdomainOf(subdomain, domain);
|
||||
|
||||
// Assert
|
||||
Assert.False(isSubdomain);
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> IsSubdomainOfTestData
|
||||
{
|
||||
get
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new object[] {new Uri("http://sub.domain"), new Uri("http://domain")},
|
||||
new object[] {new Uri("https://sub.domain"), new Uri("https://domain")},
|
||||
new object[] {new Uri("https://sub.domain:5678"), new Uri("https://domain:5678")},
|
||||
new object[] {new Uri("http://sub.sub.domain"), new Uri("http://domain")},
|
||||
new object[] {new Uri("http://sub.sub.domain"), new Uri("http://sub.domain")}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> IsNotSubdomainOfTestData
|
||||
{
|
||||
get
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new object[] {new Uri("http://subdomain"), new Uri("http://domain")},
|
||||
new object[] {new Uri("https://sub.domain"), new Uri("http://domain")},
|
||||
new object[] {new Uri("https://sub.domain:1234"), new Uri("https://domain:5678")},
|
||||
new object[] {new Uri("http://domain.tld"), new Uri("http://domain")},
|
||||
new object[] {new Uri("http://sub.domain.tld"), new Uri("http://domain")},
|
||||
new object[] {new Uri("/relativeUri", UriKind.Relative), new Uri("http://domain")},
|
||||
new object[] {new Uri("http://sub.domain"), new Uri("/relative", UriKind.Relative)}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.Cors\Microsoft.AspNetCore.Cors.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="$(MicrosoftAspNetCoreServerIISIntegrationPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(MicrosoftAspNetCoreServerKestrelPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
// 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.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace CorsMiddlewareWebSite
|
||||
{
|
||||
public class EchoMiddleware
|
||||
{
|
||||
/// <summary>
|
||||
/// Instantiates a new <see cref="EchoMiddleware"/>.
|
||||
/// </summary>
|
||||
/// <param name="next">The next middleware in the pipeline.</param>
|
||||
public EchoMiddleware(RequestDelegate next)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Echo the request's path in the response. Does not invoke later middleware in the pipeline.
|
||||
/// </summary>
|
||||
/// <param name="context">The <see cref="HttpContext"/> of the current request.</param>
|
||||
/// <returns>A <see cref="Task"/> that completes when writing to the response is done.</returns>
|
||||
public Task Invoke(HttpContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
context.Response.ContentType = "text/plain; charset=utf-8";
|
||||
var path = context.Request.PathBase + context.Request.Path + context.Request.QueryString;
|
||||
return context.Response.WriteAsync(path, Encoding.UTF8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace CorsMiddlewareWebSite
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddCors();
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseCors(policy => policy.WithOrigins("http://example.com"));
|
||||
app.UseMiddleware<EchoMiddleware>();
|
||||
}
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var host = new WebHostBuilder()
|
||||
.UseKestrel()
|
||||
.UseIISIntegration()
|
||||
.UseStartup<Startup>()
|
||||
.Build();
|
||||
|
||||
host.Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
CorsMiddlewareWebSite
|
||||
===
|
||||
|
||||
This web site illustrates how to use CorsMiddleware to apply a policy for entire application.
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0"?>
|
||||
<configuration>
|
||||
<system.webServer>
|
||||
<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" />
|
||||
</system.webServer>
|
||||
</configuration>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<Project>
|
||||
<PropertyGroup>
|
||||
<VersionPrefix>2.1.1</VersionPrefix>
|
||||
<VersionSuffix>rtm</VersionSuffix>
|
||||
<PackageVersion Condition="'$(IsFinalBuild)' == 'true' AND '$(VersionSuffix)' == 'rtm' ">$(VersionPrefix)</PackageVersion>
|
||||
<PackageVersion Condition="'$(IsFinalBuild)' == 'true' AND '$(VersionSuffix)' != 'rtm' ">$(VersionPrefix)-$(VersionSuffix)-final</PackageVersion>
|
||||
<BuildNumber Condition="'$(BuildNumber)' == ''">t000</BuildNumber>
|
||||
<FeatureBranchVersionPrefix Condition="'$(FeatureBranchVersionPrefix)' == ''">a-</FeatureBranchVersionPrefix>
|
||||
<VersionSuffix Condition="'$(VersionSuffix)' != '' And '$(FeatureBranchVersionSuffix)' != ''">$(FeatureBranchVersionPrefix)$(VersionSuffix)-$([System.Text.RegularExpressions.Regex]::Replace('$(FeatureBranchVersionSuffix)', '[^\w-]', '-'))</VersionSuffix>
|
||||
<VersionSuffix Condition="'$(VersionSuffix)' != '' And '$(BuildNumber)' != ''">$(VersionSuffix)-$(BuildNumber)</VersionSuffix>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
Loading…
Reference in New Issue