Merge remote-tracking branch 'origin/release/2.1' into rybrande/Mondo2.2
This commit is contained in:
commit
1ee4e2b936
|
|
@ -1,7 +1,3 @@
|
|||
[submodule "modules/Antiforgery"]
|
||||
path = modules/Antiforgery
|
||||
url = https://github.com/aspnet/Antiforgery.git
|
||||
branch = release/2.2
|
||||
[submodule "modules/BasicMiddleware"]
|
||||
path = modules/BasicMiddleware
|
||||
url = https://github.com/aspnet/BasicMiddleware.git
|
||||
|
|
@ -10,18 +6,10 @@
|
|||
path = modules/BrowserLink
|
||||
url = https://github.com/aspnet/BrowserLink.git
|
||||
branch = release/2.2
|
||||
[submodule "modules/CORS"]
|
||||
path = modules/CORS
|
||||
url = https://github.com/aspnet/CORS.git
|
||||
branch = release/2.2
|
||||
[submodule "modules/EntityFrameworkCore"]
|
||||
path = modules/EntityFrameworkCore
|
||||
url = https://github.com/aspnet/EntityFrameworkCore.git
|
||||
branch = release/2.2
|
||||
[submodule "modules/HttpSysServer"]
|
||||
path = modules/HttpSysServer
|
||||
url = https://github.com/aspnet/HttpSysServer.git
|
||||
branch = release/2.2
|
||||
[submodule "modules/Identity"]
|
||||
path = modules/Identity
|
||||
url = https://github.com/aspnet/Identity.git
|
||||
|
|
@ -42,22 +30,10 @@
|
|||
path = modules/Razor
|
||||
url = https://github.com/aspnet/Razor.git
|
||||
branch = release/2.2
|
||||
[submodule "modules/ResponseCaching"]
|
||||
path = modules/ResponseCaching
|
||||
url = https://github.com/aspnet/ResponseCaching.git
|
||||
branch = release/2.2
|
||||
[submodule "modules/Routing"]
|
||||
path = modules/Routing
|
||||
url = https://github.com/aspnet/Routing.git
|
||||
branch = release/2.2
|
||||
[submodule "modules/Scaffolding"]
|
||||
path = modules/Scaffolding
|
||||
url = https://github.com/aspnet/Scaffolding.git
|
||||
branch = release/2.2
|
||||
[submodule "modules/Security"]
|
||||
path = modules/Security
|
||||
url = https://github.com/aspnet/Security.git
|
||||
branch = release/2.2
|
||||
[submodule "modules/SignalR"]
|
||||
path = modules/SignalR
|
||||
url = https://github.com/aspnet/SignalR.git
|
||||
|
|
|
|||
|
|
@ -9,18 +9,18 @@
|
|||
<ItemGroup>
|
||||
<RepositoryBuildOrder Include="Razor" Order="6" />
|
||||
<RepositoryBuildOrder Include="EntityFrameworkCore" Order="8" />
|
||||
<RepositoryBuildOrder Include="HttpSysServer" Order="8" />
|
||||
<RepositoryBuildOrder Include="HttpSysServer" Order="8" RootPath="$(RepositoryRoot)src\HttpSysServer\" />
|
||||
<RepositoryBuildOrder Include="BasicMiddleware" Order="9" />
|
||||
<RepositoryBuildOrder Include="Antiforgery" Order="10" />
|
||||
<RepositoryBuildOrder Include="Antiforgery" Order="9" RootPath="$(RepositoryRoot)src\Antiforgery\" />
|
||||
<RepositoryBuildOrder Include="IISIntegration" Order="10" RootPath="$(RepositoryRoot)src\IISIntegration\" />
|
||||
<RepositoryBuildOrder Include="CORS" Order="12" />
|
||||
<RepositoryBuildOrder Include="CORS" Order="11" RootPath="$(RepositoryRoot)src\CORS\" />
|
||||
<RepositoryBuildOrder Include="StaticFiles" Order="11" RootPath="$(RepositoryRoot)src\StaticFiles\" />
|
||||
<RepositoryBuildOrder Include="Routing" Order="12" />
|
||||
<RepositoryBuildOrder Include="ResponseCaching" Order="11" />
|
||||
<RepositoryBuildOrder Include="Routing" Order="11" RootPath="$(RepositoryRoot)src\Routing\" />
|
||||
<RepositoryBuildOrder Include="ResponseCaching" Order="11" RootPath="$(RepositoryRoot)src\ResponseCaching\" />
|
||||
<RepositoryBuildOrder Include="Session" Order="11" RootPath="$(RepositoryRoot)src\Session\" />
|
||||
<RepositoryBuildOrder Include="ServerTests" Order="11" RootPath="$(RepositoryRoot)src\ServerTests\" />
|
||||
<RepositoryBuildOrder Include="Localization" Order="13" />
|
||||
<RepositoryBuildOrder Include="Security" Order="13" />
|
||||
<RepositoryBuildOrder Include="Localization" Order="12" />
|
||||
<RepositoryBuildOrder Include="Security" Order="13" RootPath="$(RepositoryRoot)src\Security\" />
|
||||
<RepositoryBuildOrder Include="MetaPackages" Order="13" RootPath="$(RepositoryRoot)src\MetaPackages\" />
|
||||
<RepositoryBuildOrder Include="Mvc" Order="14" />
|
||||
<RepositoryBuildOrder Include="AADIntegration" Order="15" RootPath="$(RepositoryRoot)src\AADIntegration\" />
|
||||
|
|
|
|||
|
|
@ -48,12 +48,12 @@
|
|||
|
||||
<ItemGroup>
|
||||
<ShippedRepository Include="AADIntegration" RootPath="$(RepositoryRoot)src\AADIntegration\" />
|
||||
<ShippedRepository Include="Antiforgery" />
|
||||
<ShippedRepository Include="Antiforgery" RootPath="$(RepositoryRoot)src\Antiforgery\" />
|
||||
<ShippedRepository Include="AzureIntegration" RootPath="$(RepositoryRoot)src\AzureIntegration\" />
|
||||
<ShippedRepository Include="BasicMiddleware" />
|
||||
<ShippedRepository Include="CORS" />
|
||||
<ShippedRepository Include="CORS" RootPath="$(RepositoryRoot)src\CORS\" />
|
||||
<ShippedRepository Include="EntityFrameworkCore" />
|
||||
<ShippedRepository Include="HttpSysServer" />
|
||||
<ShippedRepository Include="HttpSysServer" RootPath="$(RepositoryRoot)src\HttpSysServer\" />
|
||||
<ShippedRepository Include="Identity" />
|
||||
<ShippedRepository Include="JavaScriptServices" RootPath="$(RepositoryRoot)src\JavaScriptServices\" />
|
||||
<ShippedRepository Include="Localization" />
|
||||
|
|
@ -61,9 +61,9 @@
|
|||
<ShippedRepository Include="Mvc" />
|
||||
<ShippedRepository Include="MvcPrecompilation" />
|
||||
<ShippedRepository Include="Razor" />
|
||||
<ShippedRepository Include="ResponseCaching" />
|
||||
<ShippedRepository Include="Routing" />
|
||||
<ShippedRepository Include="Security" />
|
||||
<ShippedRepository Include="ResponseCaching" RootPath="$(RepositoryRoot)src\ResponseCaching\" />
|
||||
<ShippedRepository Include="Routing" RootPath="$(RepositoryRoot)src\Routing\" />
|
||||
<ShippedRepository Include="Security" RootPath="$(RepositoryRoot)src\Security\" />
|
||||
<ShippedRepository Include="Session" RootPath="$(RepositoryRoot)src\Session\" />
|
||||
<ShippedRepository Include="SignalR" />
|
||||
<ShippedRepository Include="StaticFiles" RootPath="$(RepositoryRoot)src\StaticFiles\" />
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 9e5146cff912ebbd003d597eb055144e740759fa
|
||||
|
|
@ -1 +0,0 @@
|
|||
Subproject commit f05b0e792d2361be214947798857ef3eac77825d
|
||||
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 3e08bf8833827737a5bd1b64210c7a5e8e2941ec
|
||||
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 9f49398f28d7c1e0dc0305f8080351c413b0f297
|
||||
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 3d828221a19d91907c52c2f40928b019bee1ef92
|
||||
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 93926543f8469614c2feb23de8a8c0561b8b2463
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<Product>Microsoft ASP.NET Core AAD Integration</Product>
|
||||
<RepositoryUrl>https://github.com/aspnet/AADIntegration</RepositoryUrl>
|
||||
<RepositoryUrl>https://github.com/aspnet/AspNetCore</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<RepositoryRoot>$(MSBuildThisFileDirectory)</RepositoryRoot>
|
||||
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)..\..\eng\AspNetCore.snk</AssemblyOriginatorKeyFile>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
[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/
|
||||
global.json
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.26208.0
|
||||
MinimumVisualStudioVersion = 15.0.26730.03
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{71D070C4-B325-48F7-9F25-DD4E91C2BBCA}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{6EDD8B57-4DE8-4246-A6A3-47ECD92740B4}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Antiforgery", "src\Microsoft.AspNetCore.Antiforgery\Microsoft.AspNetCore.Antiforgery.csproj", "{46FB03FB-7A44-4106-BDDE-D6F5417544AB}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Antiforgery.Test", "test\Microsoft.AspNetCore.Antiforgery.Test\Microsoft.AspNetCore.Antiforgery.Test.csproj", "{415E83F8-6002-47E4-AA8E-CD5169C06F28}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{46FB03FB-7A44-4106-BDDE-D6F5417544AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{46FB03FB-7A44-4106-BDDE-D6F5417544AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{46FB03FB-7A44-4106-BDDE-D6F5417544AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{46FB03FB-7A44-4106-BDDE-D6F5417544AB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{415E83F8-6002-47E4-AA8E-CD5169C06F28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{415E83F8-6002-47E4-AA8E-CD5169C06F28}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{415E83F8-6002-47E4-AA8E-CD5169C06F28}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{415E83F8-6002-47E4-AA8E-CD5169C06F28}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{46FB03FB-7A44-4106-BDDE-D6F5417544AB} = {71D070C4-B325-48F7-9F25-DD4E91C2BBCA}
|
||||
{415E83F8-6002-47E4-AA8E-CD5169C06F28} = {6EDD8B57-4DE8-4246-A6A3-47ECD92740B4}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<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/AspNetCore</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>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Internal.AspNetCore.Sdk" PrivateAssets="All" Version="$(InternalAspNetCoreSdkPackageVersion)" />
|
||||
</ItemGroup>
|
||||
</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,14 @@
|
|||
Antiforgery
|
||||
===========
|
||||
|
||||
AppVeyor: [](https://ci.appveyor.com/project/aspnetci/Antiforgery/branch/dev)
|
||||
|
||||
Travis: [](https://travis-ci.org/aspnet/Antiforgery)
|
||||
|
||||
Antiforgery system for generating secure tokens to prevent Cross-Site Request Forgery attacks.
|
||||
|
||||
This project is part of ASP.NET Core. You can find documentation and getting started instructions for ASP.NET Core at the [Home](https://github.com/aspnet/home) repo.
|
||||
|
||||
Samples can be found in [Entropy](https://github.com/aspnet/Entropy).
|
||||
The [MVC](https://github.com/aspnet/Entropy/tree/dev/samples/Antiforgery.MvcWithAuthAndAjax) sample shows how to use Antiforgery in MVC when making AJAX requests.
|
||||
The [Angular](https://github.com/aspnet/Entropy/tree/dev/samples/Antiforgery.Angular1) sample shows how to use Antiforgery with Angular 1.
|
||||
Binary file not shown.
|
|
@ -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>
|
||||
<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">
|
||||
<MicrosoftAspNetCoreDataProtectionPackageVersion>2.1.1</MicrosoftAspNetCoreDataProtectionPackageVersion>
|
||||
<MicrosoftAspNetCoreHttpAbstractionsPackageVersion>2.1.1</MicrosoftAspNetCoreHttpAbstractionsPackageVersion>
|
||||
<MicrosoftAspNetCoreHttpExtensionsPackageVersion>2.1.1</MicrosoftAspNetCoreHttpExtensionsPackageVersion>
|
||||
<MicrosoftAspNetCoreHttpPackageVersion>2.1.1</MicrosoftAspNetCoreHttpPackageVersion>
|
||||
<MicrosoftAspNetCoreTestingPackageVersion>2.1.0</MicrosoftAspNetCoreTestingPackageVersion>
|
||||
<MicrosoftAspNetCoreWebUtilitiesPackageVersion>2.1.1</MicrosoftAspNetCoreWebUtilitiesPackageVersion>
|
||||
<MicrosoftExtensionsDependencyInjectionPackageVersion>2.1.1</MicrosoftExtensionsDependencyInjectionPackageVersion>
|
||||
<MicrosoftExtensionsLoggingPackageVersion>2.1.1</MicrosoftExtensionsLoggingPackageVersion>
|
||||
<MicrosoftExtensionsLoggingTestingPackageVersion>2.1.1</MicrosoftExtensionsLoggingTestingPackageVersion>
|
||||
<MicrosoftExtensionsObjectPoolPackageVersion>2.1.1</MicrosoftExtensionsObjectPoolPackageVersion>
|
||||
<MicrosoftExtensionsWebEncodersPackageVersion>2.1.1</MicrosoftExtensionsWebEncodersPackageVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<Project>
|
||||
<Import Project="dependencies.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<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,150 @@
|
|||
// 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.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Antiforgery
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides programmatic configuration for the antiforgery token system.
|
||||
/// </summary>
|
||||
public class AntiforgeryOptions
|
||||
{
|
||||
private const string AntiforgeryTokenFieldName = "__RequestVerificationToken";
|
||||
private const string AntiforgeryTokenHeaderName = "RequestVerificationToken";
|
||||
|
||||
private string _formFieldName = AntiforgeryTokenFieldName;
|
||||
|
||||
private CookieBuilder _cookieBuilder = new CookieBuilder
|
||||
{
|
||||
SameSite = SameSiteMode.Strict,
|
||||
HttpOnly = true,
|
||||
|
||||
// Check the comment on CookieBuilder for more details
|
||||
IsEssential = true,
|
||||
|
||||
// Some browsers do not allow non-secure endpoints to set cookies with a 'secure' flag or overwrite cookies
|
||||
// whose 'secure' flag is set (http://httpwg.org/http-extensions/draft-ietf-httpbis-cookie-alone.html).
|
||||
// Since mixing secure and non-secure endpoints is a common scenario in applications, we are relaxing the
|
||||
// restriction on secure policy on some cookies by setting to 'None'. Cookies related to authentication or
|
||||
// authorization use a stronger policy than 'None'.
|
||||
SecurePolicy = CookieSecurePolicy.None,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The default cookie prefix, which is ".AspNetCore.Antiforgery.".
|
||||
/// </summary>
|
||||
public static readonly string DefaultCookiePrefix = ".AspNetCore.Antiforgery.";
|
||||
|
||||
/// <summary>
|
||||
/// Determines the settings used to create the antiforgery cookies.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// If an explicit <see cref="CookieBuilder.Name"/> is not provided, the system will automatically generate a
|
||||
/// unique name that begins with <see cref="DefaultCookiePrefix"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <see cref="CookieBuilder.SameSite"/> defaults to <see cref="SameSiteMode.Strict"/>.
|
||||
/// <see cref="CookieBuilder.HttpOnly"/> defaults to <c>true</c>.
|
||||
/// <see cref="CookieBuilder.IsEssential"/> defaults to <c>true</c>. The cookie used by the antiforgery system
|
||||
/// is part of a security system that is necessary when using cookie-based authentication. It should be
|
||||
/// considered required for the application to function.
|
||||
/// <see cref="CookieBuilder.SecurePolicy"/> defaults to <see cref="CookieSecurePolicy.None"/>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public CookieBuilder Cookie
|
||||
{
|
||||
get => _cookieBuilder;
|
||||
set => _cookieBuilder = value ?? throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the name of the antiforgery token field that is used by the antiforgery system.
|
||||
/// </summary>
|
||||
public string FormFieldName
|
||||
{
|
||||
get => _formFieldName;
|
||||
set => _formFieldName = value ?? throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the name of the header value that is used by the antiforgery system. If <c>null</c> then
|
||||
/// antiforgery validation will only consider form data.
|
||||
/// </summary>
|
||||
public string HeaderName { get; set; } = AntiforgeryTokenHeaderName;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies whether to suppress the generation of X-Frame-Options header
|
||||
/// which is used to prevent ClickJacking. By default, the X-Frame-Options
|
||||
/// header is generated with the value SAMEORIGIN. If this setting is 'true',
|
||||
/// the X-Frame-Options header will not be generated for the response.
|
||||
/// </summary>
|
||||
public bool SuppressXFrameOptionsHeader { get; set; }
|
||||
|
||||
#region Obsolete API
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// This property is obsolete and will be removed in a future version. The recommended alternative is <seealso cref="CookieBuilder.Name"/> on <see cref="Cookie"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Specifies the name of the cookie that is used by the antiforgery system.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If an explicit name is not provided, the system will automatically generate a
|
||||
/// unique name that begins with <see cref="DefaultCookiePrefix"/>.
|
||||
/// </remarks>
|
||||
[Obsolete("This property is obsolete and will be removed in a future version. The recommended alternative is " + nameof(Cookie) + "." + nameof(CookieBuilder.Name) + ".")]
|
||||
public string CookieName { get => Cookie.Name; set => Cookie.Name = value; }
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// This property is obsolete and will be removed in a future version. The recommended alternative is <seealso cref="CookieBuilder.Path"/> on <see cref="Cookie"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The path set on the cookie. If set to <c>null</c>, the "path" attribute on the cookie is set to the current
|
||||
/// request's <see cref="HttpRequest.PathBase"/> value. If the value of <see cref="HttpRequest.PathBase"/> is
|
||||
/// <c>null</c> or empty, then the "path" attribute is set to the value of <see cref="CookieOptions.Path"/>.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Obsolete("This property is obsolete and will be removed in a future version. The recommended alternative is " + nameof(Cookie) + "." + nameof(CookieBuilder.Path) + ".")]
|
||||
public PathString? CookiePath { get => Cookie.Path; set => Cookie.Path = value; }
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// This property is obsolete and will be removed in a future version. The recommended alternative is <seealso cref="CookieBuilder.Domain"/> on <see cref="Cookie"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The domain set on the cookie. By default its <c>null</c> which results in the "domain" attribute not being set.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Obsolete("This property is obsolete and will be removed in a future version. The recommended alternative is " + nameof(Cookie) + "." + nameof(CookieBuilder.Domain) + ".")]
|
||||
public string CookieDomain { get => Cookie.Domain; set => Cookie.Domain = value; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// This property is obsolete and will be removed in a future version.
|
||||
/// The recommended alternative is to set <seealso cref="CookieBuilder.SecurePolicy"/> on <see cref="Cookie"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <c>true</c> is equivalent to <see cref="CookieSecurePolicy.Always"/>.
|
||||
/// <c>false</c> is equivalent to <see cref="CookieSecurePolicy.None"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Specifies whether SSL is required for the antiforgery system
|
||||
/// to operate. If this setting is 'true' and a non-SSL request
|
||||
/// comes into the system, all antiforgery APIs will fail.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Obsolete("This property is obsolete and will be removed in a future version. The recommended alternative is to set " + nameof(Cookie) + "." + nameof(CookieBuilder.SecurePolicy) + ".")]
|
||||
public bool RequireSsl
|
||||
{
|
||||
get => Cookie.SecurePolicy == CookieSecurePolicy.Always;
|
||||
set => Cookie.SecurePolicy = value ? CookieSecurePolicy.Always : CookieSecurePolicy.None;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
// 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.Antiforgery;
|
||||
using Microsoft.AspNetCore.Antiforgery.Internal;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods for setting up antiforgery services in an <see cref="IServiceCollection" />.
|
||||
/// </summary>
|
||||
public static class AntiforgeryServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds antiforgery 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 AddAntiforgery(this IServiceCollection services)
|
||||
{
|
||||
if (services == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(services));
|
||||
}
|
||||
|
||||
services.AddDataProtection();
|
||||
|
||||
// Don't overwrite any options setups that a user may have added.
|
||||
services.TryAddEnumerable(
|
||||
ServiceDescriptor.Transient<IConfigureOptions<AntiforgeryOptions>, AntiforgeryOptionsSetup>());
|
||||
|
||||
services.TryAddSingleton<IAntiforgery, DefaultAntiforgery>();
|
||||
services.TryAddSingleton<IAntiforgeryTokenGenerator, DefaultAntiforgeryTokenGenerator>();
|
||||
services.TryAddSingleton<IAntiforgeryTokenSerializer, DefaultAntiforgeryTokenSerializer>();
|
||||
services.TryAddSingleton<IAntiforgeryTokenStore, DefaultAntiforgeryTokenStore>();
|
||||
services.TryAddSingleton<IClaimUidExtractor, DefaultClaimUidExtractor>();
|
||||
services.TryAddSingleton<IAntiforgeryAdditionalDataProvider, DefaultAntiforgeryAdditionalDataProvider>();
|
||||
|
||||
services.TryAddSingleton<ObjectPool<AntiforgerySerializationContext>>(serviceProvider =>
|
||||
{
|
||||
var provider = serviceProvider.GetRequiredService<ObjectPoolProvider>();
|
||||
var policy = new AntiforgerySerializationContextPooledObjectPolicy();
|
||||
return provider.Create(policy);
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds antiforgery 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{AntiforgeryOptions}"/> to configure the provided <see cref="AntiforgeryOptions"/>.</param>
|
||||
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
|
||||
public static IServiceCollection AddAntiforgery(this IServiceCollection services, Action<AntiforgeryOptions> setupAction)
|
||||
{
|
||||
if (services == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(services));
|
||||
}
|
||||
|
||||
if (setupAction == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(setupAction));
|
||||
}
|
||||
|
||||
services.AddAntiforgery();
|
||||
services.Configure(setupAction);
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
// 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.Antiforgery
|
||||
{
|
||||
/// <summary>
|
||||
/// The antiforgery token pair (cookie and request token) for a request.
|
||||
/// </summary>
|
||||
public class AntiforgeryTokenSet
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates the antiforgery token pair (cookie and request token) for a request.
|
||||
/// </summary>
|
||||
/// <param name="requestToken">The token that is supplied in the request.</param>
|
||||
/// <param name="cookieToken">The token that is supplied in the request cookie.</param>
|
||||
/// <param name="formFieldName">The name of the form field used for the request token.</param>
|
||||
/// <param name="headerName">The name of the header used for the request token.</param>
|
||||
public AntiforgeryTokenSet(
|
||||
string requestToken,
|
||||
string cookieToken,
|
||||
string formFieldName,
|
||||
string headerName)
|
||||
{
|
||||
if (formFieldName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(formFieldName));
|
||||
}
|
||||
|
||||
RequestToken = requestToken;
|
||||
CookieToken = cookieToken;
|
||||
FormFieldName = formFieldName;
|
||||
HeaderName = headerName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the request token.
|
||||
/// </summary>
|
||||
public string RequestToken { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the form field used for the request token.
|
||||
/// </summary>
|
||||
public string FormFieldName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the header used for the request token.
|
||||
/// </summary>
|
||||
public string HeaderName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the cookie token.
|
||||
/// </summary>
|
||||
public string CookieToken { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Antiforgery
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="Exception"/> that is thrown when the antiforgery token validation fails.
|
||||
/// </summary>
|
||||
public class AntiforgeryValidationException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="AntiforgeryValidationException"/> with the specified
|
||||
/// exception message.
|
||||
/// </summary>
|
||||
/// <param name="message">The message that describes the error.</param>
|
||||
public AntiforgeryValidationException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="AntiforgeryValidationException"/> with the specified
|
||||
/// exception message and inner exception.
|
||||
/// </summary>
|
||||
/// <param name="message">The message that describes the error.</param>
|
||||
/// <param name="innerException">The inner <see cref="Exception"/>.</param>
|
||||
public AntiforgeryValidationException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
// 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.Antiforgery
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides access to the antiforgery system, which provides protection against
|
||||
/// Cross-site Request Forgery (XSRF, also called CSRF) attacks.
|
||||
/// </summary>
|
||||
public interface IAntiforgery
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates an <see cref="AntiforgeryTokenSet"/> for this request and stores the cookie token
|
||||
/// in the response. This operation also sets the "Cache-control" and "Pragma" headers to "no-cache" and
|
||||
/// the "X-Frame-Options" header to "SAMEORIGIN".
|
||||
/// </summary>
|
||||
/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
|
||||
/// <returns>An <see cref="AntiforgeryTokenSet" /> with tokens for the response.</returns>
|
||||
/// <remarks>
|
||||
/// This method has a side effect:
|
||||
/// A response cookie is set if there is no valid cookie associated with the request.
|
||||
/// </remarks>
|
||||
AntiforgeryTokenSet GetAndStoreTokens(HttpContext httpContext);
|
||||
|
||||
/// <summary>
|
||||
/// Generates an <see cref="AntiforgeryTokenSet"/> for this request.
|
||||
/// </summary>
|
||||
/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
|
||||
/// <remarks>
|
||||
/// Unlike <see cref="GetAndStoreTokens(HttpContext)"/>, this method has no side effect. The caller
|
||||
/// is responsible for setting the response cookie and injecting the returned
|
||||
/// form token as appropriate.
|
||||
/// </remarks>
|
||||
AntiforgeryTokenSet GetTokens(HttpContext httpContext);
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously returns a value indicating whether the request passes antiforgery validation. If the
|
||||
/// request uses a safe HTTP method (GET, HEAD, OPTIONS, TRACE), the antiforgery token is not validated.
|
||||
/// </summary>
|
||||
/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="Task{Boolean}"/> that, when completed, returns <c>true</c> if the request uses a safe HTTP
|
||||
/// method or contains a valid antiforgery token, otherwise returns <c>false</c>.
|
||||
/// </returns>
|
||||
Task<bool> IsRequestValidAsync(HttpContext httpContext);
|
||||
|
||||
/// <summary>
|
||||
/// Validates an antiforgery token that was supplied as part of the request.
|
||||
/// </summary>
|
||||
/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
|
||||
/// <exception cref="AntiforgeryValidationException">
|
||||
/// Thrown when the request does not include a valid antiforgery token.
|
||||
/// </exception>
|
||||
Task ValidateRequestAsync(HttpContext httpContext);
|
||||
|
||||
/// <summary>
|
||||
/// Generates and stores an antiforgery cookie token if one is not available or not valid.
|
||||
/// </summary>
|
||||
/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
|
||||
void SetCookieTokenAndHeader(HttpContext httpContext);
|
||||
}
|
||||
}
|
||||
|
|
@ -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.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Antiforgery
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows providing or validating additional custom data for antiforgery tokens.
|
||||
/// For example, the developer could use this to supply a nonce when the token is
|
||||
/// generated, then he could validate the nonce when the token is validated.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The antiforgery system already embeds the client's username within the
|
||||
/// generated tokens. This interface provides and consumes <em>supplemental</em>
|
||||
/// data. If an incoming antiforgery token contains supplemental data but no
|
||||
/// additional data provider is configured, the supplemental data will not be
|
||||
/// validated.
|
||||
/// </remarks>
|
||||
public interface IAntiforgeryAdditionalDataProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides additional data to be stored for the antiforgery tokens generated
|
||||
/// during this request.
|
||||
/// </summary>
|
||||
/// <param name="context">Information about the current request.</param>
|
||||
/// <returns>Supplemental data to embed within the antiforgery token.</returns>
|
||||
string GetAdditionalData(HttpContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Validates additional data that was embedded inside an incoming antiforgery
|
||||
/// token.
|
||||
/// </summary>
|
||||
/// <param name="context">Information about the current request.</param>
|
||||
/// <param name="additionalData">Supplemental data that was embedded within the token.</param>
|
||||
/// <returns>True if the data is valid; false if the data is invalid.</returns>
|
||||
bool ValidateAdditionalData(HttpContext context, string additionalData);
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Antiforgery.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to hold per-request state.
|
||||
/// </summary>
|
||||
public class AntiforgeryFeature : IAntiforgeryFeature
|
||||
{
|
||||
public bool HaveDeserializedCookieToken { get; set; }
|
||||
|
||||
public AntiforgeryToken CookieToken { get; set; }
|
||||
|
||||
public bool HaveDeserializedRequestToken { get; set; }
|
||||
|
||||
public AntiforgeryToken RequestToken { get; set; }
|
||||
|
||||
public bool HaveGeneratedNewCookieToken { get; set; }
|
||||
|
||||
// After HaveGeneratedNewCookieToken is true, remains null if CookieToken is valid.
|
||||
public AntiforgeryToken NewCookieToken { get; set; }
|
||||
|
||||
// After HaveGeneratedNewCookieToken is true, remains null if CookieToken is valid.
|
||||
public string NewCookieTokenString { get; set; }
|
||||
|
||||
public AntiforgeryToken NewRequestToken { get; set; }
|
||||
|
||||
public string NewRequestTokenString { get; set; }
|
||||
|
||||
// Always false if NewCookieToken is null. Never store null cookie token or re-store cookie token from request.
|
||||
public bool HaveStoredNewCookieToken { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
// 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.Antiforgery.Internal
|
||||
{
|
||||
internal static class AntiforgeryLoggerExtensions
|
||||
{
|
||||
private static readonly Action<ILogger, Exception> _failedToDeserialzeTokens;
|
||||
private static readonly Action<ILogger, string, Exception> _validationFailed;
|
||||
private static readonly Action<ILogger, Exception> _validated;
|
||||
private static readonly Action<ILogger, string, Exception> _missingCookieToken;
|
||||
private static readonly Action<ILogger, string, string, Exception> _missingRequestToken;
|
||||
private static readonly Action<ILogger, Exception> _newCookieToken;
|
||||
private static readonly Action<ILogger, Exception> _reusedCookieToken;
|
||||
private static readonly Action<ILogger, Exception> _tokenDeserializeException;
|
||||
private static readonly Action<ILogger, Exception> _responseCacheHeadersOverridenToNoCache;
|
||||
|
||||
static AntiforgeryLoggerExtensions()
|
||||
{
|
||||
_validationFailed = LoggerMessage.Define<string>(
|
||||
LogLevel.Warning,
|
||||
1,
|
||||
"Antiforgery validation failed with message '{Message}'.");
|
||||
_validated = LoggerMessage.Define(
|
||||
LogLevel.Debug,
|
||||
2,
|
||||
"Antiforgery successfully validated a request.");
|
||||
_missingCookieToken = LoggerMessage.Define<string>(
|
||||
LogLevel.Warning,
|
||||
3,
|
||||
"The required antiforgery cookie '{CookieName}' is not present.");
|
||||
_missingRequestToken = LoggerMessage.Define<string, string>(
|
||||
LogLevel.Warning,
|
||||
4,
|
||||
"The required antiforgery request token was not provided in either form field '{FormFieldName}' "
|
||||
+ "or header '{HeaderName}'.");
|
||||
_newCookieToken = LoggerMessage.Define(
|
||||
LogLevel.Debug,
|
||||
5,
|
||||
"A new antiforgery cookie token was created.");
|
||||
_reusedCookieToken = LoggerMessage.Define(
|
||||
LogLevel.Debug,
|
||||
6,
|
||||
"An antiforgery cookie token was reused.");
|
||||
_tokenDeserializeException = LoggerMessage.Define(
|
||||
LogLevel.Error,
|
||||
7,
|
||||
"An exception was thrown while deserializing the token.");
|
||||
_responseCacheHeadersOverridenToNoCache = LoggerMessage.Define(
|
||||
LogLevel.Warning,
|
||||
8,
|
||||
"The 'Cache-Control' and 'Pragma' headers have been overridden and set to 'no-cache, no-store' and " +
|
||||
"'no-cache' respectively to prevent caching of this response. Any response that uses antiforgery " +
|
||||
"should not be cached.");
|
||||
_failedToDeserialzeTokens = LoggerMessage.Define(
|
||||
LogLevel.Debug,
|
||||
9,
|
||||
"Failed to deserialize antiforgery tokens.");
|
||||
}
|
||||
|
||||
public static void ValidationFailed(this ILogger logger, string message)
|
||||
{
|
||||
_validationFailed(logger, message, null);
|
||||
}
|
||||
|
||||
public static void ValidatedAntiforgeryToken(this ILogger logger)
|
||||
{
|
||||
_validated(logger, null);
|
||||
}
|
||||
|
||||
public static void MissingCookieToken(this ILogger logger, string cookieName)
|
||||
{
|
||||
_missingCookieToken(logger, cookieName, null);
|
||||
}
|
||||
|
||||
public static void MissingRequestToken(this ILogger logger, string formFieldName, string headerName)
|
||||
{
|
||||
_missingRequestToken(logger, formFieldName, headerName, null);
|
||||
}
|
||||
|
||||
public static void NewCookieToken(this ILogger logger)
|
||||
{
|
||||
_newCookieToken(logger, null);
|
||||
}
|
||||
|
||||
public static void ReusedCookieToken(this ILogger logger)
|
||||
{
|
||||
_reusedCookieToken(logger, null);
|
||||
}
|
||||
|
||||
public static void TokenDeserializeException(this ILogger logger, Exception exception)
|
||||
{
|
||||
_tokenDeserializeException(logger, exception);
|
||||
}
|
||||
|
||||
public static void ResponseCacheHeadersOverridenToNoCache(this ILogger logger)
|
||||
{
|
||||
_responseCacheHeadersOverridenToNoCache(logger, null);
|
||||
}
|
||||
|
||||
public static void FailedToDeserialzeTokens(this ILogger logger, Exception exception)
|
||||
{
|
||||
_failedToDeserialzeTokens(logger, exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Antiforgery.Internal
|
||||
{
|
||||
public class AntiforgeryOptionsSetup : ConfigureOptions<AntiforgeryOptions>
|
||||
{
|
||||
public AntiforgeryOptionsSetup(IOptions<DataProtectionOptions> dataProtectionOptionsAccessor)
|
||||
: base((options) => ConfigureOptions(options, dataProtectionOptionsAccessor.Value))
|
||||
{
|
||||
}
|
||||
|
||||
public static void ConfigureOptions(AntiforgeryOptions options, DataProtectionOptions dataProtectionOptions)
|
||||
{
|
||||
if (options.Cookie.Name == null)
|
||||
{
|
||||
var applicationId = dataProtectionOptions.ApplicationDiscriminator ?? string.Empty;
|
||||
options.Cookie.Name = AntiforgeryOptions.DefaultCookiePrefix + ComputeCookieName(applicationId);
|
||||
}
|
||||
}
|
||||
|
||||
private static string ComputeCookieName(string applicationId)
|
||||
{
|
||||
using (var sha256 = CryptographyAlgorithms.CreateSHA256())
|
||||
{
|
||||
var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(applicationId));
|
||||
var subHash = hash.Take(8).ToArray();
|
||||
return WebEncoders.Base64UrlEncode(subHash);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
// 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 System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.AspNetCore.Antiforgery.Internal
|
||||
{
|
||||
public class AntiforgerySerializationContext
|
||||
{
|
||||
// Avoid allocating 256 bytes (the default) and using 18 (the AntiforgeryToken minimum). 64 bytes is enough for
|
||||
// a short username or claim UID and some additional data. MemoryStream bumps capacity to 256 if exceeded.
|
||||
private const int InitialStreamSize = 64;
|
||||
|
||||
// Don't let the MemoryStream grow beyond 1 MB.
|
||||
private const int MaximumStreamSize = 0x100000;
|
||||
|
||||
// Start _chars off with length 256 (18 bytes is protected into 116 bytes then encoded into 156 characters).
|
||||
// Double length from there if necessary.
|
||||
private const int InitialCharsLength = 256;
|
||||
|
||||
// Don't let _chars grow beyond 512k characters.
|
||||
private const int MaximumCharsLength = 0x80000;
|
||||
|
||||
private char[] _chars;
|
||||
private MemoryStream _stream;
|
||||
private BinaryReader _reader;
|
||||
private BinaryWriter _writer;
|
||||
private SHA256 _sha256;
|
||||
|
||||
public MemoryStream Stream
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_stream == null)
|
||||
{
|
||||
_stream = new MemoryStream(InitialStreamSize);
|
||||
}
|
||||
|
||||
return _stream;
|
||||
}
|
||||
private set
|
||||
{
|
||||
_stream = value;
|
||||
}
|
||||
}
|
||||
|
||||
public BinaryReader Reader
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_reader == null)
|
||||
{
|
||||
// Leave open to clean up correctly even if only one of the reader or writer has been created.
|
||||
_reader = new BinaryReader(Stream, Encoding.UTF8, leaveOpen: true);
|
||||
}
|
||||
|
||||
return _reader;
|
||||
}
|
||||
private set
|
||||
{
|
||||
_reader = value;
|
||||
}
|
||||
}
|
||||
|
||||
public BinaryWriter Writer
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_writer == null)
|
||||
{
|
||||
// Leave open to clean up correctly even if only one of the reader or writer has been created.
|
||||
_writer = new BinaryWriter(Stream, Encoding.UTF8, leaveOpen: true);
|
||||
}
|
||||
|
||||
return _writer;
|
||||
}
|
||||
private set
|
||||
{
|
||||
_writer = value;
|
||||
}
|
||||
}
|
||||
|
||||
public SHA256 Sha256
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_sha256 == null)
|
||||
{
|
||||
_sha256 = CryptographyAlgorithms.CreateSHA256();
|
||||
}
|
||||
|
||||
return _sha256;
|
||||
}
|
||||
private set
|
||||
{
|
||||
_sha256 = value;
|
||||
}
|
||||
}
|
||||
|
||||
public char[] GetChars(int count)
|
||||
{
|
||||
if (_chars == null || _chars.Length < count)
|
||||
{
|
||||
var newLength = _chars == null ? InitialCharsLength : checked(_chars.Length * 2);
|
||||
while (newLength < count)
|
||||
{
|
||||
newLength = checked(newLength * 2);
|
||||
}
|
||||
|
||||
_chars = new char[newLength];
|
||||
}
|
||||
|
||||
return _chars;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
if (_chars != null && _chars.Length > MaximumCharsLength)
|
||||
{
|
||||
_chars = null;
|
||||
}
|
||||
|
||||
if (_stream != null)
|
||||
{
|
||||
if (Stream.Capacity > MaximumStreamSize)
|
||||
{
|
||||
Stream = null;
|
||||
Reader = null;
|
||||
Writer = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
Stream.Position = 0L;
|
||||
Stream.SetLength(0L);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
// 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.ObjectPool;
|
||||
|
||||
namespace Microsoft.AspNetCore.Antiforgery.Internal
|
||||
{
|
||||
public class AntiforgerySerializationContextPooledObjectPolicy
|
||||
: IPooledObjectPolicy<AntiforgerySerializationContext>
|
||||
{
|
||||
public AntiforgerySerializationContext Create()
|
||||
{
|
||||
return new AntiforgerySerializationContext();
|
||||
}
|
||||
|
||||
public bool Return(AntiforgerySerializationContext obj)
|
||||
{
|
||||
obj.Reset();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
// 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.Antiforgery.Internal
|
||||
{
|
||||
public sealed class AntiforgeryToken
|
||||
{
|
||||
internal const int SecurityTokenBitLength = 128;
|
||||
internal const int ClaimUidBitLength = 256;
|
||||
|
||||
private string _additionalData = string.Empty;
|
||||
private string _username = string.Empty;
|
||||
private BinaryBlob _securityToken;
|
||||
|
||||
public string AdditionalData
|
||||
{
|
||||
get { return _additionalData; }
|
||||
set
|
||||
{
|
||||
_additionalData = value ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public BinaryBlob ClaimUid { get; set; }
|
||||
|
||||
public bool IsCookieToken { get; set; }
|
||||
|
||||
public BinaryBlob SecurityToken
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_securityToken == null)
|
||||
{
|
||||
_securityToken = new BinaryBlob(SecurityTokenBitLength);
|
||||
}
|
||||
return _securityToken;
|
||||
}
|
||||
set
|
||||
{
|
||||
_securityToken = value;
|
||||
}
|
||||
}
|
||||
|
||||
public string Username
|
||||
{
|
||||
get { return _username; }
|
||||
set
|
||||
{
|
||||
_username = value ?? string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.AspNetCore.Antiforgery.Internal
|
||||
{
|
||||
// Represents a binary blob (token) that contains random data.
|
||||
// Useful for binary data inside a serialized stream.
|
||||
[DebuggerDisplay("{DebuggerString}")]
|
||||
public sealed class BinaryBlob : IEquatable<BinaryBlob>
|
||||
{
|
||||
private static readonly RandomNumberGenerator _randomNumberGenerator = RandomNumberGenerator.Create();
|
||||
private readonly byte[] _data;
|
||||
|
||||
// Generates a new token using a specified bit length.
|
||||
public BinaryBlob(int bitLength)
|
||||
: this(bitLength, GenerateNewToken(bitLength))
|
||||
{
|
||||
}
|
||||
|
||||
// Generates a token using an existing binary value.
|
||||
public BinaryBlob(int bitLength, byte[] data)
|
||||
{
|
||||
if (bitLength < 32 || bitLength % 8 != 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("bitLength");
|
||||
}
|
||||
if (data == null || data.Length != bitLength / 8)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("data");
|
||||
}
|
||||
|
||||
_data = data;
|
||||
}
|
||||
|
||||
public int BitLength
|
||||
{
|
||||
get
|
||||
{
|
||||
return checked(_data.Length * 8);
|
||||
}
|
||||
}
|
||||
|
||||
private string DebuggerString
|
||||
{
|
||||
get
|
||||
{
|
||||
var sb = new StringBuilder("0x", 2 + (_data.Length * 2));
|
||||
for (var i = 0; i < _data.Length; i++)
|
||||
{
|
||||
sb.AppendFormat(CultureInfo.InvariantCulture, "{0:x2}", _data[i]);
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return Equals(obj as BinaryBlob);
|
||||
}
|
||||
|
||||
public bool Equals(BinaryBlob other)
|
||||
{
|
||||
if (other == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Debug.Assert(_data.Length == other._data.Length);
|
||||
return AreByteArraysEqual(_data, other._data);
|
||||
}
|
||||
|
||||
public byte[] GetData()
|
||||
{
|
||||
return _data;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
// Since data should contain uniformly-distributed entropy, the
|
||||
// first 32 bits can serve as the hash code.
|
||||
Debug.Assert(_data != null && _data.Length >= (32 / 8));
|
||||
return BitConverter.ToInt32(_data, 0);
|
||||
}
|
||||
|
||||
private static byte[] GenerateNewToken(int bitLength)
|
||||
{
|
||||
var data = new byte[bitLength / 8];
|
||||
_randomNumberGenerator.GetBytes(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
// Need to mark it with NoInlining and NoOptimization attributes to ensure that the
|
||||
// operation runs in constant time.
|
||||
[MethodImplAttribute(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
|
||||
private static bool AreByteArraysEqual(byte[] a, byte[] b)
|
||||
{
|
||||
if (a == null || b == null || a.Length != b.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var areEqual = true;
|
||||
for (var i = 0; i < a.Length; i++)
|
||||
{
|
||||
areEqual &= (a[i] == b[i]);
|
||||
}
|
||||
return areEqual;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.Security.Cryptography;
|
||||
|
||||
namespace Microsoft.AspNetCore.Antiforgery.Internal
|
||||
{
|
||||
public static class CryptographyAlgorithms
|
||||
{
|
||||
public static SHA256 CreateSHA256()
|
||||
{
|
||||
try
|
||||
{
|
||||
return SHA256.Create();
|
||||
}
|
||||
// SHA256.Create is documented to throw this exception on FIPS compliant machines.
|
||||
// See: https://msdn.microsoft.com/en-us/library/z08hz7ad%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396
|
||||
catch (System.Reflection.TargetInvocationException)
|
||||
{
|
||||
// Fallback to a FIPS compliant SHA256 algorithm.
|
||||
return new SHA256CryptoServiceProvider();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,497 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Microsoft.AspNetCore.Antiforgery.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides access to the antiforgery system, which provides protection against
|
||||
/// Cross-site Request Forgery (XSRF, also called CSRF) attacks.
|
||||
/// </summary>
|
||||
public class DefaultAntiforgery : IAntiforgery
|
||||
{
|
||||
private readonly AntiforgeryOptions _options;
|
||||
private readonly IAntiforgeryTokenGenerator _tokenGenerator;
|
||||
private readonly IAntiforgeryTokenSerializer _tokenSerializer;
|
||||
private readonly IAntiforgeryTokenStore _tokenStore;
|
||||
private readonly ILogger<DefaultAntiforgery> _logger;
|
||||
|
||||
public DefaultAntiforgery(
|
||||
IOptions<AntiforgeryOptions> antiforgeryOptionsAccessor,
|
||||
IAntiforgeryTokenGenerator tokenGenerator,
|
||||
IAntiforgeryTokenSerializer tokenSerializer,
|
||||
IAntiforgeryTokenStore tokenStore,
|
||||
ILoggerFactory loggerFactory)
|
||||
{
|
||||
_options = antiforgeryOptionsAccessor.Value;
|
||||
_tokenGenerator = tokenGenerator;
|
||||
_tokenSerializer = tokenSerializer;
|
||||
_tokenStore = tokenStore;
|
||||
_logger = loggerFactory.CreateLogger<DefaultAntiforgery>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public AntiforgeryTokenSet GetAndStoreTokens(HttpContext httpContext)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
CheckSSLConfig(httpContext);
|
||||
|
||||
var antiforgeryFeature = GetTokensInternal(httpContext);
|
||||
var tokenSet = Serialize(antiforgeryFeature);
|
||||
|
||||
if (!antiforgeryFeature.HaveStoredNewCookieToken)
|
||||
{
|
||||
if (antiforgeryFeature.NewCookieToken != null)
|
||||
{
|
||||
// Serialize handles the new cookie token string.
|
||||
Debug.Assert(antiforgeryFeature.NewCookieTokenString != null);
|
||||
|
||||
SaveCookieTokenAndHeader(httpContext, antiforgeryFeature.NewCookieTokenString);
|
||||
antiforgeryFeature.HaveStoredNewCookieToken = true;
|
||||
_logger.NewCookieToken();
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.ReusedCookieToken();
|
||||
}
|
||||
}
|
||||
|
||||
if (!httpContext.Response.HasStarted)
|
||||
{
|
||||
// Explicitly set the cache headers to 'no-cache'. This could override any user set value but this is fine
|
||||
// as a response with antiforgery token must never be cached.
|
||||
SetDoNotCacheHeaders(httpContext);
|
||||
}
|
||||
|
||||
return tokenSet;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public AntiforgeryTokenSet GetTokens(HttpContext httpContext)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
CheckSSLConfig(httpContext);
|
||||
|
||||
var antiforgeryFeature = GetTokensInternal(httpContext);
|
||||
return Serialize(antiforgeryFeature);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> IsRequestValidAsync(HttpContext httpContext)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
CheckSSLConfig(httpContext);
|
||||
|
||||
var method = httpContext.Request.Method;
|
||||
if (string.Equals(method, "GET", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(method, "HEAD", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(method, "OPTIONS", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(method, "TRACE", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Validation not needed for these request types.
|
||||
return true;
|
||||
}
|
||||
|
||||
var tokens = await _tokenStore.GetRequestTokensAsync(httpContext);
|
||||
if (tokens.CookieToken == null)
|
||||
{
|
||||
_logger.MissingCookieToken(_options.Cookie.Name);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tokens.RequestToken == null)
|
||||
{
|
||||
_logger.MissingRequestToken(_options.FormFieldName, _options.HeaderName);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract cookie & request tokens
|
||||
AntiforgeryToken deserializedCookieToken;
|
||||
AntiforgeryToken deserializedRequestToken;
|
||||
if (!TryDeserializeTokens(httpContext, tokens, out deserializedCookieToken, out deserializedRequestToken))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate
|
||||
string message;
|
||||
var result = _tokenGenerator.TryValidateTokenSet(
|
||||
httpContext,
|
||||
deserializedCookieToken,
|
||||
deserializedRequestToken,
|
||||
out message);
|
||||
|
||||
if (result)
|
||||
{
|
||||
_logger.ValidatedAntiforgeryToken();
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.ValidationFailed(message);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task ValidateRequestAsync(HttpContext httpContext)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
CheckSSLConfig(httpContext);
|
||||
|
||||
var tokens = await _tokenStore.GetRequestTokensAsync(httpContext);
|
||||
if (tokens.CookieToken == null)
|
||||
{
|
||||
throw new AntiforgeryValidationException(
|
||||
Resources.FormatAntiforgery_CookieToken_MustBeProvided(_options.Cookie.Name));
|
||||
}
|
||||
|
||||
if (tokens.RequestToken == null)
|
||||
{
|
||||
if (_options.HeaderName == null)
|
||||
{
|
||||
var message = Resources.FormatAntiforgery_FormToken_MustBeProvided(_options.FormFieldName);
|
||||
throw new AntiforgeryValidationException(message);
|
||||
}
|
||||
else if (!httpContext.Request.HasFormContentType)
|
||||
{
|
||||
var message = Resources.FormatAntiforgery_HeaderToken_MustBeProvided(_options.HeaderName);
|
||||
throw new AntiforgeryValidationException(message);
|
||||
}
|
||||
else
|
||||
{
|
||||
var message = Resources.FormatAntiforgery_RequestToken_MustBeProvided(
|
||||
_options.FormFieldName,
|
||||
_options.HeaderName);
|
||||
throw new AntiforgeryValidationException(message);
|
||||
}
|
||||
}
|
||||
|
||||
ValidateTokens(httpContext, tokens);
|
||||
|
||||
_logger.ValidatedAntiforgeryToken();
|
||||
}
|
||||
|
||||
private void ValidateTokens(HttpContext httpContext, AntiforgeryTokenSet antiforgeryTokenSet)
|
||||
{
|
||||
Debug.Assert(!string.IsNullOrEmpty(antiforgeryTokenSet.CookieToken));
|
||||
Debug.Assert(!string.IsNullOrEmpty(antiforgeryTokenSet.RequestToken));
|
||||
|
||||
// Extract cookie & request tokens
|
||||
AntiforgeryToken deserializedCookieToken;
|
||||
AntiforgeryToken deserializedRequestToken;
|
||||
|
||||
DeserializeTokens(
|
||||
httpContext,
|
||||
antiforgeryTokenSet,
|
||||
out deserializedCookieToken,
|
||||
out deserializedRequestToken);
|
||||
|
||||
// Validate
|
||||
string message;
|
||||
if (!_tokenGenerator.TryValidateTokenSet(
|
||||
httpContext,
|
||||
deserializedCookieToken,
|
||||
deserializedRequestToken,
|
||||
out message))
|
||||
{
|
||||
throw new AntiforgeryValidationException(message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetCookieTokenAndHeader(HttpContext httpContext)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
CheckSSLConfig(httpContext);
|
||||
|
||||
var antiforgeryFeature = GetCookieTokens(httpContext);
|
||||
if (!antiforgeryFeature.HaveStoredNewCookieToken && antiforgeryFeature.NewCookieToken != null)
|
||||
{
|
||||
if (antiforgeryFeature.NewCookieTokenString == null)
|
||||
{
|
||||
antiforgeryFeature.NewCookieTokenString =
|
||||
_tokenSerializer.Serialize(antiforgeryFeature.NewCookieToken);
|
||||
}
|
||||
|
||||
SaveCookieTokenAndHeader(httpContext, antiforgeryFeature.NewCookieTokenString);
|
||||
antiforgeryFeature.HaveStoredNewCookieToken = true;
|
||||
_logger.NewCookieToken();
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.ReusedCookieToken();
|
||||
}
|
||||
|
||||
if (!httpContext.Response.HasStarted)
|
||||
{
|
||||
SetDoNotCacheHeaders(httpContext);
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveCookieTokenAndHeader(HttpContext httpContext, string cookieToken)
|
||||
{
|
||||
if (cookieToken != null)
|
||||
{
|
||||
// Persist the new cookie if it is not null.
|
||||
_tokenStore.SaveCookieToken(httpContext, cookieToken);
|
||||
}
|
||||
|
||||
if (!_options.SuppressXFrameOptionsHeader && !httpContext.Response.Headers.ContainsKey("X-Frame-Options"))
|
||||
{
|
||||
// Adding X-Frame-Options header to prevent ClickJacking. See
|
||||
// http://tools.ietf.org/html/draft-ietf-websec-x-frame-options-10
|
||||
// for more information.
|
||||
httpContext.Response.Headers["X-Frame-Options"] = "SAMEORIGIN";
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckSSLConfig(HttpContext context)
|
||||
{
|
||||
if (_options.Cookie.SecurePolicy == CookieSecurePolicy.Always && !context.Request.IsHttps)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.FormatAntiforgery_RequiresSSL(
|
||||
string.Join(".", nameof(AntiforgeryOptions), nameof(AntiforgeryOptions.Cookie), nameof(CookieBuilder.SecurePolicy)),
|
||||
nameof(CookieSecurePolicy.Always)));
|
||||
}
|
||||
}
|
||||
|
||||
private static IAntiforgeryFeature GetAntiforgeryFeature(HttpContext httpContext)
|
||||
{
|
||||
var antiforgeryFeature = httpContext.Features.Get<IAntiforgeryFeature>();
|
||||
if (antiforgeryFeature == null)
|
||||
{
|
||||
antiforgeryFeature = new AntiforgeryFeature();
|
||||
httpContext.Features.Set(antiforgeryFeature);
|
||||
}
|
||||
|
||||
return antiforgeryFeature;
|
||||
}
|
||||
|
||||
private IAntiforgeryFeature GetCookieTokens(HttpContext httpContext)
|
||||
{
|
||||
var antiforgeryFeature = GetAntiforgeryFeature(httpContext);
|
||||
|
||||
if (antiforgeryFeature.HaveGeneratedNewCookieToken)
|
||||
{
|
||||
Debug.Assert(antiforgeryFeature.HaveDeserializedCookieToken);
|
||||
|
||||
// Have executed this method earlier in the context of this request.
|
||||
return antiforgeryFeature;
|
||||
}
|
||||
|
||||
AntiforgeryToken cookieToken;
|
||||
if (antiforgeryFeature.HaveDeserializedCookieToken)
|
||||
{
|
||||
cookieToken = antiforgeryFeature.CookieToken;
|
||||
}
|
||||
else
|
||||
{
|
||||
cookieToken = GetCookieTokenDoesNotThrow(httpContext);
|
||||
|
||||
antiforgeryFeature.CookieToken = cookieToken;
|
||||
antiforgeryFeature.HaveDeserializedCookieToken = true;
|
||||
}
|
||||
|
||||
AntiforgeryToken newCookieToken;
|
||||
if (_tokenGenerator.IsCookieTokenValid(cookieToken))
|
||||
{
|
||||
// No need for the cookie token from the request after it has been verified.
|
||||
newCookieToken = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Need to make sure we're always operating with a good cookie token.
|
||||
newCookieToken = _tokenGenerator.GenerateCookieToken();
|
||||
Debug.Assert(_tokenGenerator.IsCookieTokenValid(newCookieToken));
|
||||
}
|
||||
|
||||
antiforgeryFeature.HaveGeneratedNewCookieToken = true;
|
||||
antiforgeryFeature.NewCookieToken = newCookieToken;
|
||||
|
||||
return antiforgeryFeature;
|
||||
}
|
||||
|
||||
private AntiforgeryToken GetCookieTokenDoesNotThrow(HttpContext httpContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
var serializedToken = _tokenStore.GetCookieToken(httpContext);
|
||||
|
||||
if (serializedToken != null)
|
||||
{
|
||||
var token = _tokenSerializer.Deserialize(serializedToken);
|
||||
return token;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// ignore failures since we'll just generate a new token
|
||||
_logger.TokenDeserializeException(ex);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private IAntiforgeryFeature GetTokensInternal(HttpContext httpContext)
|
||||
{
|
||||
var antiforgeryFeature = GetCookieTokens(httpContext);
|
||||
if (antiforgeryFeature.NewRequestToken == null)
|
||||
{
|
||||
var cookieToken = antiforgeryFeature.NewCookieToken ?? antiforgeryFeature.CookieToken;
|
||||
antiforgeryFeature.NewRequestToken = _tokenGenerator.GenerateRequestToken(
|
||||
httpContext,
|
||||
cookieToken);
|
||||
}
|
||||
|
||||
return antiforgeryFeature;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the 'Cache-Control' header to 'no-cache, no-store' and 'Pragma' header to 'no-cache' overriding any user set value.
|
||||
/// </summary>
|
||||
/// <param name="httpContext">The <see cref="HttpContext"/>.</param>
|
||||
protected virtual void SetDoNotCacheHeaders(HttpContext httpContext)
|
||||
{
|
||||
// Since antifogery token generation is not very obvious to the end users (ex: MVC's form tag generates them
|
||||
// by default), log a warning to let users know of the change in behavior to any cache headers they might
|
||||
// have set explicitly.
|
||||
LogCacheHeaderOverrideWarning(httpContext.Response);
|
||||
|
||||
httpContext.Response.Headers[HeaderNames.CacheControl] = "no-cache, no-store";
|
||||
httpContext.Response.Headers[HeaderNames.Pragma] = "no-cache";
|
||||
}
|
||||
|
||||
private void LogCacheHeaderOverrideWarning(HttpResponse response)
|
||||
{
|
||||
var logWarning = false;
|
||||
CacheControlHeaderValue cacheControlHeaderValue;
|
||||
if (CacheControlHeaderValue.TryParse(response.Headers[HeaderNames.CacheControl].ToString(), out cacheControlHeaderValue))
|
||||
{
|
||||
if (!cacheControlHeaderValue.NoCache)
|
||||
{
|
||||
logWarning = true;
|
||||
}
|
||||
}
|
||||
|
||||
var pragmaHeader = response.Headers[HeaderNames.Pragma];
|
||||
if (!logWarning
|
||||
&& !string.IsNullOrEmpty(pragmaHeader)
|
||||
&& string.Compare(pragmaHeader, "no-cache", ignoreCase: true) != 0)
|
||||
{
|
||||
logWarning = true;
|
||||
}
|
||||
|
||||
if (logWarning)
|
||||
{
|
||||
_logger.ResponseCacheHeadersOverridenToNoCache();
|
||||
}
|
||||
}
|
||||
|
||||
private AntiforgeryTokenSet Serialize(IAntiforgeryFeature antiforgeryFeature)
|
||||
{
|
||||
// Should only be called after new tokens have been generated.
|
||||
Debug.Assert(antiforgeryFeature.HaveGeneratedNewCookieToken);
|
||||
Debug.Assert(antiforgeryFeature.NewRequestToken != null);
|
||||
|
||||
if (antiforgeryFeature.NewRequestTokenString == null)
|
||||
{
|
||||
antiforgeryFeature.NewRequestTokenString =
|
||||
_tokenSerializer.Serialize(antiforgeryFeature.NewRequestToken);
|
||||
}
|
||||
|
||||
if (antiforgeryFeature.NewCookieTokenString == null && antiforgeryFeature.NewCookieToken != null)
|
||||
{
|
||||
antiforgeryFeature.NewCookieTokenString =
|
||||
_tokenSerializer.Serialize(antiforgeryFeature.NewCookieToken);
|
||||
}
|
||||
|
||||
return new AntiforgeryTokenSet(
|
||||
antiforgeryFeature.NewRequestTokenString,
|
||||
antiforgeryFeature.NewCookieTokenString,
|
||||
_options.FormFieldName,
|
||||
_options.HeaderName);
|
||||
}
|
||||
|
||||
private bool TryDeserializeTokens(
|
||||
HttpContext httpContext,
|
||||
AntiforgeryTokenSet antiforgeryTokenSet,
|
||||
out AntiforgeryToken cookieToken,
|
||||
out AntiforgeryToken requestToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
DeserializeTokens(httpContext, antiforgeryTokenSet, out cookieToken, out requestToken);
|
||||
return true;
|
||||
}
|
||||
catch (AntiforgeryValidationException ex)
|
||||
{
|
||||
_logger.FailedToDeserialzeTokens(ex);
|
||||
|
||||
cookieToken = null;
|
||||
requestToken = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void DeserializeTokens(
|
||||
HttpContext httpContext,
|
||||
AntiforgeryTokenSet antiforgeryTokenSet,
|
||||
out AntiforgeryToken cookieToken,
|
||||
out AntiforgeryToken requestToken)
|
||||
{
|
||||
var antiforgeryFeature = GetAntiforgeryFeature(httpContext);
|
||||
|
||||
if (antiforgeryFeature.HaveDeserializedCookieToken)
|
||||
{
|
||||
cookieToken = antiforgeryFeature.CookieToken;
|
||||
}
|
||||
else
|
||||
{
|
||||
cookieToken = _tokenSerializer.Deserialize(antiforgeryTokenSet.CookieToken);
|
||||
|
||||
antiforgeryFeature.CookieToken = cookieToken;
|
||||
antiforgeryFeature.HaveDeserializedCookieToken = true;
|
||||
}
|
||||
|
||||
if (antiforgeryFeature.HaveDeserializedRequestToken)
|
||||
{
|
||||
requestToken = antiforgeryFeature.RequestToken;
|
||||
}
|
||||
else
|
||||
{
|
||||
requestToken = _tokenSerializer.Deserialize(antiforgeryTokenSet.RequestToken);
|
||||
|
||||
antiforgeryFeature.RequestToken = requestToken;
|
||||
antiforgeryFeature.HaveDeserializedRequestToken = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
// 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.Antiforgery.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// A default <see cref="IAntiforgeryAdditionalDataProvider"/> implementation.
|
||||
/// </summary>
|
||||
public class DefaultAntiforgeryAdditionalDataProvider : IAntiforgeryAdditionalDataProvider
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public virtual string GetAdditionalData(HttpContext context)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual bool ValidateAdditionalData(HttpContext context, string additionalData)
|
||||
{
|
||||
// Default implementation does not understand anything but empty data.
|
||||
return string.IsNullOrEmpty(additionalData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,238 @@
|
|||
// 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.Security.Claims;
|
||||
using System.Security.Principal;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Antiforgery.Internal
|
||||
{
|
||||
public class DefaultAntiforgeryTokenGenerator : IAntiforgeryTokenGenerator
|
||||
{
|
||||
private readonly IClaimUidExtractor _claimUidExtractor;
|
||||
private readonly IAntiforgeryAdditionalDataProvider _additionalDataProvider;
|
||||
|
||||
public DefaultAntiforgeryTokenGenerator(
|
||||
IClaimUidExtractor claimUidExtractor,
|
||||
IAntiforgeryAdditionalDataProvider additionalDataProvider)
|
||||
{
|
||||
_claimUidExtractor = claimUidExtractor;
|
||||
_additionalDataProvider = additionalDataProvider;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public AntiforgeryToken GenerateCookieToken()
|
||||
{
|
||||
return new AntiforgeryToken()
|
||||
{
|
||||
// SecurityToken will be populated automatically.
|
||||
IsCookieToken = true
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public AntiforgeryToken GenerateRequestToken(
|
||||
HttpContext httpContext,
|
||||
AntiforgeryToken cookieToken)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (cookieToken == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(cookieToken));
|
||||
}
|
||||
|
||||
if (!IsCookieTokenValid(cookieToken))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
Resources.Antiforgery_CookieToken_IsInvalid,
|
||||
nameof(cookieToken));
|
||||
}
|
||||
|
||||
var requestToken = new AntiforgeryToken()
|
||||
{
|
||||
SecurityToken = cookieToken.SecurityToken,
|
||||
IsCookieToken = false
|
||||
};
|
||||
|
||||
var isIdentityAuthenticated = false;
|
||||
|
||||
// populate Username and ClaimUid
|
||||
var authenticatedIdentity = GetAuthenticatedIdentity(httpContext.User);
|
||||
if (authenticatedIdentity != null)
|
||||
{
|
||||
isIdentityAuthenticated = true;
|
||||
requestToken.ClaimUid = GetClaimUidBlob(_claimUidExtractor.ExtractClaimUid(httpContext.User));
|
||||
|
||||
if (requestToken.ClaimUid == null)
|
||||
{
|
||||
requestToken.Username = authenticatedIdentity.Name;
|
||||
}
|
||||
}
|
||||
|
||||
// populate AdditionalData
|
||||
if (_additionalDataProvider != null)
|
||||
{
|
||||
requestToken.AdditionalData = _additionalDataProvider.GetAdditionalData(httpContext);
|
||||
}
|
||||
|
||||
if (isIdentityAuthenticated
|
||||
&& string.IsNullOrEmpty(requestToken.Username)
|
||||
&& requestToken.ClaimUid == null
|
||||
&& string.IsNullOrEmpty(requestToken.AdditionalData))
|
||||
{
|
||||
// Application says user is authenticated, but we have no identifier for the user.
|
||||
throw new InvalidOperationException(
|
||||
Resources.FormatAntiforgeryTokenValidator_AuthenticatedUserWithoutUsername(
|
||||
authenticatedIdentity.GetType(),
|
||||
nameof(IIdentity.IsAuthenticated),
|
||||
"true",
|
||||
nameof(IIdentity.Name),
|
||||
nameof(IAntiforgeryAdditionalDataProvider),
|
||||
nameof(DefaultAntiforgeryAdditionalDataProvider)));
|
||||
}
|
||||
|
||||
return requestToken;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsCookieTokenValid(AntiforgeryToken cookieToken)
|
||||
{
|
||||
return cookieToken != null && cookieToken.IsCookieToken;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryValidateTokenSet(
|
||||
HttpContext httpContext,
|
||||
AntiforgeryToken cookieToken,
|
||||
AntiforgeryToken requestToken,
|
||||
out string message)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (cookieToken == null)
|
||||
{
|
||||
throw new ArgumentNullException(
|
||||
nameof(cookieToken),
|
||||
Resources.Antiforgery_CookieToken_MustBeProvided_Generic);
|
||||
}
|
||||
|
||||
if (requestToken == null)
|
||||
{
|
||||
throw new ArgumentNullException(
|
||||
nameof(requestToken),
|
||||
Resources.Antiforgery_RequestToken_MustBeProvided_Generic);
|
||||
}
|
||||
|
||||
// Do the tokens have the correct format?
|
||||
if (!cookieToken.IsCookieToken || requestToken.IsCookieToken)
|
||||
{
|
||||
message = Resources.AntiforgeryToken_TokensSwapped;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Are the security tokens embedded in each incoming token identical?
|
||||
if (!object.Equals(cookieToken.SecurityToken, requestToken.SecurityToken))
|
||||
{
|
||||
message = Resources.AntiforgeryToken_SecurityTokenMismatch;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Is the incoming token meant for the current user?
|
||||
var currentUsername = string.Empty;
|
||||
BinaryBlob currentClaimUid = null;
|
||||
|
||||
var authenticatedIdentity = GetAuthenticatedIdentity(httpContext.User);
|
||||
if (authenticatedIdentity != null)
|
||||
{
|
||||
currentClaimUid = GetClaimUidBlob(_claimUidExtractor.ExtractClaimUid(httpContext.User));
|
||||
if (currentClaimUid == null)
|
||||
{
|
||||
currentUsername = authenticatedIdentity.Name ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
// OpenID and other similar authentication schemes use URIs for the username.
|
||||
// These should be treated as case-sensitive.
|
||||
var comparer = StringComparer.OrdinalIgnoreCase;
|
||||
if (currentUsername.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ||
|
||||
currentUsername.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
comparer = StringComparer.Ordinal;
|
||||
}
|
||||
|
||||
if (!comparer.Equals(requestToken.Username, currentUsername))
|
||||
{
|
||||
message = Resources.FormatAntiforgeryToken_UsernameMismatch(requestToken.Username, currentUsername);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!object.Equals(requestToken.ClaimUid, currentClaimUid))
|
||||
{
|
||||
message = Resources.AntiforgeryToken_ClaimUidMismatch;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Is the AdditionalData valid?
|
||||
if (_additionalDataProvider != null &&
|
||||
!_additionalDataProvider.ValidateAdditionalData(httpContext, requestToken.AdditionalData))
|
||||
{
|
||||
message = Resources.AntiforgeryToken_AdditionalDataCheckFailed;
|
||||
return false;
|
||||
}
|
||||
|
||||
message = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static BinaryBlob GetClaimUidBlob(string base64ClaimUid)
|
||||
{
|
||||
if (base64ClaimUid == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new BinaryBlob(256, Convert.FromBase64String(base64ClaimUid));
|
||||
}
|
||||
|
||||
private static ClaimsIdentity GetAuthenticatedIdentity(ClaimsPrincipal claimsPrincipal)
|
||||
{
|
||||
if (claimsPrincipal == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var identitiesList = claimsPrincipal.Identities as List<ClaimsIdentity>;
|
||||
if (identitiesList != null)
|
||||
{
|
||||
for (var i = 0; i < identitiesList.Count; i++)
|
||||
{
|
||||
if (identitiesList[i].IsAuthenticated)
|
||||
{
|
||||
return identitiesList[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var identity in claimsPrincipal.Identities)
|
||||
{
|
||||
if (identity.IsAuthenticated)
|
||||
{
|
||||
return identity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,188 @@
|
|||
// 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.IO;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
|
||||
namespace Microsoft.AspNetCore.Antiforgery.Internal
|
||||
{
|
||||
public class DefaultAntiforgeryTokenSerializer : IAntiforgeryTokenSerializer
|
||||
{
|
||||
private static readonly string Purpose = "Microsoft.AspNetCore.Antiforgery.AntiforgeryToken.v1";
|
||||
private const byte TokenVersion = 0x01;
|
||||
|
||||
private readonly IDataProtector _cryptoSystem;
|
||||
private readonly ObjectPool<AntiforgerySerializationContext> _pool;
|
||||
|
||||
public DefaultAntiforgeryTokenSerializer(
|
||||
IDataProtectionProvider provider,
|
||||
ObjectPool<AntiforgerySerializationContext> pool)
|
||||
{
|
||||
if (provider == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(provider));
|
||||
}
|
||||
|
||||
if (pool == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(pool));
|
||||
}
|
||||
|
||||
_cryptoSystem = provider.CreateProtector(Purpose);
|
||||
_pool = pool;
|
||||
}
|
||||
|
||||
public AntiforgeryToken Deserialize(string serializedToken)
|
||||
{
|
||||
var serializationContext = _pool.Get();
|
||||
|
||||
Exception innerException = null;
|
||||
try
|
||||
{
|
||||
var count = serializedToken.Length;
|
||||
var charsRequired = WebEncoders.GetArraySizeRequiredToDecode(count);
|
||||
var chars = serializationContext.GetChars(charsRequired);
|
||||
var tokenBytes = WebEncoders.Base64UrlDecode(
|
||||
serializedToken,
|
||||
offset: 0,
|
||||
buffer: chars,
|
||||
bufferOffset: 0,
|
||||
count: count);
|
||||
|
||||
var unprotectedBytes = _cryptoSystem.Unprotect(tokenBytes);
|
||||
var stream = serializationContext.Stream;
|
||||
stream.Write(unprotectedBytes, offset: 0, count: unprotectedBytes.Length);
|
||||
stream.Position = 0L;
|
||||
|
||||
var reader = serializationContext.Reader;
|
||||
var token = Deserialize(reader);
|
||||
if (token != null)
|
||||
{
|
||||
return token;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// swallow all exceptions - homogenize error if something went wrong
|
||||
innerException = ex;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_pool.Return(serializationContext);
|
||||
}
|
||||
|
||||
// if we reached this point, something went wrong deserializing
|
||||
throw new AntiforgeryValidationException(Resources.AntiforgeryToken_DeserializationFailed, innerException);
|
||||
}
|
||||
|
||||
/* The serialized format of the anti-XSRF token is as follows:
|
||||
* Version: 1 byte integer
|
||||
* SecurityToken: 16 byte binary blob
|
||||
* IsCookieToken: 1 byte Boolean
|
||||
* [if IsCookieToken != true]
|
||||
* +- IsClaimsBased: 1 byte Boolean
|
||||
* | [if IsClaimsBased = true]
|
||||
* | `- ClaimUid: 32 byte binary blob
|
||||
* | [if IsClaimsBased = false]
|
||||
* | `- Username: UTF-8 string with 7-bit integer length prefix
|
||||
* `- AdditionalData: UTF-8 string with 7-bit integer length prefix
|
||||
*/
|
||||
private static AntiforgeryToken Deserialize(BinaryReader reader)
|
||||
{
|
||||
// we can only consume tokens of the same serialized version that we generate
|
||||
var embeddedVersion = reader.ReadByte();
|
||||
if (embeddedVersion != TokenVersion)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var deserializedToken = new AntiforgeryToken();
|
||||
var securityTokenBytes = reader.ReadBytes(AntiforgeryToken.SecurityTokenBitLength / 8);
|
||||
deserializedToken.SecurityToken =
|
||||
new BinaryBlob(AntiforgeryToken.SecurityTokenBitLength, securityTokenBytes);
|
||||
deserializedToken.IsCookieToken = reader.ReadBoolean();
|
||||
|
||||
if (!deserializedToken.IsCookieToken)
|
||||
{
|
||||
var isClaimsBased = reader.ReadBoolean();
|
||||
if (isClaimsBased)
|
||||
{
|
||||
var claimUidBytes = reader.ReadBytes(AntiforgeryToken.ClaimUidBitLength / 8);
|
||||
deserializedToken.ClaimUid = new BinaryBlob(AntiforgeryToken.ClaimUidBitLength, claimUidBytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
deserializedToken.Username = reader.ReadString();
|
||||
}
|
||||
|
||||
deserializedToken.AdditionalData = reader.ReadString();
|
||||
}
|
||||
|
||||
// if there's still unconsumed data in the stream, fail
|
||||
if (reader.BaseStream.ReadByte() != -1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// success
|
||||
return deserializedToken;
|
||||
}
|
||||
|
||||
public string Serialize(AntiforgeryToken token)
|
||||
{
|
||||
if (token == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(token));
|
||||
}
|
||||
|
||||
var serializationContext = _pool.Get();
|
||||
|
||||
try
|
||||
{
|
||||
var writer = serializationContext.Writer;
|
||||
writer.Write(TokenVersion);
|
||||
writer.Write(token.SecurityToken.GetData());
|
||||
writer.Write(token.IsCookieToken);
|
||||
|
||||
if (!token.IsCookieToken)
|
||||
{
|
||||
if (token.ClaimUid != null)
|
||||
{
|
||||
writer.Write(true /* isClaimsBased */);
|
||||
writer.Write(token.ClaimUid.GetData());
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.Write(false /* isClaimsBased */);
|
||||
writer.Write(token.Username);
|
||||
}
|
||||
|
||||
writer.Write(token.AdditionalData);
|
||||
}
|
||||
|
||||
writer.Flush();
|
||||
var stream = serializationContext.Stream;
|
||||
var bytes = _cryptoSystem.Protect(stream.ToArray());
|
||||
|
||||
var count = bytes.Length;
|
||||
var charsRequired = WebEncoders.GetArraySizeRequiredToEncode(count);
|
||||
var chars = serializationContext.GetChars(charsRequired);
|
||||
var outputLength = WebEncoders.Base64UrlEncode(
|
||||
bytes,
|
||||
offset: 0,
|
||||
output: chars,
|
||||
outputOffset: 0,
|
||||
count: count);
|
||||
|
||||
return new string(chars, startIndex: 0, length: outputLength);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_pool.Return(serializationContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.Antiforgery.Internal
|
||||
{
|
||||
public class DefaultAntiforgeryTokenStore : IAntiforgeryTokenStore
|
||||
{
|
||||
private readonly AntiforgeryOptions _options;
|
||||
|
||||
public DefaultAntiforgeryTokenStore(IOptions<AntiforgeryOptions> optionsAccessor)
|
||||
{
|
||||
if (optionsAccessor == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(optionsAccessor));
|
||||
}
|
||||
|
||||
_options = optionsAccessor.Value;
|
||||
}
|
||||
|
||||
public string GetCookieToken(HttpContext httpContext)
|
||||
{
|
||||
Debug.Assert(httpContext != null);
|
||||
|
||||
var requestCookie = httpContext.Request.Cookies[_options.Cookie.Name];
|
||||
if (string.IsNullOrEmpty(requestCookie))
|
||||
{
|
||||
// unable to find the cookie.
|
||||
return null;
|
||||
}
|
||||
|
||||
return requestCookie;
|
||||
}
|
||||
|
||||
public async Task<AntiforgeryTokenSet> GetRequestTokensAsync(HttpContext httpContext)
|
||||
{
|
||||
Debug.Assert(httpContext != null);
|
||||
|
||||
var cookieToken = httpContext.Request.Cookies[_options.Cookie.Name];
|
||||
|
||||
// We want to delay reading the form as much as possible, for example in case of large file uploads,
|
||||
// request token could be part of the header.
|
||||
StringValues requestToken;
|
||||
if (_options.HeaderName != null)
|
||||
{
|
||||
requestToken = httpContext.Request.Headers[_options.HeaderName];
|
||||
}
|
||||
|
||||
// Fall back to reading form instead
|
||||
if (requestToken.Count == 0 && httpContext.Request.HasFormContentType)
|
||||
{
|
||||
// Check the content-type before accessing the form collection to make sure
|
||||
// we report errors gracefully.
|
||||
var form = await httpContext.Request.ReadFormAsync();
|
||||
requestToken = form[_options.FormFieldName];
|
||||
}
|
||||
|
||||
return new AntiforgeryTokenSet(requestToken, cookieToken, _options.FormFieldName, _options.HeaderName);
|
||||
}
|
||||
|
||||
public void SaveCookieToken(HttpContext httpContext, string token)
|
||||
{
|
||||
Debug.Assert(httpContext != null);
|
||||
Debug.Assert(token != null);
|
||||
|
||||
var options = _options.Cookie.Build(httpContext);
|
||||
|
||||
if (_options.Cookie.Path != null)
|
||||
{
|
||||
options.Path = _options.Cookie.Path.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
var pathBase = httpContext.Request.PathBase.ToString();
|
||||
if (!string.IsNullOrEmpty(pathBase))
|
||||
{
|
||||
options.Path = pathBase;
|
||||
}
|
||||
}
|
||||
|
||||
httpContext.Response.Cookies.Append(_options.Cookie.Name, token, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
// 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.Diagnostics;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
|
||||
namespace Microsoft.AspNetCore.Antiforgery.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of <see cref="IClaimUidExtractor"/>.
|
||||
/// </summary>
|
||||
public class DefaultClaimUidExtractor : IClaimUidExtractor
|
||||
{
|
||||
private readonly ObjectPool<AntiforgerySerializationContext> _pool;
|
||||
|
||||
public DefaultClaimUidExtractor(ObjectPool<AntiforgerySerializationContext> pool)
|
||||
{
|
||||
_pool = pool;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string ExtractClaimUid(ClaimsPrincipal claimsPrincipal)
|
||||
{
|
||||
Debug.Assert(claimsPrincipal != null);
|
||||
|
||||
var uniqueIdentifierParameters = GetUniqueIdentifierParameters(claimsPrincipal.Identities);
|
||||
if (uniqueIdentifierParameters == null)
|
||||
{
|
||||
// No authenticated identities containing claims found.
|
||||
return null;
|
||||
}
|
||||
|
||||
var claimUidBytes = ComputeSha256(uniqueIdentifierParameters);
|
||||
return Convert.ToBase64String(claimUidBytes);
|
||||
}
|
||||
|
||||
public static IList<string> GetUniqueIdentifierParameters(IEnumerable<ClaimsIdentity> claimsIdentities)
|
||||
{
|
||||
var identitiesList = claimsIdentities as List<ClaimsIdentity>;
|
||||
if (identitiesList == null)
|
||||
{
|
||||
identitiesList = new List<ClaimsIdentity>(claimsIdentities);
|
||||
}
|
||||
|
||||
for (var i = 0; i < identitiesList.Count; i++)
|
||||
{
|
||||
var identity = identitiesList[i];
|
||||
if (!identity.IsAuthenticated)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var subClaim = identity.FindFirst(
|
||||
claim => string.Equals("sub", claim.Type, StringComparison.Ordinal));
|
||||
if (subClaim != null && !string.IsNullOrEmpty(subClaim.Value))
|
||||
{
|
||||
return new string[]
|
||||
{
|
||||
subClaim.Type,
|
||||
subClaim.Value,
|
||||
subClaim.Issuer
|
||||
};
|
||||
}
|
||||
|
||||
var nameIdentifierClaim = identity.FindFirst(
|
||||
claim => string.Equals(ClaimTypes.NameIdentifier, claim.Type, StringComparison.Ordinal));
|
||||
if (nameIdentifierClaim != null && !string.IsNullOrEmpty(nameIdentifierClaim.Value))
|
||||
{
|
||||
return new string[]
|
||||
{
|
||||
nameIdentifierClaim.Type,
|
||||
nameIdentifierClaim.Value,
|
||||
nameIdentifierClaim.Issuer
|
||||
};
|
||||
}
|
||||
|
||||
var upnClaim = identity.FindFirst(
|
||||
claim => string.Equals(ClaimTypes.Upn, claim.Type, StringComparison.Ordinal));
|
||||
if (upnClaim != null && !string.IsNullOrEmpty(upnClaim.Value))
|
||||
{
|
||||
return new string[]
|
||||
{
|
||||
upnClaim.Type,
|
||||
upnClaim.Value,
|
||||
upnClaim.Issuer
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// We do not understand any of the ClaimsIdentity instances, fallback on serializing all claims in every claims Identity.
|
||||
var allClaims = new List<Claim>();
|
||||
for (var i = 0; i < identitiesList.Count; i++)
|
||||
{
|
||||
if (identitiesList[i].IsAuthenticated)
|
||||
{
|
||||
allClaims.AddRange(identitiesList[i].Claims);
|
||||
}
|
||||
}
|
||||
|
||||
if (allClaims.Count == 0)
|
||||
{
|
||||
// No authenticated identities containing claims found.
|
||||
return null;
|
||||
}
|
||||
|
||||
allClaims.Sort((a, b) => string.Compare(a.Type, b.Type, StringComparison.Ordinal));
|
||||
|
||||
var identifierParameters = new List<string>(allClaims.Count * 3);
|
||||
for (var i = 0; i < allClaims.Count; i++)
|
||||
{
|
||||
var claim = allClaims[i];
|
||||
identifierParameters.Add(claim.Type);
|
||||
identifierParameters.Add(claim.Value);
|
||||
identifierParameters.Add(claim.Issuer);
|
||||
}
|
||||
|
||||
return identifierParameters;
|
||||
}
|
||||
|
||||
private byte[] ComputeSha256(IEnumerable<string> parameters)
|
||||
{
|
||||
var serializationContext = _pool.Get();
|
||||
|
||||
try
|
||||
{
|
||||
var writer = serializationContext.Writer;
|
||||
foreach (string parameter in parameters)
|
||||
{
|
||||
writer.Write(parameter); // also writes the length as a prefix; unambiguous
|
||||
}
|
||||
|
||||
writer.Flush();
|
||||
|
||||
var sha256 = serializationContext.Sha256;
|
||||
var stream = serializationContext.Stream;
|
||||
var bytes = sha256.ComputeHash(stream.ToArray(), 0, checked((int)stream.Length));
|
||||
|
||||
return bytes;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_pool.Return(serializationContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
namespace Microsoft.AspNetCore.Antiforgery.Internal
|
||||
{
|
||||
public interface IAntiforgeryFeature
|
||||
{
|
||||
AntiforgeryToken CookieToken { get; set; }
|
||||
|
||||
bool HaveDeserializedCookieToken { get; set; }
|
||||
|
||||
bool HaveDeserializedRequestToken { get; set; }
|
||||
|
||||
bool HaveGeneratedNewCookieToken { get; set; }
|
||||
|
||||
bool HaveStoredNewCookieToken { get; set; }
|
||||
|
||||
AntiforgeryToken NewCookieToken { get; set; }
|
||||
|
||||
string NewCookieTokenString { get; set; }
|
||||
|
||||
AntiforgeryToken NewRequestToken { get; set; }
|
||||
|
||||
string NewRequestTokenString { get; set; }
|
||||
|
||||
AntiforgeryToken RequestToken { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
// 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.Antiforgery.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates and validates antiforgery tokens.
|
||||
/// </summary>
|
||||
public interface IAntiforgeryTokenGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates a new random cookie token.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="AntiforgeryToken"/>.</returns>
|
||||
AntiforgeryToken GenerateCookieToken();
|
||||
|
||||
/// <summary>
|
||||
/// Generates a request token corresponding to <paramref name="cookieToken"/>.
|
||||
/// </summary>
|
||||
/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
|
||||
/// <param name="cookieToken">A valid cookie token.</param>
|
||||
/// <returns>An <see cref="AntiforgeryToken"/>.</returns>
|
||||
AntiforgeryToken GenerateRequestToken(HttpContext httpContext, AntiforgeryToken cookieToken);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to validate a cookie token.
|
||||
/// </summary>
|
||||
/// <param name="cookieToken">A valid cookie token.</param>
|
||||
/// <returns><c>true</c> if the cookie token is valid, otherwise <c>false</c>.</returns>
|
||||
bool IsCookieTokenValid(AntiforgeryToken cookieToken);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to validate a cookie and request token set for the given <paramref name="httpContext"/>.
|
||||
/// </summary>
|
||||
/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
|
||||
/// <param name="cookieToken">A cookie token.</param>
|
||||
/// <param name="requestToken">A request token.</param>
|
||||
/// <param name="message">
|
||||
/// Will be set to the validation message if the tokens are invalid, otherwise <c>null</c>.
|
||||
/// </param>
|
||||
/// <returns><c>true</c> if the tokens are valid, otherwise <c>false</c>.</returns>
|
||||
bool TryValidateTokenSet(
|
||||
HttpContext httpContext,
|
||||
AntiforgeryToken cookieToken,
|
||||
AntiforgeryToken requestToken,
|
||||
out string message);
|
||||
}
|
||||
}
|
||||
|
|
@ -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.Antiforgery.Internal
|
||||
{
|
||||
// Abstracts out the serialization process for an antiforgery token
|
||||
public interface IAntiforgeryTokenSerializer
|
||||
{
|
||||
AntiforgeryToken Deserialize(string serializedToken);
|
||||
string Serialize(AntiforgeryToken token);
|
||||
}
|
||||
}
|
||||
|
|
@ -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.Antiforgery.Internal
|
||||
{
|
||||
public interface IAntiforgeryTokenStore
|
||||
{
|
||||
string GetCookieToken(HttpContext httpContext);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the cookie and request tokens from the request.
|
||||
/// </summary>
|
||||
/// <param name="httpContext">The <see cref="HttpContext"/> for the current request.</param>
|
||||
/// <returns>The <see cref="AntiforgeryTokenSet"/>.</returns>
|
||||
Task<AntiforgeryTokenSet> GetRequestTokensAsync(HttpContext httpContext);
|
||||
|
||||
void SaveCookieToken(HttpContext httpContext, string token);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace Microsoft.AspNetCore.Antiforgery.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// This interface can extract unique identifers for a <see cref="ClaimsPrincipal"/>.
|
||||
/// </summary>
|
||||
public interface IClaimUidExtractor
|
||||
{
|
||||
/// <summary>
|
||||
/// Extracts claims identifier.
|
||||
/// </summary>
|
||||
/// <param name="claimsPrincipal">The <see cref="ClaimsPrincipal"/>.</param>
|
||||
/// <returns>The claims identifier.</returns>
|
||||
string ExtractClaimUid(ClaimsPrincipal claimsPrincipal);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>An antiforgery system for ASP.NET Core designed to generate and validate tokens to prevent Cross-Site Request Forgery attacks.</Description>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<PackageTags>aspnetcore;antiforgery</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.DataProtection" Version="$(MicrosoftAspNetCoreDataProtectionPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="$(MicrosoftAspNetCoreHttpAbstractionsPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="$(MicrosoftAspNetCoreHttpExtensionsPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="$(MicrosoftAspNetCoreWebUtilitiesPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="$(MicrosoftExtensionsObjectPoolPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
// 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("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
|
||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Antiforgery.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
254
src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Properties/Resources.Designer.cs
generated
Normal file
254
src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery/Properties/Resources.Designer.cs
generated
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
// <auto-generated />
|
||||
namespace Microsoft.AspNetCore.Antiforgery
|
||||
{
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Resources;
|
||||
|
||||
internal static class Resources
|
||||
{
|
||||
private static readonly ResourceManager _resourceManager
|
||||
= new ResourceManager("Microsoft.AspNetCore.Antiforgery.Resources", typeof(Resources).GetTypeInfo().Assembly);
|
||||
|
||||
/// <summary>
|
||||
/// The provided identity of type '{0}' is marked {1} = {2} but does not have a value for {3}. By default, the antiforgery system requires that all authenticated identities have a unique {3}. If it is not possible to provide a unique {3} for this identity, consider extending {4} by overriding the {5} or a custom type that can provide some form of unique identifier for the current user.
|
||||
/// </summary>
|
||||
internal static string AntiforgeryTokenValidator_AuthenticatedUserWithoutUsername
|
||||
{
|
||||
get => GetString("AntiforgeryTokenValidator_AuthenticatedUserWithoutUsername");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The provided identity of type '{0}' is marked {1} = {2} but does not have a value for {3}. By default, the antiforgery system requires that all authenticated identities have a unique {3}. If it is not possible to provide a unique {3} for this identity, consider extending {4} by overriding the {5} or a custom type that can provide some form of unique identifier for the current user.
|
||||
/// </summary>
|
||||
internal static string FormatAntiforgeryTokenValidator_AuthenticatedUserWithoutUsername(object p0, object p1, object p2, object p3, object p4, object p5)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("AntiforgeryTokenValidator_AuthenticatedUserWithoutUsername"), p0, p1, p2, p3, p4, p5);
|
||||
|
||||
/// <summary>
|
||||
/// The provided antiforgery token failed a custom data check.
|
||||
/// </summary>
|
||||
internal static string AntiforgeryToken_AdditionalDataCheckFailed
|
||||
{
|
||||
get => GetString("AntiforgeryToken_AdditionalDataCheckFailed");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The provided antiforgery token failed a custom data check.
|
||||
/// </summary>
|
||||
internal static string FormatAntiforgeryToken_AdditionalDataCheckFailed()
|
||||
=> GetString("AntiforgeryToken_AdditionalDataCheckFailed");
|
||||
|
||||
/// <summary>
|
||||
/// The provided antiforgery token was meant for a different claims-based user than the current user.
|
||||
/// </summary>
|
||||
internal static string AntiforgeryToken_ClaimUidMismatch
|
||||
{
|
||||
get => GetString("AntiforgeryToken_ClaimUidMismatch");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The provided antiforgery token was meant for a different claims-based user than the current user.
|
||||
/// </summary>
|
||||
internal static string FormatAntiforgeryToken_ClaimUidMismatch()
|
||||
=> GetString("AntiforgeryToken_ClaimUidMismatch");
|
||||
|
||||
/// <summary>
|
||||
/// The antiforgery token could not be decrypted.
|
||||
/// </summary>
|
||||
internal static string AntiforgeryToken_DeserializationFailed
|
||||
{
|
||||
get => GetString("AntiforgeryToken_DeserializationFailed");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The antiforgery token could not be decrypted.
|
||||
/// </summary>
|
||||
internal static string FormatAntiforgeryToken_DeserializationFailed()
|
||||
=> GetString("AntiforgeryToken_DeserializationFailed");
|
||||
|
||||
/// <summary>
|
||||
/// The antiforgery cookie token and request token do not match.
|
||||
/// </summary>
|
||||
internal static string AntiforgeryToken_SecurityTokenMismatch
|
||||
{
|
||||
get => GetString("AntiforgeryToken_SecurityTokenMismatch");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The antiforgery cookie token and request token do not match.
|
||||
/// </summary>
|
||||
internal static string FormatAntiforgeryToken_SecurityTokenMismatch()
|
||||
=> GetString("AntiforgeryToken_SecurityTokenMismatch");
|
||||
|
||||
/// <summary>
|
||||
/// Validation of the provided antiforgery token failed. The cookie token and the request token were swapped.
|
||||
/// </summary>
|
||||
internal static string AntiforgeryToken_TokensSwapped
|
||||
{
|
||||
get => GetString("AntiforgeryToken_TokensSwapped");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validation of the provided antiforgery token failed. The cookie token and the request token were swapped.
|
||||
/// </summary>
|
||||
internal static string FormatAntiforgeryToken_TokensSwapped()
|
||||
=> GetString("AntiforgeryToken_TokensSwapped");
|
||||
|
||||
/// <summary>
|
||||
/// The provided antiforgery token was meant for user "{0}", but the current user is "{1}".
|
||||
/// </summary>
|
||||
internal static string AntiforgeryToken_UsernameMismatch
|
||||
{
|
||||
get => GetString("AntiforgeryToken_UsernameMismatch");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The provided antiforgery token was meant for user "{0}", but the current user is "{1}".
|
||||
/// </summary>
|
||||
internal static string FormatAntiforgeryToken_UsernameMismatch(object p0, object p1)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("AntiforgeryToken_UsernameMismatch"), p0, p1);
|
||||
|
||||
/// <summary>
|
||||
/// The antiforgery cookie token is invalid.
|
||||
/// </summary>
|
||||
internal static string Antiforgery_CookieToken_IsInvalid
|
||||
{
|
||||
get => GetString("Antiforgery_CookieToken_IsInvalid");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The antiforgery cookie token is invalid.
|
||||
/// </summary>
|
||||
internal static string FormatAntiforgery_CookieToken_IsInvalid()
|
||||
=> GetString("Antiforgery_CookieToken_IsInvalid");
|
||||
|
||||
/// <summary>
|
||||
/// The required antiforgery cookie "{0}" is not present.
|
||||
/// </summary>
|
||||
internal static string Antiforgery_CookieToken_MustBeProvided
|
||||
{
|
||||
get => GetString("Antiforgery_CookieToken_MustBeProvided");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The required antiforgery cookie "{0}" is not present.
|
||||
/// </summary>
|
||||
internal static string FormatAntiforgery_CookieToken_MustBeProvided(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("Antiforgery_CookieToken_MustBeProvided"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// The required antiforgery cookie token must be provided.
|
||||
/// </summary>
|
||||
internal static string Antiforgery_CookieToken_MustBeProvided_Generic
|
||||
{
|
||||
get => GetString("Antiforgery_CookieToken_MustBeProvided_Generic");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The required antiforgery cookie token must be provided.
|
||||
/// </summary>
|
||||
internal static string FormatAntiforgery_CookieToken_MustBeProvided_Generic()
|
||||
=> GetString("Antiforgery_CookieToken_MustBeProvided_Generic");
|
||||
|
||||
/// <summary>
|
||||
/// The required antiforgery form field "{0}" is not present.
|
||||
/// </summary>
|
||||
internal static string Antiforgery_FormToken_MustBeProvided
|
||||
{
|
||||
get => GetString("Antiforgery_FormToken_MustBeProvided");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The required antiforgery form field "{0}" is not present.
|
||||
/// </summary>
|
||||
internal static string FormatAntiforgery_FormToken_MustBeProvided(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("Antiforgery_FormToken_MustBeProvided"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// The required antiforgery header value "{0}" is not present.
|
||||
/// </summary>
|
||||
internal static string Antiforgery_HeaderToken_MustBeProvided
|
||||
{
|
||||
get => GetString("Antiforgery_HeaderToken_MustBeProvided");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The required antiforgery header value "{0}" is not present.
|
||||
/// </summary>
|
||||
internal static string FormatAntiforgery_HeaderToken_MustBeProvided(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("Antiforgery_HeaderToken_MustBeProvided"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// The required antiforgery request token was not provided in either form field "{0}" or header value "{1}".
|
||||
/// </summary>
|
||||
internal static string Antiforgery_RequestToken_MustBeProvided
|
||||
{
|
||||
get => GetString("Antiforgery_RequestToken_MustBeProvided");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The required antiforgery request token was not provided in either form field "{0}" or header value "{1}".
|
||||
/// </summary>
|
||||
internal static string FormatAntiforgery_RequestToken_MustBeProvided(object p0, object p1)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("Antiforgery_RequestToken_MustBeProvided"), p0, p1);
|
||||
|
||||
/// <summary>
|
||||
/// The required antiforgery request token must be provided.
|
||||
/// </summary>
|
||||
internal static string Antiforgery_RequestToken_MustBeProvided_Generic
|
||||
{
|
||||
get => GetString("Antiforgery_RequestToken_MustBeProvided_Generic");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The required antiforgery request token must be provided.
|
||||
/// </summary>
|
||||
internal static string FormatAntiforgery_RequestToken_MustBeProvided_Generic()
|
||||
=> GetString("Antiforgery_RequestToken_MustBeProvided_Generic");
|
||||
|
||||
/// <summary>
|
||||
/// The antiforgery system has the configuration value {optionName} = {value}, but the current request is not an SSL request.
|
||||
/// </summary>
|
||||
internal static string Antiforgery_RequiresSSL
|
||||
{
|
||||
get => GetString("Antiforgery_RequiresSSL");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The antiforgery system has the configuration value {optionName} = {value}, but the current request is not an SSL request.
|
||||
/// </summary>
|
||||
internal static string FormatAntiforgery_RequiresSSL(object optionName, object value)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("Antiforgery_RequiresSSL", "optionName", "value"), optionName, value);
|
||||
|
||||
/// <summary>
|
||||
/// Value cannot be null or empty.
|
||||
/// </summary>
|
||||
internal static string ArgumentCannotBeNullOrEmpty
|
||||
{
|
||||
get => GetString("ArgumentCannotBeNullOrEmpty");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Value cannot be null or empty.
|
||||
/// </summary>
|
||||
internal static string FormatArgumentCannotBeNullOrEmpty()
|
||||
=> GetString("ArgumentCannotBeNullOrEmpty");
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
||||
System.Diagnostics.Debug.Assert(value != null);
|
||||
|
||||
if (formatterNames != null)
|
||||
{
|
||||
for (var i = 0; i < formatterNames.Length; i++)
|
||||
{
|
||||
value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
<?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="AntiforgeryTokenValidator_AuthenticatedUserWithoutUsername" xml:space="preserve">
|
||||
<value>The provided identity of type '{0}' is marked {1} = {2} but does not have a value for {3}. By default, the antiforgery system requires that all authenticated identities have a unique {3}. If it is not possible to provide a unique {3} for this identity, consider extending {4} by overriding the {5} or a custom type that can provide some form of unique identifier for the current user.</value>
|
||||
<comment>0 = typeof(identity), 1 = nameof(IsAuthenticated), 2 = bool.TrueString, 3 = nameof(Name), 4 = nameof(IAdditionalDataProvider), 5 = nameof(DefaultAdditionalDataProvider)</comment>
|
||||
</data>
|
||||
<data name="AntiforgeryToken_AdditionalDataCheckFailed" xml:space="preserve">
|
||||
<value>The provided antiforgery token failed a custom data check.</value>
|
||||
</data>
|
||||
<data name="AntiforgeryToken_ClaimUidMismatch" xml:space="preserve">
|
||||
<value>The provided antiforgery token was meant for a different claims-based user than the current user.</value>
|
||||
</data>
|
||||
<data name="AntiforgeryToken_DeserializationFailed" xml:space="preserve">
|
||||
<value>The antiforgery token could not be decrypted.</value>
|
||||
</data>
|
||||
<data name="AntiforgeryToken_SecurityTokenMismatch" xml:space="preserve">
|
||||
<value>The antiforgery cookie token and request token do not match.</value>
|
||||
</data>
|
||||
<data name="AntiforgeryToken_TokensSwapped" xml:space="preserve">
|
||||
<value>Validation of the provided antiforgery token failed. The cookie token and the request token were swapped.</value>
|
||||
</data>
|
||||
<data name="AntiforgeryToken_UsernameMismatch" xml:space="preserve">
|
||||
<value>The provided antiforgery token was meant for user "{0}", but the current user is "{1}".</value>
|
||||
</data>
|
||||
<data name="Antiforgery_CookieToken_IsInvalid" xml:space="preserve">
|
||||
<value>The antiforgery cookie token is invalid.</value>
|
||||
</data>
|
||||
<data name="Antiforgery_CookieToken_MustBeProvided" xml:space="preserve">
|
||||
<value>The required antiforgery cookie "{0}" is not present.</value>
|
||||
</data>
|
||||
<data name="Antiforgery_CookieToken_MustBeProvided_Generic" xml:space="preserve">
|
||||
<value>The required antiforgery cookie token must be provided.</value>
|
||||
</data>
|
||||
<data name="Antiforgery_FormToken_MustBeProvided" xml:space="preserve">
|
||||
<value>The required antiforgery form field "{0}" is not present.</value>
|
||||
</data>
|
||||
<data name="Antiforgery_HeaderToken_MustBeProvided" xml:space="preserve">
|
||||
<value>The required antiforgery header value "{0}" is not present.</value>
|
||||
</data>
|
||||
<data name="Antiforgery_RequestToken_MustBeProvided" xml:space="preserve">
|
||||
<value>The required antiforgery request token was not provided in either form field "{0}" or header value "{1}".</value>
|
||||
</data>
|
||||
<data name="Antiforgery_RequestToken_MustBeProvided_Generic" xml:space="preserve">
|
||||
<value>The required antiforgery request token must be provided.</value>
|
||||
</data>
|
||||
<data name="Antiforgery_RequiresSSL" xml:space="preserve">
|
||||
<value>The antiforgery system has the configuration value {optionName} = {value}, but the current request is not an SSL request.</value>
|
||||
</data>
|
||||
<data name="ArgumentCannotBeNullOrEmpty" xml:space="preserve">
|
||||
<value>Value cannot be null or empty.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -0,0 +1,456 @@
|
|||
{
|
||||
"AssemblyIdentity": "Microsoft.AspNetCore.Antiforgery, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
|
||||
"Types": [
|
||||
{
|
||||
"Name": "Microsoft.Extensions.DependencyInjection.AntiforgeryServiceCollectionExtensions",
|
||||
"Visibility": "Public",
|
||||
"Kind": "Class",
|
||||
"Abstract": true,
|
||||
"Static": true,
|
||||
"Sealed": true,
|
||||
"ImplementedInterfaces": [],
|
||||
"Members": [
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "AddAntiforgery",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "services",
|
||||
"Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection"
|
||||
}
|
||||
],
|
||||
"ReturnType": "Microsoft.Extensions.DependencyInjection.IServiceCollection",
|
||||
"Static": true,
|
||||
"Extension": true,
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "AddAntiforgery",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "services",
|
||||
"Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection"
|
||||
},
|
||||
{
|
||||
"Name": "setupAction",
|
||||
"Type": "System.Action<Microsoft.AspNetCore.Antiforgery.AntiforgeryOptions>"
|
||||
}
|
||||
],
|
||||
"ReturnType": "Microsoft.Extensions.DependencyInjection.IServiceCollection",
|
||||
"Static": true,
|
||||
"Extension": true,
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
}
|
||||
],
|
||||
"GenericParameters": []
|
||||
},
|
||||
{
|
||||
"Name": "Microsoft.AspNetCore.Antiforgery.AntiforgeryOptions",
|
||||
"Visibility": "Public",
|
||||
"Kind": "Class",
|
||||
"ImplementedInterfaces": [],
|
||||
"Members": [
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_Cookie",
|
||||
"Parameters": [],
|
||||
"ReturnType": "Microsoft.AspNetCore.Http.CookieBuilder",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "set_Cookie",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "value",
|
||||
"Type": "Microsoft.AspNetCore.Http.CookieBuilder"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Void",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_FormFieldName",
|
||||
"Parameters": [],
|
||||
"ReturnType": "System.String",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "set_FormFieldName",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "value",
|
||||
"Type": "System.String"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Void",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_HeaderName",
|
||||
"Parameters": [],
|
||||
"ReturnType": "System.String",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "set_HeaderName",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "value",
|
||||
"Type": "System.String"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Void",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_SuppressXFrameOptionsHeader",
|
||||
"Parameters": [],
|
||||
"ReturnType": "System.Boolean",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "set_SuppressXFrameOptionsHeader",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "value",
|
||||
"Type": "System.Boolean"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Void",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_CookieName",
|
||||
"Parameters": [],
|
||||
"ReturnType": "System.String",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "set_CookieName",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "value",
|
||||
"Type": "System.String"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Void",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_CookiePath",
|
||||
"Parameters": [],
|
||||
"ReturnType": "System.Nullable<Microsoft.AspNetCore.Http.PathString>",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "set_CookiePath",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "value",
|
||||
"Type": "System.Nullable<Microsoft.AspNetCore.Http.PathString>"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Void",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_CookieDomain",
|
||||
"Parameters": [],
|
||||
"ReturnType": "System.String",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "set_CookieDomain",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "value",
|
||||
"Type": "System.String"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Void",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_RequireSsl",
|
||||
"Parameters": [],
|
||||
"ReturnType": "System.Boolean",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "set_RequireSsl",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "value",
|
||||
"Type": "System.Boolean"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Void",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Constructor",
|
||||
"Name": ".ctor",
|
||||
"Parameters": [],
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Field",
|
||||
"Name": "DefaultCookiePrefix",
|
||||
"Parameters": [],
|
||||
"ReturnType": "System.String",
|
||||
"Static": true,
|
||||
"ReadOnly": true,
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
}
|
||||
],
|
||||
"GenericParameters": []
|
||||
},
|
||||
{
|
||||
"Name": "Microsoft.AspNetCore.Antiforgery.AntiforgeryTokenSet",
|
||||
"Visibility": "Public",
|
||||
"Kind": "Class",
|
||||
"ImplementedInterfaces": [],
|
||||
"Members": [
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_RequestToken",
|
||||
"Parameters": [],
|
||||
"ReturnType": "System.String",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_FormFieldName",
|
||||
"Parameters": [],
|
||||
"ReturnType": "System.String",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_HeaderName",
|
||||
"Parameters": [],
|
||||
"ReturnType": "System.String",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_CookieToken",
|
||||
"Parameters": [],
|
||||
"ReturnType": "System.String",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Constructor",
|
||||
"Name": ".ctor",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "requestToken",
|
||||
"Type": "System.String"
|
||||
},
|
||||
{
|
||||
"Name": "cookieToken",
|
||||
"Type": "System.String"
|
||||
},
|
||||
{
|
||||
"Name": "formFieldName",
|
||||
"Type": "System.String"
|
||||
},
|
||||
{
|
||||
"Name": "headerName",
|
||||
"Type": "System.String"
|
||||
}
|
||||
],
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
}
|
||||
],
|
||||
"GenericParameters": []
|
||||
},
|
||||
{
|
||||
"Name": "Microsoft.AspNetCore.Antiforgery.AntiforgeryValidationException",
|
||||
"Visibility": "Public",
|
||||
"Kind": "Class",
|
||||
"BaseType": "System.Exception",
|
||||
"ImplementedInterfaces": [],
|
||||
"Members": [
|
||||
{
|
||||
"Kind": "Constructor",
|
||||
"Name": ".ctor",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "message",
|
||||
"Type": "System.String"
|
||||
}
|
||||
],
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Constructor",
|
||||
"Name": ".ctor",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "message",
|
||||
"Type": "System.String"
|
||||
},
|
||||
{
|
||||
"Name": "innerException",
|
||||
"Type": "System.Exception"
|
||||
}
|
||||
],
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
}
|
||||
],
|
||||
"GenericParameters": []
|
||||
},
|
||||
{
|
||||
"Name": "Microsoft.AspNetCore.Antiforgery.IAntiforgery",
|
||||
"Visibility": "Public",
|
||||
"Kind": "Interface",
|
||||
"Abstract": true,
|
||||
"ImplementedInterfaces": [],
|
||||
"Members": [
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "GetAndStoreTokens",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "httpContext",
|
||||
"Type": "Microsoft.AspNetCore.Http.HttpContext"
|
||||
}
|
||||
],
|
||||
"ReturnType": "Microsoft.AspNetCore.Antiforgery.AntiforgeryTokenSet",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "GetTokens",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "httpContext",
|
||||
"Type": "Microsoft.AspNetCore.Http.HttpContext"
|
||||
}
|
||||
],
|
||||
"ReturnType": "Microsoft.AspNetCore.Antiforgery.AntiforgeryTokenSet",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "IsRequestValidAsync",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "httpContext",
|
||||
"Type": "Microsoft.AspNetCore.Http.HttpContext"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Threading.Tasks.Task<System.Boolean>",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "ValidateRequestAsync",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "httpContext",
|
||||
"Type": "Microsoft.AspNetCore.Http.HttpContext"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Threading.Tasks.Task",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "SetCookieTokenAndHeader",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "httpContext",
|
||||
"Type": "Microsoft.AspNetCore.Http.HttpContext"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Void",
|
||||
"GenericParameter": []
|
||||
}
|
||||
],
|
||||
"GenericParameters": []
|
||||
},
|
||||
{
|
||||
"Name": "Microsoft.AspNetCore.Antiforgery.IAntiforgeryAdditionalDataProvider",
|
||||
"Visibility": "Public",
|
||||
"Kind": "Interface",
|
||||
"Abstract": true,
|
||||
"ImplementedInterfaces": [],
|
||||
"Members": [
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "GetAdditionalData",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "context",
|
||||
"Type": "Microsoft.AspNetCore.Http.HttpContext"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.String",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "ValidateAdditionalData",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "context",
|
||||
"Type": "Microsoft.AspNetCore.Http.HttpContext"
|
||||
},
|
||||
{
|
||||
"Name": "additionalData",
|
||||
"Type": "System.String"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Boolean",
|
||||
"GenericParameter": []
|
||||
}
|
||||
],
|
||||
"GenericParameters": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<Project>
|
||||
<Import Project="..\Directory.Build.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<DeveloperBuildTestTfms>netcoreapp2.1</DeveloperBuildTestTfms>
|
||||
<StandardTestTfms>$(DeveloperBuildTestTfms)</StandardTestTfms>
|
||||
<StandardTestTfms Condition=" '$(DeveloperBuild)' != 'true' ">netcoreapp2.1;netcoreapp2.0</StandardTestTfms>
|
||||
<StandardTestTfms Condition=" '$(DeveloperBuild)' != 'true' AND '$(OS)' == 'Windows_NT' ">$(StandardTestTfms);net461</StandardTestTfms>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
// 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.DataProtection;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Antiforgery.Internal
|
||||
{
|
||||
public class AntiforgeryOptionsSetupTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("HelloWorldApp", ".AspNetCore.Antiforgery.tGmK82_ckDw")]
|
||||
[InlineData("TodoCalendar", ".AspNetCore.Antiforgery.7mK1hBEBwYs")]
|
||||
public void AntiforgeryOptionsSetup_SetsDefaultCookieName_BasedOnApplicationId(
|
||||
string applicationId,
|
||||
string expectedCookieName)
|
||||
{
|
||||
// Arrange
|
||||
var serviceCollection = new ServiceCollection();
|
||||
serviceCollection.AddAntiforgery();
|
||||
serviceCollection
|
||||
.AddDataProtection()
|
||||
.SetApplicationName(applicationId);
|
||||
|
||||
var services = serviceCollection.BuildServiceProvider();
|
||||
var options = services.GetRequiredService<IOptions<AntiforgeryOptions>>();
|
||||
|
||||
// Act
|
||||
var cookieName = options.Value.Cookie.Name;
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedCookieName, cookieName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AntiforgeryOptionsSetup_UserOptionsSetup_CanSetCookieName()
|
||||
{
|
||||
// Arrange
|
||||
var serviceCollection = new ServiceCollection();
|
||||
serviceCollection.Configure<AntiforgeryOptions>(o =>
|
||||
{
|
||||
Assert.Null(o.Cookie.Name);
|
||||
o.Cookie.Name = "antiforgery";
|
||||
});
|
||||
serviceCollection.AddAntiforgery();
|
||||
serviceCollection
|
||||
.AddDataProtection()
|
||||
.SetApplicationName("HelloWorldApp");
|
||||
|
||||
var services = serviceCollection.BuildServiceProvider();
|
||||
var options = services.GetRequiredService<IOptions<AntiforgeryOptions>>();
|
||||
|
||||
// Act
|
||||
var cookieName = options.Value.Cookie.Name;
|
||||
|
||||
// Assert
|
||||
Assert.Equal("antiforgery", cookieName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AntiforgeryOptions_SetsCookieSecurePolicy_ToNone_ByDefault()
|
||||
{
|
||||
// Arrange & Act
|
||||
var options = new AntiforgeryOptions();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(CookieSecurePolicy.None, options.Cookie.SecurePolicy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
// 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 Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Antiforgery.Internal
|
||||
{
|
||||
public class AntiforgeryTokenTest
|
||||
{
|
||||
[Fact]
|
||||
public void AdditionalDataProperty()
|
||||
{
|
||||
// Arrange
|
||||
var token = new AntiforgeryToken();
|
||||
|
||||
// Act & assert - 1
|
||||
Assert.Equal("", token.AdditionalData);
|
||||
|
||||
// Act & assert - 2
|
||||
token.AdditionalData = "additional data";
|
||||
Assert.Equal("additional data", token.AdditionalData);
|
||||
|
||||
// Act & assert - 3
|
||||
token.AdditionalData = null;
|
||||
Assert.Equal("", token.AdditionalData);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClaimUidProperty()
|
||||
{
|
||||
// Arrange
|
||||
var token = new AntiforgeryToken();
|
||||
|
||||
// Act & assert - 1
|
||||
Assert.Null(token.ClaimUid);
|
||||
|
||||
// Act & assert - 2
|
||||
BinaryBlob blob = new BinaryBlob(32);
|
||||
token.ClaimUid = blob;
|
||||
Assert.Equal(blob, token.ClaimUid);
|
||||
|
||||
// Act & assert - 3
|
||||
token.ClaimUid = null;
|
||||
Assert.Null(token.ClaimUid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsCookieTokenProperty()
|
||||
{
|
||||
// Arrange
|
||||
var token = new AntiforgeryToken();
|
||||
|
||||
// Act & assert - 1
|
||||
Assert.False(token.IsCookieToken);
|
||||
|
||||
// Act & assert - 2
|
||||
token.IsCookieToken = true;
|
||||
Assert.True(token.IsCookieToken);
|
||||
|
||||
// Act & assert - 3
|
||||
token.IsCookieToken = false;
|
||||
Assert.False(token.IsCookieToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UsernameProperty()
|
||||
{
|
||||
// Arrange
|
||||
var token = new AntiforgeryToken();
|
||||
|
||||
// Act & assert - 1
|
||||
Assert.Equal("", token.Username);
|
||||
|
||||
// Act & assert - 2
|
||||
token.Username = "my username";
|
||||
Assert.Equal("my username", token.Username);
|
||||
|
||||
// Act & assert - 3
|
||||
token.Username = null;
|
||||
Assert.Equal("", token.Username);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SecurityTokenProperty_GetsAutopopulated()
|
||||
{
|
||||
// Arrange
|
||||
var token = new AntiforgeryToken();
|
||||
|
||||
// Act
|
||||
var securityToken = token.SecurityToken;
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(securityToken);
|
||||
Assert.Equal(AntiforgeryToken.SecurityTokenBitLength, securityToken.BitLength);
|
||||
|
||||
// check that we're not making a new one each property call
|
||||
Assert.Equal(securityToken, token.SecurityToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SecurityTokenProperty_PropertySetter_DoesNotUseDefaults()
|
||||
{
|
||||
// Arrange
|
||||
var token = new AntiforgeryToken();
|
||||
|
||||
// Act
|
||||
var securityToken = new BinaryBlob(64);
|
||||
token.SecurityToken = securityToken;
|
||||
|
||||
// Assert
|
||||
Assert.Equal(securityToken, token.SecurityToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SecurityTokenProperty_PropertySetter_DoesNotAllowNulls()
|
||||
{
|
||||
// Arrange
|
||||
var token = new AntiforgeryToken();
|
||||
|
||||
// Act
|
||||
token.SecurityToken = null;
|
||||
var securityToken = token.SecurityToken;
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(securityToken);
|
||||
Assert.Equal(AntiforgeryToken.SecurityTokenBitLength, securityToken.BitLength);
|
||||
|
||||
// check that we're not making a new one each property call
|
||||
Assert.Equal(securityToken, token.SecurityToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
// 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.Antiforgery.Internal
|
||||
{
|
||||
public class BinaryBlobTest
|
||||
{
|
||||
[Fact]
|
||||
public void Ctor_BitLength()
|
||||
{
|
||||
// Act
|
||||
var blob = new BinaryBlob(bitLength: 64);
|
||||
var data = blob.GetData();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(64, blob.BitLength);
|
||||
Assert.Equal(64 / 8, data.Length);
|
||||
Assert.NotEqual(new byte[64 / 8], data); // should not be a zero-filled array
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(24)]
|
||||
[InlineData(33)]
|
||||
public void Ctor_BitLength_Bad(int bitLength)
|
||||
{
|
||||
// Act & assert
|
||||
var ex = Assert.Throws<ArgumentOutOfRangeException>(() => new BinaryBlob(bitLength));
|
||||
Assert.Equal("bitLength", ex.ParamName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Ctor_BitLength_ProducesDifferentValues()
|
||||
{
|
||||
// Act
|
||||
var blobA = new BinaryBlob(bitLength: 64);
|
||||
var blobB = new BinaryBlob(bitLength: 64);
|
||||
|
||||
// Assert
|
||||
Assert.NotEqual(blobA.GetData(), blobB.GetData());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Ctor_Data()
|
||||
{
|
||||
// Arrange
|
||||
var expectedData = new byte[] { 0x01, 0x02, 0x03, 0x04 };
|
||||
|
||||
// Act
|
||||
var blob = new BinaryBlob(32, expectedData);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(32, blob.BitLength);
|
||||
Assert.Equal(expectedData, blob.GetData());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData((object[])null)]
|
||||
[InlineData(new byte[] { 0x01, 0x02, 0x03 })]
|
||||
public void Ctor_Data_Bad(byte[] data)
|
||||
{
|
||||
// Act & assert
|
||||
var ex = Assert.Throws<ArgumentOutOfRangeException>(() => new BinaryBlob(32, data));
|
||||
Assert.Equal("data", ex.ParamName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equals_DifferentData_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
object blobA = new BinaryBlob(32, new byte[] { 0x01, 0x02, 0x03, 0x04 });
|
||||
object blobB = new BinaryBlob(32, new byte[] { 0x04, 0x03, 0x02, 0x01 });
|
||||
|
||||
// Act & assert
|
||||
Assert.NotEqual(blobA, blobB);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equals_NotABlob_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
object blobA = new BinaryBlob(32);
|
||||
object blobB = "hello";
|
||||
|
||||
// Act & assert
|
||||
Assert.NotEqual(blobA, blobB);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equals_Null_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
object blobA = new BinaryBlob(32);
|
||||
object blobB = null;
|
||||
|
||||
// Act & assert
|
||||
Assert.NotEqual(blobA, blobB);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equals_SameData_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
object blobA = new BinaryBlob(32, new byte[] { 0x01, 0x02, 0x03, 0x04 });
|
||||
object blobB = new BinaryBlob(32, new byte[] { 0x01, 0x02, 0x03, 0x04 });
|
||||
|
||||
// Act & assert
|
||||
Assert.Equal(blobA, blobB);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetHashCodeTest()
|
||||
{
|
||||
// Arrange
|
||||
var blobData = new byte[] { 0x01, 0x02, 0x03, 0x04 };
|
||||
var expectedHashCode = BitConverter.ToInt32(blobData, 0);
|
||||
|
||||
var blob = new BinaryBlob(32, blobData);
|
||||
|
||||
// Act
|
||||
var actualHashCode = blob.GetHashCode();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedHashCode, actualHashCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,628 @@
|
|||
// 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.Security.Claims;
|
||||
using System.Security.Cryptography;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Antiforgery.Internal
|
||||
{
|
||||
public class DefaultAntiforgeryTokenGeneratorProviderTest
|
||||
{
|
||||
[Fact]
|
||||
public void GenerateCookieToken()
|
||||
{
|
||||
// Arrange
|
||||
var tokenProvider = new DefaultAntiforgeryTokenGenerator(
|
||||
claimUidExtractor: null,
|
||||
additionalDataProvider: null);
|
||||
|
||||
// Act
|
||||
var token = tokenProvider.GenerateCookieToken();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(token);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GenerateRequestToken_InvalidCookieToken()
|
||||
{
|
||||
// Arrange
|
||||
var cookieToken = new AntiforgeryToken() { IsCookieToken = false };
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
|
||||
Assert.False(httpContext.User.Identity.IsAuthenticated);
|
||||
|
||||
var tokenProvider = new DefaultAntiforgeryTokenGenerator(
|
||||
claimUidExtractor: null,
|
||||
additionalDataProvider: null);
|
||||
|
||||
// Act & Assert
|
||||
ExceptionAssert.ThrowsArgument(
|
||||
() => tokenProvider.GenerateRequestToken(httpContext, cookieToken),
|
||||
"cookieToken",
|
||||
"The antiforgery cookie token is invalid.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GenerateRequestToken_AnonymousUser()
|
||||
{
|
||||
// Arrange
|
||||
var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
|
||||
Assert.False(httpContext.User.Identity.IsAuthenticated);
|
||||
|
||||
var tokenProvider = new DefaultAntiforgeryTokenGenerator(
|
||||
claimUidExtractor: null,
|
||||
additionalDataProvider: null);
|
||||
|
||||
// Act
|
||||
var fieldToken = tokenProvider.GenerateRequestToken(httpContext, cookieToken);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(fieldToken);
|
||||
Assert.Equal(cookieToken.SecurityToken, fieldToken.SecurityToken);
|
||||
Assert.False(fieldToken.IsCookieToken);
|
||||
Assert.Empty(fieldToken.Username);
|
||||
Assert.Null(fieldToken.ClaimUid);
|
||||
Assert.Empty(fieldToken.AdditionalData);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GenerateRequestToken_AuthenticatedWithoutUsernameAndNoAdditionalData_NoAdditionalData()
|
||||
{
|
||||
// Arrange
|
||||
var cookieToken = new AntiforgeryToken()
|
||||
{
|
||||
IsCookieToken = true
|
||||
};
|
||||
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.User = new ClaimsPrincipal(new MyAuthenticatedIdentityWithoutUsername());
|
||||
|
||||
var options = new AntiforgeryOptions();
|
||||
var claimUidExtractor = new Mock<IClaimUidExtractor>().Object;
|
||||
|
||||
var tokenProvider = new DefaultAntiforgeryTokenGenerator(
|
||||
claimUidExtractor: claimUidExtractor,
|
||||
additionalDataProvider: null);
|
||||
|
||||
// Act & assert
|
||||
var exception = Assert.Throws<InvalidOperationException>(
|
||||
() => tokenProvider.GenerateRequestToken(httpContext, cookieToken));
|
||||
Assert.Equal(
|
||||
"The provided identity of type " +
|
||||
$"'{typeof(MyAuthenticatedIdentityWithoutUsername).FullName}' " +
|
||||
"is marked IsAuthenticated = true but does not have a value for Name. " +
|
||||
"By default, the antiforgery system requires that all authenticated identities have a unique Name. " +
|
||||
"If it is not possible to provide a unique Name for this identity, " +
|
||||
"consider extending IAntiforgeryAdditionalDataProvider by overriding the " +
|
||||
"DefaultAntiforgeryAdditionalDataProvider " +
|
||||
"or a custom type that can provide some form of unique identifier for the current user.",
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GenerateRequestToken_AuthenticatedWithoutUsername_WithAdditionalData()
|
||||
{
|
||||
// Arrange
|
||||
var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
|
||||
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.User = new ClaimsPrincipal(new MyAuthenticatedIdentityWithoutUsername());
|
||||
|
||||
var mockAdditionalDataProvider = new Mock<IAntiforgeryAdditionalDataProvider>();
|
||||
mockAdditionalDataProvider.Setup(o => o.GetAdditionalData(httpContext))
|
||||
.Returns("additional-data");
|
||||
|
||||
var claimUidExtractor = new Mock<IClaimUidExtractor>().Object;
|
||||
|
||||
var tokenProvider = new DefaultAntiforgeryTokenGenerator(
|
||||
claimUidExtractor: claimUidExtractor,
|
||||
additionalDataProvider: mockAdditionalDataProvider.Object);
|
||||
|
||||
// Act
|
||||
var fieldToken = tokenProvider.GenerateRequestToken(httpContext, cookieToken);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(fieldToken);
|
||||
Assert.Equal(cookieToken.SecurityToken, fieldToken.SecurityToken);
|
||||
Assert.False(fieldToken.IsCookieToken);
|
||||
Assert.Empty(fieldToken.Username);
|
||||
Assert.Null(fieldToken.ClaimUid);
|
||||
Assert.Equal("additional-data", fieldToken.AdditionalData);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GenerateRequestToken_ClaimsBasedIdentity()
|
||||
{
|
||||
// Arrange
|
||||
var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
|
||||
|
||||
var identity = GetAuthenticatedIdentity("some-identity");
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.User = new ClaimsPrincipal(identity);
|
||||
|
||||
byte[] data = new byte[256 / 8];
|
||||
using (var rng = RandomNumberGenerator.Create())
|
||||
{
|
||||
rng.GetBytes(data);
|
||||
}
|
||||
var base64ClaimUId = Convert.ToBase64String(data);
|
||||
var expectedClaimUid = new BinaryBlob(256, data);
|
||||
|
||||
var mockClaimUidExtractor = new Mock<IClaimUidExtractor>();
|
||||
mockClaimUidExtractor.Setup(o => o.ExtractClaimUid(It.Is<ClaimsPrincipal>(c => c.Identity == identity)))
|
||||
.Returns(base64ClaimUId);
|
||||
|
||||
var tokenProvider = new DefaultAntiforgeryTokenGenerator(
|
||||
claimUidExtractor: mockClaimUidExtractor.Object,
|
||||
additionalDataProvider: null);
|
||||
|
||||
// Act
|
||||
var fieldToken = tokenProvider.GenerateRequestToken(httpContext, cookieToken);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(fieldToken);
|
||||
Assert.Equal(cookieToken.SecurityToken, fieldToken.SecurityToken);
|
||||
Assert.False(fieldToken.IsCookieToken);
|
||||
Assert.Equal("", fieldToken.Username);
|
||||
Assert.Equal(expectedClaimUid, fieldToken.ClaimUid);
|
||||
Assert.Equal("", fieldToken.AdditionalData);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GenerateRequestToken_RegularUserWithUsername()
|
||||
{
|
||||
// Arrange
|
||||
var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
|
||||
|
||||
var httpContext = new DefaultHttpContext();
|
||||
var mockIdentity = new Mock<ClaimsIdentity>();
|
||||
mockIdentity.Setup(o => o.IsAuthenticated)
|
||||
.Returns(true);
|
||||
mockIdentity.Setup(o => o.Name)
|
||||
.Returns("my-username");
|
||||
|
||||
httpContext.User = new ClaimsPrincipal(mockIdentity.Object);
|
||||
|
||||
var claimUidExtractor = new Mock<IClaimUidExtractor>().Object;
|
||||
|
||||
var tokenProvider = new DefaultAntiforgeryTokenGenerator(
|
||||
claimUidExtractor: claimUidExtractor,
|
||||
additionalDataProvider: null);
|
||||
|
||||
// Act
|
||||
var fieldToken = tokenProvider.GenerateRequestToken(httpContext, cookieToken);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(fieldToken);
|
||||
Assert.Equal(cookieToken.SecurityToken, fieldToken.SecurityToken);
|
||||
Assert.False(fieldToken.IsCookieToken);
|
||||
Assert.Equal("my-username", fieldToken.Username);
|
||||
Assert.Null(fieldToken.ClaimUid);
|
||||
Assert.Empty(fieldToken.AdditionalData);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsCookieTokenValid_FieldToken_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var cookieToken = new AntiforgeryToken()
|
||||
{
|
||||
IsCookieToken = false
|
||||
};
|
||||
|
||||
var tokenProvider = new DefaultAntiforgeryTokenGenerator(
|
||||
claimUidExtractor: null,
|
||||
additionalDataProvider: null);
|
||||
|
||||
// Act
|
||||
var isValid = tokenProvider.IsCookieTokenValid(cookieToken);
|
||||
|
||||
// Assert
|
||||
Assert.False(isValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsCookieTokenValid_NullToken_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
AntiforgeryToken cookieToken = null;
|
||||
var tokenProvider = new DefaultAntiforgeryTokenGenerator(
|
||||
claimUidExtractor: null,
|
||||
additionalDataProvider: null);
|
||||
|
||||
// Act
|
||||
var isValid = tokenProvider.IsCookieTokenValid(cookieToken);
|
||||
|
||||
// Assert
|
||||
Assert.False(isValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsCookieTokenValid_ValidToken_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var cookieToken = new AntiforgeryToken()
|
||||
{
|
||||
IsCookieToken = true
|
||||
};
|
||||
|
||||
var tokenProvider = new DefaultAntiforgeryTokenGenerator(
|
||||
claimUidExtractor: null,
|
||||
additionalDataProvider: null);
|
||||
|
||||
// Act
|
||||
var isValid = tokenProvider.IsCookieTokenValid(cookieToken);
|
||||
|
||||
// Assert
|
||||
Assert.True(isValid);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void TryValidateTokenSet_CookieTokenMissing()
|
||||
{
|
||||
// Arrange
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
|
||||
|
||||
var fieldtoken = new AntiforgeryToken() { IsCookieToken = false };
|
||||
|
||||
var tokenProvider = new DefaultAntiforgeryTokenGenerator(
|
||||
claimUidExtractor: null,
|
||||
additionalDataProvider: null);
|
||||
|
||||
// Act & Assert
|
||||
string message;
|
||||
var ex = Assert.Throws<ArgumentNullException>(
|
||||
() => tokenProvider.TryValidateTokenSet(httpContext, null, fieldtoken, out message));
|
||||
|
||||
var trimmed = ex.Message.Substring(0, ex.Message.IndexOf(Environment.NewLine));
|
||||
Assert.Equal(@"The required antiforgery cookie token must be provided.", trimmed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryValidateTokenSet_FieldTokenMissing()
|
||||
{
|
||||
// Arrange
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
|
||||
|
||||
var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
|
||||
|
||||
var tokenProvider = new DefaultAntiforgeryTokenGenerator(
|
||||
claimUidExtractor: null,
|
||||
additionalDataProvider: null);
|
||||
|
||||
|
||||
// Act & Assert
|
||||
string message;
|
||||
var ex = Assert.Throws<ArgumentNullException>(
|
||||
() => tokenProvider.TryValidateTokenSet(httpContext, cookieToken, null, out message));
|
||||
|
||||
var trimmed = ex.Message.Substring(0, ex.Message.IndexOf(Environment.NewLine));
|
||||
Assert.Equal("The required antiforgery request token must be provided.", trimmed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryValidateTokenSet_FieldAndCookieTokensSwapped_FieldTokenDuplicated()
|
||||
{
|
||||
// Arrange
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
|
||||
|
||||
var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
|
||||
var fieldtoken = new AntiforgeryToken() { IsCookieToken = false };
|
||||
|
||||
var tokenProvider = new DefaultAntiforgeryTokenGenerator(
|
||||
claimUidExtractor: null,
|
||||
additionalDataProvider: null);
|
||||
|
||||
string expectedMessage =
|
||||
"Validation of the provided antiforgery token failed. " +
|
||||
"The cookie token and the request token were swapped.";
|
||||
|
||||
// Act
|
||||
string message;
|
||||
var result = tokenProvider.TryValidateTokenSet(httpContext, fieldtoken, fieldtoken, out message);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
Assert.Equal(expectedMessage, message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryValidateTokenSet_FieldAndCookieTokensSwapped_CookieDuplicated()
|
||||
{
|
||||
// Arrange
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
|
||||
|
||||
var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
|
||||
var fieldtoken = new AntiforgeryToken() { IsCookieToken = false };
|
||||
|
||||
var tokenProvider = new DefaultAntiforgeryTokenGenerator(
|
||||
claimUidExtractor: null,
|
||||
additionalDataProvider: null);
|
||||
|
||||
string expectedMessage =
|
||||
"Validation of the provided antiforgery token failed. " +
|
||||
"The cookie token and the request token were swapped.";
|
||||
|
||||
// Act
|
||||
string message;
|
||||
var result = tokenProvider.TryValidateTokenSet(httpContext, cookieToken, cookieToken, out message);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
Assert.Equal(expectedMessage, message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryValidateTokenSet_FieldAndCookieTokensHaveDifferentSecurityKeys()
|
||||
{
|
||||
// Arrange
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
|
||||
|
||||
var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
|
||||
var fieldtoken = new AntiforgeryToken() { IsCookieToken = false };
|
||||
|
||||
var tokenProvider = new DefaultAntiforgeryTokenGenerator(
|
||||
claimUidExtractor: null,
|
||||
additionalDataProvider: null);
|
||||
|
||||
string expectedMessage = "The antiforgery cookie token and request token do not match.";
|
||||
|
||||
// Act
|
||||
string message;
|
||||
var result = tokenProvider.TryValidateTokenSet(httpContext, cookieToken, fieldtoken, out message);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
Assert.Equal(expectedMessage, message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("the-user", "the-other-user")]
|
||||
[InlineData("http://example.com/uri-casing", "http://example.com/URI-casing")]
|
||||
[InlineData("https://example.com/secure-uri-casing", "https://example.com/secure-URI-casing")]
|
||||
public void TryValidateTokenSet_UsernameMismatch(string identityUsername, string embeddedUsername)
|
||||
{
|
||||
// Arrange
|
||||
var httpContext = new DefaultHttpContext();
|
||||
var identity = GetAuthenticatedIdentity(identityUsername);
|
||||
httpContext.User = new ClaimsPrincipal(identity);
|
||||
|
||||
var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
|
||||
var fieldtoken = new AntiforgeryToken()
|
||||
{
|
||||
SecurityToken = cookieToken.SecurityToken,
|
||||
Username = embeddedUsername,
|
||||
IsCookieToken = false
|
||||
};
|
||||
|
||||
var mockClaimUidExtractor = new Mock<IClaimUidExtractor>();
|
||||
mockClaimUidExtractor.Setup(o => o.ExtractClaimUid(It.Is<ClaimsPrincipal>(c => c.Identity == identity)))
|
||||
.Returns((string)null);
|
||||
|
||||
var tokenProvider = new DefaultAntiforgeryTokenGenerator(
|
||||
claimUidExtractor: mockClaimUidExtractor.Object,
|
||||
additionalDataProvider: null);
|
||||
|
||||
string expectedMessage =
|
||||
$"The provided antiforgery token was meant for user \"{embeddedUsername}\", " +
|
||||
$"but the current user is \"{identityUsername}\".";
|
||||
|
||||
// Act
|
||||
string message;
|
||||
var result = tokenProvider.TryValidateTokenSet(httpContext, cookieToken, fieldtoken, out message);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
Assert.Equal(expectedMessage, message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryValidateTokenSet_ClaimUidMismatch()
|
||||
{
|
||||
// Arrange
|
||||
var httpContext = new DefaultHttpContext();
|
||||
var identity = GetAuthenticatedIdentity("the-user");
|
||||
httpContext.User = new ClaimsPrincipal(identity);
|
||||
|
||||
var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
|
||||
var fieldtoken = new AntiforgeryToken()
|
||||
{
|
||||
SecurityToken = cookieToken.SecurityToken,
|
||||
IsCookieToken = false,
|
||||
ClaimUid = new BinaryBlob(256)
|
||||
};
|
||||
|
||||
var differentToken = new BinaryBlob(256);
|
||||
var mockClaimUidExtractor = new Mock<IClaimUidExtractor>();
|
||||
mockClaimUidExtractor.Setup(o => o.ExtractClaimUid(It.Is<ClaimsPrincipal>(c => c.Identity == identity)))
|
||||
.Returns(Convert.ToBase64String(differentToken.GetData()));
|
||||
|
||||
var tokenProvider = new DefaultAntiforgeryTokenGenerator(
|
||||
claimUidExtractor: mockClaimUidExtractor.Object,
|
||||
additionalDataProvider: null);
|
||||
|
||||
string expectedMessage =
|
||||
"The provided antiforgery token was meant for a different " +
|
||||
"claims-based user than the current user.";
|
||||
|
||||
// Act
|
||||
string message;
|
||||
var result = tokenProvider.TryValidateTokenSet(httpContext, cookieToken, fieldtoken, out message);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
Assert.Equal(expectedMessage, message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryValidateTokenSet_AdditionalDataRejected()
|
||||
{
|
||||
// Arrange
|
||||
var httpContext = new DefaultHttpContext();
|
||||
var identity = new ClaimsIdentity();
|
||||
httpContext.User = new ClaimsPrincipal(identity);
|
||||
|
||||
var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
|
||||
var fieldtoken = new AntiforgeryToken()
|
||||
{
|
||||
SecurityToken = cookieToken.SecurityToken,
|
||||
Username = String.Empty,
|
||||
IsCookieToken = false,
|
||||
AdditionalData = "some-additional-data"
|
||||
};
|
||||
|
||||
var mockAdditionalDataProvider = new Mock<IAntiforgeryAdditionalDataProvider>();
|
||||
mockAdditionalDataProvider
|
||||
.Setup(o => o.ValidateAdditionalData(httpContext, "some-additional-data"))
|
||||
.Returns(false);
|
||||
|
||||
var tokenProvider = new DefaultAntiforgeryTokenGenerator(
|
||||
claimUidExtractor: null,
|
||||
additionalDataProvider: mockAdditionalDataProvider.Object);
|
||||
|
||||
string expectedMessage = "The provided antiforgery token failed a custom data check.";
|
||||
|
||||
// Act
|
||||
string message;
|
||||
var result = tokenProvider.TryValidateTokenSet(httpContext, cookieToken, fieldtoken, out message);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
Assert.Equal(expectedMessage, message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryValidateTokenSet_Success_AnonymousUser()
|
||||
{
|
||||
// Arrange
|
||||
var httpContext = new DefaultHttpContext();
|
||||
var identity = new ClaimsIdentity();
|
||||
httpContext.User = new ClaimsPrincipal(identity);
|
||||
|
||||
var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
|
||||
var fieldtoken = new AntiforgeryToken()
|
||||
{
|
||||
SecurityToken = cookieToken.SecurityToken,
|
||||
Username = String.Empty,
|
||||
IsCookieToken = false,
|
||||
AdditionalData = "some-additional-data"
|
||||
};
|
||||
|
||||
var mockAdditionalDataProvider = new Mock<IAntiforgeryAdditionalDataProvider>();
|
||||
mockAdditionalDataProvider.Setup(o => o.ValidateAdditionalData(httpContext, "some-additional-data"))
|
||||
.Returns(true);
|
||||
|
||||
var tokenProvider = new DefaultAntiforgeryTokenGenerator(
|
||||
claimUidExtractor: null,
|
||||
additionalDataProvider: mockAdditionalDataProvider.Object);
|
||||
|
||||
// Act
|
||||
string message;
|
||||
var result = tokenProvider.TryValidateTokenSet(httpContext, cookieToken, fieldtoken, out message);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
Assert.Null(message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryValidateTokenSet_Success_AuthenticatedUserWithUsername()
|
||||
{
|
||||
// Arrange
|
||||
var httpContext = new DefaultHttpContext();
|
||||
var identity = GetAuthenticatedIdentity("the-user");
|
||||
httpContext.User = new ClaimsPrincipal(identity);
|
||||
|
||||
var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
|
||||
var fieldtoken = new AntiforgeryToken()
|
||||
{
|
||||
SecurityToken = cookieToken.SecurityToken,
|
||||
Username = "THE-USER",
|
||||
IsCookieToken = false,
|
||||
AdditionalData = "some-additional-data"
|
||||
};
|
||||
|
||||
var mockAdditionalDataProvider = new Mock<IAntiforgeryAdditionalDataProvider>();
|
||||
mockAdditionalDataProvider.Setup(o => o.ValidateAdditionalData(httpContext, "some-additional-data"))
|
||||
.Returns(true);
|
||||
|
||||
var tokenProvider = new DefaultAntiforgeryTokenGenerator(
|
||||
claimUidExtractor: new Mock<IClaimUidExtractor>().Object,
|
||||
additionalDataProvider: mockAdditionalDataProvider.Object);
|
||||
|
||||
// Act
|
||||
string message;
|
||||
var result = tokenProvider.TryValidateTokenSet(httpContext, cookieToken, fieldtoken, out message);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
Assert.Null(message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryValidateTokenSet_Success_ClaimsBasedUser()
|
||||
{
|
||||
// Arrange
|
||||
var httpContext = new DefaultHttpContext();
|
||||
var identity = GetAuthenticatedIdentity("the-user");
|
||||
httpContext.User = new ClaimsPrincipal(identity);
|
||||
|
||||
var cookieToken = new AntiforgeryToken() { IsCookieToken = true };
|
||||
var fieldtoken = new AntiforgeryToken()
|
||||
{
|
||||
SecurityToken = cookieToken.SecurityToken,
|
||||
IsCookieToken = false,
|
||||
ClaimUid = new BinaryBlob(256)
|
||||
};
|
||||
|
||||
var mockClaimUidExtractor = new Mock<IClaimUidExtractor>();
|
||||
mockClaimUidExtractor.Setup(o => o.ExtractClaimUid(It.Is<ClaimsPrincipal>(c => c.Identity == identity)))
|
||||
.Returns(Convert.ToBase64String(fieldtoken.ClaimUid.GetData()));
|
||||
|
||||
var tokenProvider = new DefaultAntiforgeryTokenGenerator(
|
||||
claimUidExtractor: mockClaimUidExtractor.Object,
|
||||
additionalDataProvider: null);
|
||||
|
||||
// Act
|
||||
string message;
|
||||
var result = tokenProvider.TryValidateTokenSet(httpContext, cookieToken, fieldtoken, out message);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
Assert.Null(message);
|
||||
}
|
||||
|
||||
private static ClaimsIdentity GetAuthenticatedIdentity(string identityUsername)
|
||||
{
|
||||
var claim = new Claim(ClaimsIdentity.DefaultNameClaimType, identityUsername);
|
||||
return new ClaimsIdentity(new[] { claim }, "Some-Authentication");
|
||||
}
|
||||
|
||||
private sealed class MyAuthenticatedIdentityWithoutUsername : ClaimsIdentity
|
||||
{
|
||||
public override bool IsAuthenticated
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public override string Name
|
||||
{
|
||||
get { return String.Empty; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,189 @@
|
|||
// 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 Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Antiforgery.Internal
|
||||
{
|
||||
public class DefaultAntiforgeryTokenSerializerTest
|
||||
{
|
||||
private static readonly Mock<IDataProtectionProvider> _dataProtector = GetDataProtector();
|
||||
private static readonly BinaryBlob _claimUid = new BinaryBlob(256, new byte[] { 0x6F, 0x16, 0x48, 0xE9, 0x72, 0x49, 0xAA, 0x58, 0x75, 0x40, 0x36, 0xA6, 0x7E, 0x24, 0x8C, 0xF0, 0x44, 0xF0, 0x7E, 0xCF, 0xB0, 0xED, 0x38, 0x75, 0x56, 0xCE, 0x02, 0x9A, 0x4F, 0x9A, 0x40, 0xE0 });
|
||||
private static readonly BinaryBlob _securityToken = new BinaryBlob(128, new byte[] { 0x70, 0x5E, 0xED, 0xCC, 0x7D, 0x42, 0xF1, 0xD6, 0xB3, 0xB9, 0x8A, 0x59, 0x36, 0x25, 0xBB, 0x4C });
|
||||
private static readonly ObjectPool<AntiforgerySerializationContext> _pool =
|
||||
new DefaultObjectPoolProvider().Create(new AntiforgerySerializationContextPooledObjectPolicy());
|
||||
private const byte _salt = 0x05;
|
||||
|
||||
[Theory]
|
||||
[InlineData(
|
||||
"01" // Version
|
||||
+ "705EEDCC7D42F1D6B3B9" // SecurityToken
|
||||
// (WRONG!) Stream ends too early
|
||||
)]
|
||||
[InlineData(
|
||||
"01" // Version
|
||||
+ "705EEDCC7D42F1D6B3B98A593625BB4C" // SecurityToken
|
||||
+ "01" // IsCookieToken
|
||||
+ "00" // (WRONG!) Too much data in stream
|
||||
)]
|
||||
[InlineData(
|
||||
"02" // (WRONG! - must be 0x01) Version
|
||||
+ "705EEDCC7D42F1D6B3B98A593625BB4C" // SecurityToken
|
||||
+ "01" // IsCookieToken
|
||||
)]
|
||||
[InlineData(
|
||||
"01" // Version
|
||||
+ "705EEDCC7D42F1D6B3B98A593625BB4C" // SecurityToken
|
||||
+ "00" // IsCookieToken
|
||||
+ "00" // IsClaimsBased
|
||||
+ "05" // Username length header
|
||||
+ "0000" // (WRONG!) Too little data in stream
|
||||
)]
|
||||
public void Deserialize_BadToken_Throws(string serializedToken)
|
||||
{
|
||||
// Arrange
|
||||
var testSerializer = new DefaultAntiforgeryTokenSerializer(_dataProtector.Object, _pool);
|
||||
|
||||
// Act & assert
|
||||
var ex = Assert.Throws<AntiforgeryValidationException>(() => testSerializer.Deserialize(serializedToken));
|
||||
Assert.Equal(@"The antiforgery token could not be decrypted.", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Serialize_FieldToken_WithClaimUid_TokenRoundTripSuccessful()
|
||||
{
|
||||
// Arrange
|
||||
var testSerializer = new DefaultAntiforgeryTokenSerializer(_dataProtector.Object, _pool);
|
||||
|
||||
//"01" // Version
|
||||
//+ "705EEDCC7D42F1D6B3B98A593625BB4C" // SecurityToken
|
||||
//+ "00" // IsCookieToken
|
||||
//+ "01" // IsClaimsBased
|
||||
//+ "6F1648E97249AA58754036A67E248CF044F07ECFB0ED387556CE029A4F9A40E0" // ClaimUid
|
||||
//+ "05" // AdditionalData length header
|
||||
//+ "E282AC3437"; // AdditionalData ("€47") as UTF8
|
||||
var token = new AntiforgeryToken()
|
||||
{
|
||||
SecurityToken = _securityToken,
|
||||
IsCookieToken = false,
|
||||
ClaimUid = _claimUid,
|
||||
AdditionalData = "€47"
|
||||
};
|
||||
|
||||
// Act
|
||||
var actualSerializedData = testSerializer.Serialize(token);
|
||||
var deserializedToken = testSerializer.Deserialize(actualSerializedData);
|
||||
|
||||
// Assert
|
||||
AssertTokensEqual(token, deserializedToken);
|
||||
_dataProtector.Verify();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Serialize_FieldToken_WithUsername_TokenRoundTripSuccessful()
|
||||
{
|
||||
// Arrange
|
||||
var testSerializer = new DefaultAntiforgeryTokenSerializer(_dataProtector.Object, _pool);
|
||||
|
||||
//"01" // Version
|
||||
//+ "705EEDCC7D42F1D6B3B98A593625BB4C" // SecurityToken
|
||||
//+ "00" // IsCookieToken
|
||||
//+ "00" // IsClaimsBased
|
||||
//+ "08" // Username length header
|
||||
//+ "4AC3A972C3B46D65" // Username ("Jérôme") as UTF8
|
||||
//+ "05" // AdditionalData length header
|
||||
//+ "E282AC3437"; // AdditionalData ("€47") as UTF8
|
||||
var token = new AntiforgeryToken()
|
||||
{
|
||||
SecurityToken = _securityToken,
|
||||
IsCookieToken = false,
|
||||
Username = "Jérôme",
|
||||
AdditionalData = "€47"
|
||||
};
|
||||
|
||||
// Act
|
||||
var actualSerializedData = testSerializer.Serialize(token);
|
||||
var deserializedToken = testSerializer.Deserialize(actualSerializedData);
|
||||
|
||||
// Assert
|
||||
AssertTokensEqual(token, deserializedToken);
|
||||
_dataProtector.Verify();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Serialize_CookieToken_TokenRoundTripSuccessful()
|
||||
{
|
||||
// Arrange
|
||||
var testSerializer = new DefaultAntiforgeryTokenSerializer(_dataProtector.Object, _pool);
|
||||
|
||||
//"01" // Version
|
||||
//+ "705EEDCC7D42F1D6B3B98A593625BB4C" // SecurityToken
|
||||
//+ "01"; // IsCookieToken
|
||||
var token = new AntiforgeryToken()
|
||||
{
|
||||
SecurityToken = _securityToken,
|
||||
IsCookieToken = true
|
||||
};
|
||||
|
||||
// Act
|
||||
string actualSerializedData = testSerializer.Serialize(token);
|
||||
var deserializedToken = testSerializer.Deserialize(actualSerializedData);
|
||||
|
||||
// Assert
|
||||
AssertTokensEqual(token, deserializedToken);
|
||||
_dataProtector.Verify();
|
||||
}
|
||||
|
||||
private static Mock<IDataProtectionProvider> GetDataProtector()
|
||||
{
|
||||
var mockCryptoSystem = new Mock<IDataProtector>();
|
||||
mockCryptoSystem.Setup(o => o.Protect(It.IsAny<byte[]>()))
|
||||
.Returns<byte[]>(Protect)
|
||||
.Verifiable();
|
||||
mockCryptoSystem.Setup(o => o.Unprotect(It.IsAny<byte[]>()))
|
||||
.Returns<byte[]>(UnProtect)
|
||||
.Verifiable();
|
||||
|
||||
var provider = new Mock<IDataProtectionProvider>();
|
||||
provider
|
||||
.Setup(p => p.CreateProtector(It.IsAny<string>()))
|
||||
.Returns(mockCryptoSystem.Object);
|
||||
return provider;
|
||||
}
|
||||
|
||||
private static byte[] Protect(byte[] data)
|
||||
{
|
||||
var input = new List<byte>(data);
|
||||
input.Add(_salt);
|
||||
return input.ToArray();
|
||||
}
|
||||
|
||||
private static byte[] UnProtect(byte[] data)
|
||||
{
|
||||
var salt = data[data.Length - 1];
|
||||
if (salt != _salt)
|
||||
{
|
||||
throw new ArgumentException("Invalid salt value in data");
|
||||
}
|
||||
|
||||
return data.Take(data.Length - 1).ToArray();
|
||||
}
|
||||
|
||||
private static void AssertTokensEqual(AntiforgeryToken expected, AntiforgeryToken actual)
|
||||
{
|
||||
Assert.NotNull(expected);
|
||||
Assert.NotNull(actual);
|
||||
Assert.Equal(expected.AdditionalData, actual.AdditionalData);
|
||||
Assert.Equal(expected.ClaimUid, actual.ClaimUid);
|
||||
Assert.Equal(expected.IsCookieToken, actual.IsCookieToken);
|
||||
Assert.Equal(expected.SecurityToken, actual.SecurityToken);
|
||||
Assert.Equal(expected.Username, actual.Username);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,457 @@
|
|||
// 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.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Internal;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Antiforgery.Internal
|
||||
{
|
||||
public class DefaultAntiforgeryTokenStoreTest
|
||||
{
|
||||
private readonly string _cookieName = "cookie-name";
|
||||
|
||||
[Fact]
|
||||
public void GetCookieToken_CookieDoesNotExist_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
var httpContext = GetHttpContext(new RequestCookieCollection());
|
||||
var options = new AntiforgeryOptions
|
||||
{
|
||||
Cookie = { Name = _cookieName }
|
||||
};
|
||||
|
||||
var tokenStore = new DefaultAntiforgeryTokenStore(new TestOptionsManager(options));
|
||||
|
||||
// Act
|
||||
var token = tokenStore.GetCookieToken(httpContext);
|
||||
|
||||
// Assert
|
||||
Assert.Null(token);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCookieToken_CookieIsEmpty_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
var httpContext = GetHttpContext(_cookieName, string.Empty);
|
||||
var options = new AntiforgeryOptions
|
||||
{
|
||||
Cookie = { Name = _cookieName }
|
||||
};
|
||||
|
||||
var tokenStore = new DefaultAntiforgeryTokenStore(new TestOptionsManager(options));
|
||||
|
||||
// Act
|
||||
var token = tokenStore.GetCookieToken(httpContext);
|
||||
|
||||
// Assert
|
||||
Assert.Null(token);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCookieToken_CookieIsNotEmpty_ReturnsToken()
|
||||
{
|
||||
// Arrange
|
||||
var expectedToken = "valid-value";
|
||||
var httpContext = GetHttpContext(_cookieName, expectedToken);
|
||||
|
||||
var options = new AntiforgeryOptions
|
||||
{
|
||||
Cookie = { Name = _cookieName }
|
||||
};
|
||||
|
||||
var tokenStore = new DefaultAntiforgeryTokenStore(new TestOptionsManager(options));
|
||||
|
||||
// Act
|
||||
var token = tokenStore.GetCookieToken(httpContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedToken, token);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetRequestTokens_CookieIsEmpty_ReturnsNullTokens()
|
||||
{
|
||||
// Arrange
|
||||
var httpContext = GetHttpContext(new RequestCookieCollection());
|
||||
httpContext.Request.Form = FormCollection.Empty;
|
||||
|
||||
var options = new AntiforgeryOptions
|
||||
{
|
||||
Cookie = { Name = "cookie-name" },
|
||||
FormFieldName = "form-field-name",
|
||||
};
|
||||
|
||||
var tokenStore = new DefaultAntiforgeryTokenStore(new TestOptionsManager(options));
|
||||
|
||||
// Act
|
||||
var tokenSet = await tokenStore.GetRequestTokensAsync(httpContext);
|
||||
|
||||
// Assert
|
||||
Assert.Null(tokenSet.CookieToken);
|
||||
Assert.Null(tokenSet.RequestToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetRequestTokens_HeaderTokenTakensPriority_OverFormToken()
|
||||
{
|
||||
// Arrange
|
||||
var httpContext = GetHttpContext("cookie-name", "cookie-value");
|
||||
httpContext.Request.ContentType = "application/x-www-form-urlencoded";
|
||||
httpContext.Request.Form = new FormCollection(new Dictionary<string, StringValues>
|
||||
{
|
||||
{ "form-field-name", "form-value" },
|
||||
}); // header value has priority.
|
||||
httpContext.Request.Headers.Add("header-name", "header-value");
|
||||
|
||||
var options = new AntiforgeryOptions
|
||||
{
|
||||
Cookie = { Name = "cookie-name" },
|
||||
FormFieldName = "form-field-name",
|
||||
HeaderName = "header-name",
|
||||
};
|
||||
|
||||
var tokenStore = new DefaultAntiforgeryTokenStore(new TestOptionsManager(options));
|
||||
|
||||
// Act
|
||||
var tokens = await tokenStore.GetRequestTokensAsync(httpContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("cookie-value", tokens.CookieToken);
|
||||
Assert.Equal("header-value", tokens.RequestToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetRequestTokens_NoHeaderToken_FallsBackToFormToken()
|
||||
{
|
||||
// Arrange
|
||||
var httpContext = GetHttpContext("cookie-name", "cookie-value");
|
||||
httpContext.Request.ContentType = "application/x-www-form-urlencoded";
|
||||
httpContext.Request.Form = new FormCollection(new Dictionary<string, StringValues>
|
||||
{
|
||||
{ "form-field-name", "form-value" },
|
||||
});
|
||||
|
||||
var options = new AntiforgeryOptions
|
||||
{
|
||||
Cookie = { Name = "cookie-name" },
|
||||
FormFieldName = "form-field-name",
|
||||
HeaderName = "header-name",
|
||||
};
|
||||
|
||||
var tokenStore = new DefaultAntiforgeryTokenStore(new TestOptionsManager(options));
|
||||
|
||||
// Act
|
||||
var tokens = await tokenStore.GetRequestTokensAsync(httpContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("cookie-value", tokens.CookieToken);
|
||||
Assert.Equal("form-value", tokens.RequestToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetRequestTokens_NonFormContentType_UsesHeaderToken()
|
||||
{
|
||||
// Arrange
|
||||
var httpContext = GetHttpContext("cookie-name", "cookie-value");
|
||||
httpContext.Request.ContentType = "application/json";
|
||||
httpContext.Request.Headers.Add("header-name", "header-value");
|
||||
|
||||
// Will not be accessed
|
||||
httpContext.Request.Form = null;
|
||||
|
||||
var options = new AntiforgeryOptions
|
||||
{
|
||||
Cookie = { Name = "cookie-name" },
|
||||
FormFieldName = "form-field-name",
|
||||
HeaderName = "header-name",
|
||||
};
|
||||
|
||||
var tokenStore = new DefaultAntiforgeryTokenStore(new TestOptionsManager(options));
|
||||
|
||||
// Act
|
||||
var tokens = await tokenStore.GetRequestTokensAsync(httpContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("cookie-value", tokens.CookieToken);
|
||||
Assert.Equal("header-value", tokens.RequestToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetRequestTokens_NoHeaderToken_NonFormContentType_ReturnsNullToken()
|
||||
{
|
||||
// Arrange
|
||||
var httpContext = GetHttpContext("cookie-name", "cookie-value");
|
||||
httpContext.Request.ContentType = "application/json";
|
||||
|
||||
// Will not be accessed
|
||||
httpContext.Request.Form = null;
|
||||
|
||||
var options = new AntiforgeryOptions
|
||||
{
|
||||
Cookie = { Name = "cookie-name" },
|
||||
FormFieldName = "form-field-name",
|
||||
HeaderName = "header-name",
|
||||
};
|
||||
|
||||
var tokenStore = new DefaultAntiforgeryTokenStore(new TestOptionsManager(options));
|
||||
|
||||
// Act
|
||||
var tokenSet = await tokenStore.GetRequestTokensAsync(httpContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("cookie-value", tokenSet.CookieToken);
|
||||
Assert.Null(tokenSet.RequestToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetRequestTokens_BothHeaderValueAndFormFieldsEmpty_ReturnsNullTokens()
|
||||
{
|
||||
// Arrange
|
||||
var httpContext = GetHttpContext("cookie-name", "cookie-value");
|
||||
httpContext.Request.ContentType = "application/x-www-form-urlencoded";
|
||||
httpContext.Request.Form = FormCollection.Empty;
|
||||
|
||||
var options = new AntiforgeryOptions
|
||||
{
|
||||
Cookie = { Name = "cookie-name" },
|
||||
FormFieldName = "form-field-name",
|
||||
HeaderName = "header-name",
|
||||
};
|
||||
|
||||
var tokenStore = new DefaultAntiforgeryTokenStore(new TestOptionsManager(options));
|
||||
|
||||
// Act
|
||||
var tokenSet = await tokenStore.GetRequestTokensAsync(httpContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("cookie-value", tokenSet.CookieToken);
|
||||
Assert.Null(tokenSet.RequestToken);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false, CookieSecurePolicy.SameAsRequest, null)]
|
||||
[InlineData(true, CookieSecurePolicy.SameAsRequest, true)]
|
||||
[InlineData(false, CookieSecurePolicy.Always, true)]
|
||||
[InlineData(true, CookieSecurePolicy.Always, true)]
|
||||
[InlineData(false, CookieSecurePolicy.None, null)]
|
||||
[InlineData(true, CookieSecurePolicy.None, null)]
|
||||
public void SaveCookieToken_HonorsCookieSecurePolicy_OnOptions(
|
||||
bool isRequestSecure,
|
||||
CookieSecurePolicy policy,
|
||||
bool? expectedCookieSecureFlag)
|
||||
{
|
||||
// Arrange
|
||||
var token = "serialized-value";
|
||||
bool defaultCookieSecureValue = expectedCookieSecureFlag ?? false; // pulled from config; set by ctor
|
||||
var cookies = new MockResponseCookieCollection();
|
||||
|
||||
var httpContext = new Mock<HttpContext>();
|
||||
httpContext
|
||||
.Setup(hc => hc.Request.IsHttps)
|
||||
.Returns(isRequestSecure);
|
||||
httpContext
|
||||
.Setup(o => o.Response.Cookies)
|
||||
.Returns(cookies);
|
||||
httpContext
|
||||
.SetupGet(hc => hc.Request.PathBase)
|
||||
.Returns("/");
|
||||
|
||||
var options = new AntiforgeryOptions()
|
||||
{
|
||||
Cookie =
|
||||
{
|
||||
Name = _cookieName,
|
||||
SecurePolicy = policy
|
||||
},
|
||||
};
|
||||
|
||||
var tokenStore = new DefaultAntiforgeryTokenStore(new TestOptionsManager(options));
|
||||
|
||||
// Act
|
||||
tokenStore.SaveCookieToken(httpContext.Object, token);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, cookies.Count);
|
||||
Assert.NotNull(cookies);
|
||||
Assert.Equal(_cookieName, cookies.Key);
|
||||
Assert.Equal("serialized-value", cookies.Value);
|
||||
Assert.True(cookies.Options.HttpOnly);
|
||||
Assert.Equal(defaultCookieSecureValue, cookies.Options.Secure);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null, "/")]
|
||||
[InlineData("", "/")]
|
||||
[InlineData("/", "/")]
|
||||
[InlineData("/vdir1", "/vdir1")]
|
||||
[InlineData("/vdir1/vdir2", "/vdir1/vdir2")]
|
||||
public void SaveCookieToken_SetsCookieWithApproriatePathBase(string requestPathBase, string expectedCookiePath)
|
||||
{
|
||||
// Arrange
|
||||
var token = "serialized-value";
|
||||
var cookies = new MockResponseCookieCollection();
|
||||
var httpContext = new Mock<HttpContext>();
|
||||
httpContext
|
||||
.Setup(hc => hc.Response.Cookies)
|
||||
.Returns(cookies);
|
||||
httpContext
|
||||
.SetupGet(hc => hc.Request.PathBase)
|
||||
.Returns(requestPathBase);
|
||||
httpContext
|
||||
.SetupGet(hc => hc.Request.Path)
|
||||
.Returns("/index.html");
|
||||
var options = new AntiforgeryOptions
|
||||
{
|
||||
Cookie = { Name = _cookieName }
|
||||
};
|
||||
var tokenStore = new DefaultAntiforgeryTokenStore(new TestOptionsManager(options));
|
||||
|
||||
// Act
|
||||
tokenStore.SaveCookieToken(httpContext.Object, token);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, cookies.Count);
|
||||
Assert.NotNull(cookies);
|
||||
Assert.Equal(_cookieName, cookies.Key);
|
||||
Assert.Equal("serialized-value", cookies.Value);
|
||||
Assert.True(cookies.Options.HttpOnly);
|
||||
Assert.Equal(expectedCookiePath, cookies.Options.Path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SaveCookieToken_NonNullAntiforgeryOptionsConfigureCookieOptionsPath_UsesCookieOptionsPath()
|
||||
{
|
||||
// Arrange
|
||||
var expectedCookiePath = "/";
|
||||
var requestPathBase = "/vdir1";
|
||||
var token = "serialized-value";
|
||||
var cookies = new MockResponseCookieCollection();
|
||||
var httpContext = new Mock<HttpContext>();
|
||||
httpContext
|
||||
.Setup(hc => hc.Response.Cookies)
|
||||
.Returns(cookies);
|
||||
httpContext
|
||||
.SetupGet(hc => hc.Request.PathBase)
|
||||
.Returns(requestPathBase);
|
||||
httpContext
|
||||
.SetupGet(hc => hc.Request.Path)
|
||||
.Returns("/index.html");
|
||||
var options = new AntiforgeryOptions
|
||||
{
|
||||
Cookie =
|
||||
{
|
||||
Name = _cookieName,
|
||||
Path = expectedCookiePath
|
||||
}
|
||||
};
|
||||
var tokenStore = new DefaultAntiforgeryTokenStore(new TestOptionsManager(options));
|
||||
|
||||
// Act
|
||||
tokenStore.SaveCookieToken(httpContext.Object, token);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, cookies.Count);
|
||||
Assert.NotNull(cookies);
|
||||
Assert.Equal(_cookieName, cookies.Key);
|
||||
Assert.Equal("serialized-value", cookies.Value);
|
||||
Assert.True(cookies.Options.HttpOnly);
|
||||
Assert.Equal(expectedCookiePath, cookies.Options.Path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SaveCookieToken_NonNullAntiforgeryOptionsConfigureCookieOptionsDomain_UsesCookieOptionsDomain()
|
||||
{
|
||||
// Arrange
|
||||
var expectedCookieDomain = "microsoft.com";
|
||||
var token = "serialized-value";
|
||||
var cookies = new MockResponseCookieCollection();
|
||||
var httpContext = new Mock<HttpContext>();
|
||||
httpContext
|
||||
.Setup(hc => hc.Response.Cookies)
|
||||
.Returns(cookies);
|
||||
httpContext
|
||||
.SetupGet(hc => hc.Request.PathBase)
|
||||
.Returns("/vdir1");
|
||||
httpContext
|
||||
.SetupGet(hc => hc.Request.Path)
|
||||
.Returns("/index.html");
|
||||
var options = new AntiforgeryOptions
|
||||
{
|
||||
Cookie =
|
||||
{
|
||||
Name = _cookieName,
|
||||
Domain = expectedCookieDomain
|
||||
}
|
||||
};
|
||||
var tokenStore = new DefaultAntiforgeryTokenStore(new TestOptionsManager(options));
|
||||
|
||||
// Act
|
||||
tokenStore.SaveCookieToken(httpContext.Object, token);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, cookies.Count);
|
||||
Assert.NotNull(cookies);
|
||||
Assert.Equal(_cookieName, cookies.Key);
|
||||
Assert.Equal("serialized-value", cookies.Value);
|
||||
Assert.True(cookies.Options.HttpOnly);
|
||||
Assert.Equal("/vdir1", cookies.Options.Path);
|
||||
Assert.Equal(expectedCookieDomain, cookies.Options.Domain);
|
||||
}
|
||||
|
||||
private HttpContext GetHttpContext(string cookieName, string cookieValue)
|
||||
{
|
||||
var cookies = new RequestCookieCollection(new Dictionary<string, string>
|
||||
{
|
||||
{ cookieName, cookieValue },
|
||||
});
|
||||
|
||||
return GetHttpContext(cookies);
|
||||
}
|
||||
|
||||
private HttpContext GetHttpContext(IRequestCookieCollection cookies)
|
||||
{
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.Request.Cookies = cookies;
|
||||
|
||||
return httpContext;
|
||||
}
|
||||
|
||||
private class MockResponseCookieCollection : IResponseCookies
|
||||
{
|
||||
public string Key { get; set; }
|
||||
public string Value { get; set; }
|
||||
public CookieOptions Options { get; set; }
|
||||
public int Count { get; set; }
|
||||
|
||||
public void Append(string key, string value, CookieOptions options)
|
||||
{
|
||||
Key = key;
|
||||
Value = value;
|
||||
Options = options;
|
||||
Count++;
|
||||
}
|
||||
|
||||
public void Append(string key, string value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Delete(string key, CookieOptions options)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Delete(string key)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,261 @@
|
|||
// 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.Security.Claims;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Antiforgery.Internal
|
||||
{
|
||||
public class DefaultClaimUidExtractorTest
|
||||
{
|
||||
private static readonly ObjectPool<AntiforgerySerializationContext> _pool =
|
||||
new DefaultObjectPoolProvider().Create(new AntiforgerySerializationContextPooledObjectPolicy());
|
||||
|
||||
[Fact]
|
||||
public void ExtractClaimUid_Unauthenticated()
|
||||
{
|
||||
// Arrange
|
||||
var extractor = new DefaultClaimUidExtractor(_pool);
|
||||
|
||||
var mockIdentity = new Mock<ClaimsIdentity>();
|
||||
mockIdentity.Setup(o => o.IsAuthenticated)
|
||||
.Returns(false);
|
||||
|
||||
// Act
|
||||
var claimUid = extractor.ExtractClaimUid(new ClaimsPrincipal(mockIdentity.Object));
|
||||
|
||||
// Assert
|
||||
Assert.Null(claimUid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExtractClaimUid_ClaimsIdentity()
|
||||
{
|
||||
// Arrange
|
||||
var mockIdentity = new Mock<ClaimsIdentity>();
|
||||
mockIdentity.Setup(o => o.IsAuthenticated)
|
||||
.Returns(true);
|
||||
mockIdentity.Setup(o => o.Claims).Returns(new Claim[] { new Claim(ClaimTypes.Name, "someName") });
|
||||
|
||||
var extractor = new DefaultClaimUidExtractor(_pool);
|
||||
|
||||
// Act
|
||||
var claimUid = extractor.ExtractClaimUid(new ClaimsPrincipal(mockIdentity.Object ));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(claimUid);
|
||||
Assert.Equal("yhXE+2v4zSXHtRHmzm4cmrhZca2J0g7yTUwtUerdeF4=", claimUid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DefaultUniqueClaimTypes_NotPresent_SerializesAllClaimTypes()
|
||||
{
|
||||
var identity = new ClaimsIdentity("someAuthentication");
|
||||
identity.AddClaim(new Claim(ClaimTypes.Email, "someone@antifrogery.com"));
|
||||
identity.AddClaim(new Claim(ClaimTypes.GivenName, "some"));
|
||||
identity.AddClaim(new Claim(ClaimTypes.Surname, "one"));
|
||||
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, string.Empty));
|
||||
|
||||
// Arrange
|
||||
var claimsIdentity = (ClaimsIdentity)identity;
|
||||
|
||||
// Act
|
||||
var identiferParameters = DefaultClaimUidExtractor.GetUniqueIdentifierParameters(new ClaimsIdentity[] { claimsIdentity })
|
||||
.ToArray();
|
||||
var claims = claimsIdentity.Claims.ToList();
|
||||
claims.Sort((a, b) => string.Compare(a.Type, b.Type, StringComparison.Ordinal));
|
||||
|
||||
// Assert
|
||||
int index = 0;
|
||||
foreach (var claim in claims)
|
||||
{
|
||||
Assert.Equal(identiferParameters[index++], claim.Type);
|
||||
Assert.Equal(identiferParameters[index++], claim.Value);
|
||||
Assert.Equal(identiferParameters[index++], claim.Issuer);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DefaultUniqueClaimTypes_Present()
|
||||
{
|
||||
// Arrange
|
||||
var identity = new ClaimsIdentity("someAuthentication");
|
||||
identity.AddClaim(new Claim("fooClaim", "fooClaimValue"));
|
||||
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, "nameIdentifierValue"));
|
||||
|
||||
// Act
|
||||
var uniqueIdentifierParameters = DefaultClaimUidExtractor.GetUniqueIdentifierParameters(new ClaimsIdentity[] { identity });
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new string[]
|
||||
{
|
||||
ClaimTypes.NameIdentifier,
|
||||
"nameIdentifierValue",
|
||||
"LOCAL AUTHORITY",
|
||||
}, uniqueIdentifierParameters);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetUniqueIdentifierParameters_PrefersSubClaimOverNameIdentifierAndUpn()
|
||||
{
|
||||
// Arrange
|
||||
var identity = new ClaimsIdentity("someAuthentication");
|
||||
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, "nameIdentifierValue"));
|
||||
identity.AddClaim(new Claim("sub", "subClaimValue"));
|
||||
identity.AddClaim(new Claim(ClaimTypes.Upn, "upnClaimValue"));
|
||||
|
||||
// Act
|
||||
var uniqueIdentifierParameters = DefaultClaimUidExtractor.GetUniqueIdentifierParameters(new ClaimsIdentity[] { identity });
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new string[]
|
||||
{
|
||||
"sub",
|
||||
"subClaimValue",
|
||||
"LOCAL AUTHORITY",
|
||||
}, uniqueIdentifierParameters);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetUniqueIdentifierParameters_PrefersNameIdentifierOverUpn()
|
||||
{
|
||||
// Arrange
|
||||
var identity = new ClaimsIdentity("someAuthentication");
|
||||
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, "nameIdentifierValue"));
|
||||
identity.AddClaim(new Claim(ClaimTypes.Upn, "upnClaimValue"));
|
||||
|
||||
// Act
|
||||
var uniqueIdentifierParameters = DefaultClaimUidExtractor.GetUniqueIdentifierParameters(new ClaimsIdentity[] { identity });
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new string[]
|
||||
{
|
||||
ClaimTypes.NameIdentifier,
|
||||
"nameIdentifierValue",
|
||||
"LOCAL AUTHORITY",
|
||||
}, uniqueIdentifierParameters);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetUniqueIdentifierParameters_UsesUpnIfPresent()
|
||||
{
|
||||
// Arrange
|
||||
var identity = new ClaimsIdentity("someAuthentication");
|
||||
identity.AddClaim(new Claim("fooClaim", "fooClaimValue"));
|
||||
identity.AddClaim(new Claim(ClaimTypes.Upn, "upnClaimValue"));
|
||||
|
||||
// Act
|
||||
var uniqueIdentifierParameters = DefaultClaimUidExtractor.GetUniqueIdentifierParameters(new ClaimsIdentity[] { identity });
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new string[]
|
||||
{
|
||||
ClaimTypes.Upn,
|
||||
"upnClaimValue",
|
||||
"LOCAL AUTHORITY",
|
||||
}, uniqueIdentifierParameters);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetUniqueIdentifierParameters_MultipleIdentities_UsesOnlyAuthenticatedIdentities()
|
||||
{
|
||||
// Arrange
|
||||
var identity1 = new ClaimsIdentity(); // no authentication
|
||||
identity1.AddClaim(new Claim("sub", "subClaimValue"));
|
||||
var identity2 = new ClaimsIdentity("someAuthentication");
|
||||
identity2.AddClaim(new Claim(ClaimTypes.NameIdentifier, "nameIdentifierValue"));
|
||||
|
||||
// Act
|
||||
var uniqueIdentifierParameters = DefaultClaimUidExtractor.GetUniqueIdentifierParameters(new ClaimsIdentity[] { identity1, identity2 });
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new string[]
|
||||
{
|
||||
ClaimTypes.NameIdentifier,
|
||||
"nameIdentifierValue",
|
||||
"LOCAL AUTHORITY",
|
||||
}, uniqueIdentifierParameters);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetUniqueIdentifierParameters_NoKnownClaimTypesFound_SortsAndReturnsAllClaimsFromAuthenticatedIdentities()
|
||||
{
|
||||
// Arrange
|
||||
var identity1 = new ClaimsIdentity(); // no authentication
|
||||
identity1.AddClaim(new Claim("sub", "subClaimValue"));
|
||||
var identity2 = new ClaimsIdentity("someAuthentication");
|
||||
identity2.AddClaim(new Claim(ClaimTypes.Email, "email@domain.com"));
|
||||
var identity3 = new ClaimsIdentity("someAuthentication");
|
||||
identity3.AddClaim(new Claim(ClaimTypes.Country, "countryValue"));
|
||||
var identity4 = new ClaimsIdentity("someAuthentication");
|
||||
identity4.AddClaim(new Claim(ClaimTypes.Name, "claimName"));
|
||||
|
||||
// Act
|
||||
var uniqueIdentifierParameters = DefaultClaimUidExtractor.GetUniqueIdentifierParameters(
|
||||
new ClaimsIdentity[] { identity1, identity2, identity3, identity4 });
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new List<string>
|
||||
{
|
||||
ClaimTypes.Country,
|
||||
"countryValue",
|
||||
"LOCAL AUTHORITY",
|
||||
ClaimTypes.Email,
|
||||
"email@domain.com",
|
||||
"LOCAL AUTHORITY",
|
||||
ClaimTypes.Name,
|
||||
"claimName",
|
||||
"LOCAL AUTHORITY",
|
||||
}, uniqueIdentifierParameters);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetUniqueIdentifierParameters_PrefersNameFromFirstIdentity_OverSubFromSecondIdentity()
|
||||
{
|
||||
// Arrange
|
||||
var identity1 = new ClaimsIdentity("someAuthentication");
|
||||
identity1.AddClaim(new Claim(ClaimTypes.NameIdentifier, "nameIdentifierValue"));
|
||||
var identity2 = new ClaimsIdentity("someAuthentication");
|
||||
identity2.AddClaim(new Claim("sub", "subClaimValue"));
|
||||
|
||||
// Act
|
||||
var uniqueIdentifierParameters = DefaultClaimUidExtractor.GetUniqueIdentifierParameters(
|
||||
new ClaimsIdentity[] { identity1, identity2 });
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new string[]
|
||||
{
|
||||
ClaimTypes.NameIdentifier,
|
||||
"nameIdentifierValue",
|
||||
"LOCAL AUTHORITY",
|
||||
}, uniqueIdentifierParameters);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetUniqueIdentifierParameters_PrefersUpnFromFirstIdentity_OverNameFromSecondIdentity()
|
||||
{
|
||||
// Arrange
|
||||
var identity1 = new ClaimsIdentity("someAuthentication");
|
||||
identity1.AddClaim(new Claim(ClaimTypes.Upn, "upnValue"));
|
||||
var identity2 = new ClaimsIdentity("someAuthentication");
|
||||
identity2.AddClaim(new Claim(ClaimTypes.NameIdentifier, "nameIdentifierValue"));
|
||||
|
||||
// Act
|
||||
var uniqueIdentifierParameters = DefaultClaimUidExtractor.GetUniqueIdentifierParameters(
|
||||
new ClaimsIdentity[] { identity1, identity2 });
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new string[]
|
||||
{
|
||||
ClaimTypes.Upn,
|
||||
"upnValue",
|
||||
"LOCAL AUTHORITY",
|
||||
}, uniqueIdentifierParameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Antiforgery\Microsoft.AspNetCore.Antiforgery.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http" Version="$(MicrosoftAspNetCoreHttpPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Testing" Version="$(MicrosoftAspNetCoreTestingPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="$(MicrosoftExtensionsDependencyInjectionPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Testing" Version="$(MicrosoftExtensionsLoggingTestingPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="$(MicrosoftExtensionsLoggingPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.WebEncoders" Version="$(MicrosoftExtensionsWebEncodersPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPackageVersion)" />
|
||||
<PackageReference Include="Moq" Version="$(MoqPackageVersion)" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitRunnerVisualStudioPackageVersion)" />
|
||||
<PackageReference Include="xunit" Version="$(XunitPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Antiforgery
|
||||
{
|
||||
public class TestOptionsManager : IOptions<AntiforgeryOptions>
|
||||
{
|
||||
public TestOptionsManager()
|
||||
{
|
||||
}
|
||||
|
||||
public TestOptionsManager(AntiforgeryOptions options)
|
||||
{
|
||||
Value = options;
|
||||
}
|
||||
|
||||
public AntiforgeryOptions Value { get; set; } = new AntiforgeryOptions();
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<Product>Microsoft ASP.NET Core</Product>
|
||||
<RepositoryUrl>https://github.com/aspnet/AuthSamples</RepositoryUrl>
|
||||
<RepositoryUrl>https://github.com/aspnet/AspNetCore</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<RepositoryRoot>$(MSBuildThisFileDirectory)</RepositoryRoot>
|
||||
</PropertyGroup>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<Product>Microsoft ASP.NET Core</Product>
|
||||
<RepositoryUrl>https://github.com/aspnet/AzureIntegration</RepositoryUrl>
|
||||
<RepositoryUrl>https://github.com/aspnet/AspNetCore</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<RepositoryRoot>$(MSBuildThisFileDirectory)</RepositoryRoot>
|
||||
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)..\..\eng\AspNetCore.snk</AssemblyOriginatorKeyFile>
|
||||
|
|
|
|||
|
|
@ -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/AspNetCore</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));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue