Merge remote-tracking branch 'origin/release/2.1' into rybrande/Mondo2.2

This commit is contained in:
Ryan Brandenburg 2018-11-21 16:33:42 -08:00
commit 1ee4e2b936
864 changed files with 131878 additions and 53 deletions

24
.gitmodules vendored
View File

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

View File

@ -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\" />

View File

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

View File

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

40
src/Antiforgery/.gitignore vendored Normal file
View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,7 @@
{
"Default": {
"rules": [
"DefaultCompositeRule"
]
}
}

14
src/Antiforgery/README.md Normal file
View File

@ -0,0 +1,14 @@
Antiforgery
===========
AppVeyor: [![AppVeyor](https://ci.appveyor.com/api/projects/status/17l06rulbn328v4k/branch/dev?svg=true)](https://ci.appveyor.com/project/aspnetci/Antiforgery/branch/dev)
Travis: [![Travis](https://travis-ci.org/aspnet/Antiforgery.svg?branch=dev)](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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,21 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.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);
}
}

View File

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

View File

@ -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")]

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

View File

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

View File

@ -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": []
}
]
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,21 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using 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();
}
}

View File

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

View File

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

View File

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

41
src/CORS/.gitignore vendored Normal file
View File

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

89
src/CORS/CORS.sln Normal file
View File

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

View File

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

View File

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

View File

@ -0,0 +1,7 @@
{
"Default": {
"rules": [
"DefaultCompositeRule"
]
}
}

9
src/CORS/README.md Normal file
View File

@ -0,0 +1,9 @@
CORS
===
AppVeyor: [![AppVeyor](https://ci.appveyor.com/api/projects/status/yi0m8evjtg107o12/branch/dev?svg=true)](https://ci.appveyor.com/project/aspnetci/CORS/branch/dev)
Travis: [![Travis](https://travis-ci.org/aspnet/CORS.svg?branch=dev)](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.

BIN
src/CORS/build/Key.snk Normal file

Binary file not shown.

View File

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

View File

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

View File

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

View File

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

View File

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

15
src/CORS/build/repo.props Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,8 @@
<Project>
<Import Project="..\Directory.Build.props" />
<ItemGroup>
<PackageReference Include="Internal.AspNetCore.Sdk" PrivateAssets="All" Version="$(InternalAspNetCoreSdkPackageVersion)" />
</ItemGroup>
</Project>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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