Add CookiePolicy Middleware
This commit is contained in:
parent
1c0768fb71
commit
5cc1fea400
30
Security.sln
30
Security.sln
|
|
@ -46,6 +46,10 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Authorizat
|
|||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Authorization", "src\Microsoft.AspNet.Authorization\Microsoft.AspNet.Authorization.xproj", "{6AB3E514-5894-4131-9399-DC7D5284ADDB}"
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.CookiePolicy", "src\Microsoft.AspNet.CookiePolicy\Microsoft.AspNet.CookiePolicy.xproj", "{86183DC3-02A8-4A68-8B60-71ECEC066E79}"
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.CookiePolicy.Test", "test\Microsoft.AspNet.CookiePolicy.Test\Microsoft.AspNet.CookiePolicy.Test.xproj", "{1790E052-646F-4529-B90E-6FEA95520D69}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -242,6 +246,30 @@ Global
|
|||
{6AB3E514-5894-4131-9399-DC7D5284ADDB}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{6AB3E514-5894-4131-9399-DC7D5284ADDB}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{6AB3E514-5894-4131-9399-DC7D5284ADDB}.Release|x86.Build.0 = Release|Any CPU
|
||||
{86183DC3-02A8-4A68-8B60-71ECEC066E79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{86183DC3-02A8-4A68-8B60-71ECEC066E79}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{86183DC3-02A8-4A68-8B60-71ECEC066E79}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{86183DC3-02A8-4A68-8B60-71ECEC066E79}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{86183DC3-02A8-4A68-8B60-71ECEC066E79}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{86183DC3-02A8-4A68-8B60-71ECEC066E79}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{86183DC3-02A8-4A68-8B60-71ECEC066E79}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{86183DC3-02A8-4A68-8B60-71ECEC066E79}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{86183DC3-02A8-4A68-8B60-71ECEC066E79}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{86183DC3-02A8-4A68-8B60-71ECEC066E79}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{86183DC3-02A8-4A68-8B60-71ECEC066E79}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{86183DC3-02A8-4A68-8B60-71ECEC066E79}.Release|x86.Build.0 = Release|Any CPU
|
||||
{1790E052-646F-4529-B90E-6FEA95520D69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1790E052-646F-4529-B90E-6FEA95520D69}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1790E052-646F-4529-B90E-6FEA95520D69}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{1790E052-646F-4529-B90E-6FEA95520D69}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{1790E052-646F-4529-B90E-6FEA95520D69}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{1790E052-646F-4529-B90E-6FEA95520D69}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{1790E052-646F-4529-B90E-6FEA95520D69}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1790E052-646F-4529-B90E-6FEA95520D69}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{1790E052-646F-4529-B90E-6FEA95520D69}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{1790E052-646F-4529-B90E-6FEA95520D69}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{1790E052-646F-4529-B90E-6FEA95520D69}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{1790E052-646F-4529-B90E-6FEA95520D69}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -263,5 +291,7 @@ Global
|
|||
{2755BFE5-7421-4A31-A644-F817DF5CAA98} = {4D2B6A51-2F9F-44F5-8131-EA5CAC053652}
|
||||
{7AF5AD96-EB6E-4D0E-8ABE-C0B543C0F4C2} = {7BF11F3A-60B6-4796-B504-579C67FFBA34}
|
||||
{6AB3E514-5894-4131-9399-DC7D5284ADDB} = {4D2B6A51-2F9F-44F5-8131-EA5CAC053652}
|
||||
{86183DC3-02A8-4A68-8B60-71ECEC066E79} = {4D2B6A51-2F9F-44F5-8131-EA5CAC053652}
|
||||
{1790E052-646F-4529-B90E-6FEA95520D69} = {7BF11F3A-60B6-4796-B504-579C67FFBA34}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
|||
|
|
@ -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.AspNet.Http;
|
||||
|
||||
namespace Microsoft.AspNet.CookiePolicy
|
||||
{
|
||||
public class AppendCookieContext
|
||||
{
|
||||
public AppendCookieContext(HttpContext context, CookieOptions options, string name, string value)
|
||||
{
|
||||
Context = context;
|
||||
CookieOptions = options;
|
||||
CookieName = name;
|
||||
CookieValue = value;
|
||||
}
|
||||
|
||||
public HttpContext Context { get; }
|
||||
public CookieOptions CookieOptions { get; }
|
||||
public string CookieName { get; set; }
|
||||
public string CookieValue { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
// 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.AspNet.CookiePolicy;
|
||||
|
||||
namespace Microsoft.AspNet.Builder
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods provided by the cookie policy middleware
|
||||
/// </summary>
|
||||
public static class CookiePolicyAppBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a cookie policy middleware to your web application pipeline.
|
||||
/// </summary>
|
||||
/// <param name="app">The IApplicationBuilder passed to your configuration method</param>
|
||||
/// <param name="options">The options for the middleware</param>
|
||||
/// <returns>The original app parameter</returns>
|
||||
public static IApplicationBuilder UseCookiePolicy(this IApplicationBuilder app, CookiePolicyOptions options)
|
||||
{
|
||||
return app.UseMiddleware<CookiePolicyMiddleware>(options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a cookie policy middleware to your web application pipeline.
|
||||
/// </summary>
|
||||
/// <param name="app">The IApplicationBuilder passed to your configuration method</param>
|
||||
/// <param name="configureOptions">Used to configure the options for the middleware</param>
|
||||
/// <returns>The original app parameter</returns>
|
||||
public static IApplicationBuilder UseCookiePolicy(this IApplicationBuilder app, Action<CookiePolicyOptions> configureOptions)
|
||||
{
|
||||
var options = new CookiePolicyOptions();
|
||||
if (configureOptions != null)
|
||||
{
|
||||
configureOptions(options);
|
||||
}
|
||||
return app.UseCookiePolicy(options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
// 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.AspNet.Builder;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Features;
|
||||
using Microsoft.AspNet.Http.Features.Internal;
|
||||
|
||||
namespace Microsoft.AspNet.CookiePolicy
|
||||
{
|
||||
public class CookiePolicyMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
|
||||
public CookiePolicyMiddleware(
|
||||
RequestDelegate next,
|
||||
CookiePolicyOptions options)
|
||||
{
|
||||
Options = options;
|
||||
_next = next;
|
||||
}
|
||||
|
||||
public CookiePolicyOptions Options { get; set; }
|
||||
|
||||
public Task Invoke(HttpContext context)
|
||||
{
|
||||
var feature = context.Features.Get<IResponseCookiesFeature>() ?? new ResponseCookiesFeature(context.Features);
|
||||
context.Features.Set<IResponseCookiesFeature>(new CookiesWrapperFeature(context, Options, feature));
|
||||
return _next(context);
|
||||
}
|
||||
|
||||
private class CookiesWrapperFeature : IResponseCookiesFeature
|
||||
{
|
||||
public CookiesWrapperFeature(HttpContext context, CookiePolicyOptions options, IResponseCookiesFeature feature)
|
||||
{
|
||||
Wrapper = new CookiesWrapper(context, options, feature);
|
||||
}
|
||||
|
||||
public IResponseCookies Wrapper { get; }
|
||||
|
||||
public IResponseCookies Cookies
|
||||
{
|
||||
get
|
||||
{
|
||||
return Wrapper;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class CookiesWrapper : IResponseCookies
|
||||
{
|
||||
public CookiesWrapper(HttpContext context, CookiePolicyOptions options, IResponseCookiesFeature feature)
|
||||
{
|
||||
Context = context;
|
||||
Feature = feature;
|
||||
Policy = options;
|
||||
}
|
||||
|
||||
public HttpContext Context { get; }
|
||||
|
||||
public IResponseCookiesFeature Feature { get; }
|
||||
|
||||
public IResponseCookies Cookies
|
||||
{
|
||||
get
|
||||
{
|
||||
return Feature.Cookies;
|
||||
}
|
||||
}
|
||||
|
||||
public CookiePolicyOptions Policy { get; }
|
||||
|
||||
private bool PolicyRequiresCookieOptions()
|
||||
{
|
||||
return Policy.HttpOnly != HttpOnlyPolicy.None || Policy.Secure != SecurePolicy.None;
|
||||
}
|
||||
|
||||
public void Append(string key, string value)
|
||||
{
|
||||
if (PolicyRequiresCookieOptions() || Policy.OnAppendCookie != null)
|
||||
{
|
||||
Append(key, value, new CookieOptions());
|
||||
}
|
||||
else
|
||||
{
|
||||
Cookies.Append(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void Append(string key, string value, CookieOptions options)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
ApplyPolicy(options);
|
||||
if (Policy.OnAppendCookie != null)
|
||||
{
|
||||
var context = new AppendCookieContext(Context, options, key, value);
|
||||
Policy.OnAppendCookie(context);
|
||||
key = context.CookieName;
|
||||
value = context.CookieValue;
|
||||
}
|
||||
Cookies.Append(key, value, options);
|
||||
}
|
||||
|
||||
public void Delete(string key)
|
||||
{
|
||||
if (PolicyRequiresCookieOptions() || Policy.OnDeleteCookie != null)
|
||||
{
|
||||
Delete(key, new CookieOptions());
|
||||
}
|
||||
else
|
||||
{
|
||||
Cookies.Delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
public void Delete(string key, CookieOptions options)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
ApplyPolicy(options);
|
||||
if (Policy.OnDeleteCookie != null)
|
||||
{
|
||||
var context = new DeleteCookieContext(Context, options, key);
|
||||
Policy.OnDeleteCookie(context);
|
||||
key = context.CookieName;
|
||||
}
|
||||
Cookies.Delete(key, options);
|
||||
}
|
||||
|
||||
private void ApplyPolicy(CookieOptions options)
|
||||
{
|
||||
switch (Policy.Secure)
|
||||
{
|
||||
case SecurePolicy.Always:
|
||||
options.Secure = true;
|
||||
break;
|
||||
case SecurePolicy.SameAsRequest:
|
||||
options.Secure = Context.Request.IsHttps;
|
||||
break;
|
||||
case SecurePolicy.None:
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
switch (Policy.HttpOnly)
|
||||
{
|
||||
case HttpOnlyPolicy.Always:
|
||||
options.HttpOnly = true;
|
||||
break;
|
||||
case HttpOnlyPolicy.None:
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNet.CookiePolicy
|
||||
{
|
||||
public class CookiePolicyOptions
|
||||
{
|
||||
public HttpOnlyPolicy HttpOnly { get; set; } = HttpOnlyPolicy.None;
|
||||
public SecurePolicy Secure { get; set; } = SecurePolicy.None;
|
||||
|
||||
public Action<AppendCookieContext> OnAppendCookie { get; set; }
|
||||
public Action<DeleteCookieContext> OnDeleteCookie { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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.AspNet.Http;
|
||||
|
||||
namespace Microsoft.AspNet.CookiePolicy
|
||||
{
|
||||
public class DeleteCookieContext
|
||||
{
|
||||
public DeleteCookieContext(HttpContext context, CookieOptions options, string name)
|
||||
{
|
||||
Context = context;
|
||||
CookieOptions = options;
|
||||
CookieName = name;
|
||||
}
|
||||
|
||||
public HttpContext Context { get; }
|
||||
public CookieOptions CookieOptions { get; }
|
||||
public string CookieName { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNet.CookiePolicy
|
||||
{
|
||||
public enum HttpOnlyPolicy
|
||||
{
|
||||
None,
|
||||
Always
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>86183dc3-02a8-4a68-8b60-71ecec066e79</ProjectGuid>
|
||||
<RootNamespace>Microsoft.AspNet.CookiePolicy</RootNamespace>
|
||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
|
||||
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
// 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.Reflection;
|
||||
using System.Resources;
|
||||
|
||||
[assembly: AssemblyMetadata("Serviceable", "True")]
|
||||
[assembly: NeutralResourcesLanguage("en-us")]
|
||||
|
|
@ -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.AspNet.CookiePolicy
|
||||
{
|
||||
public enum SecurePolicy
|
||||
{
|
||||
None,
|
||||
Always,
|
||||
SameAsRequest
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"version": "1.0.0-*",
|
||||
"description": "ASP.NET 5 cookie policy classes.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/aspnet/security"
|
||||
},
|
||||
"dependencies": {
|
||||
"Microsoft.AspNet.Http": "1.0.0-*"
|
||||
},
|
||||
"frameworks": {
|
||||
"dnx451": { },
|
||||
"dnxcore50": { }
|
||||
}
|
||||
}
|
||||
|
|
@ -729,7 +729,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
|||
Assert.Equal(HttpStatusCode.OK, transaction.Response.StatusCode);
|
||||
}
|
||||
|
||||
/* [Fact]
|
||||
[Fact]
|
||||
public async Task UseCookieWithInstanceDoesntUseSharedOptions()
|
||||
{
|
||||
var server = TestServer.Create(app =>
|
||||
|
|
@ -759,7 +759,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
|||
|
||||
Assert.Equal(HttpStatusCode.OK, transaction.Response.StatusCode);
|
||||
Assert.True(transaction.SetCookie[0].StartsWith("One="));
|
||||
}*/
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MapWithSignInOnlyRedirectToReturnUrlOnLoginPath()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,268 @@
|
|||
// 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.AspNet.Builder;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Features;
|
||||
using Microsoft.AspNet.Http.Features.Internal;
|
||||
using Microsoft.AspNet.TestHost;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.CookiePolicy.Test
|
||||
{
|
||||
public class CookiePolicyTests
|
||||
{
|
||||
private RequestDelegate SecureCookieAppends = context =>
|
||||
{
|
||||
context.Response.Cookies.Append("A", "A");
|
||||
context.Response.Cookies.Append("B", "B", new CookieOptions { Secure = false });
|
||||
context.Response.Cookies.Append("C", "C", new CookieOptions());
|
||||
context.Response.Cookies.Append("D", "D", new CookieOptions { Secure = true });
|
||||
return Task.FromResult(0);
|
||||
};
|
||||
private RequestDelegate HttpCookieAppends = context =>
|
||||
{
|
||||
context.Response.Cookies.Append("A", "A");
|
||||
context.Response.Cookies.Append("B", "B", new CookieOptions { HttpOnly = false });
|
||||
context.Response.Cookies.Append("C", "C", new CookieOptions());
|
||||
context.Response.Cookies.Append("D", "D", new CookieOptions { HttpOnly = true });
|
||||
return Task.FromResult(0);
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public async Task SecureAlwaysSetsSecure()
|
||||
{
|
||||
await RunTest("/secureAlways",
|
||||
options => options.Secure = SecurePolicy.Always,
|
||||
SecureCookieAppends,
|
||||
new RequestTest("http://example.com/secureAlways",
|
||||
transaction =>
|
||||
{
|
||||
Assert.NotNull(transaction.SetCookie);
|
||||
Assert.Equal("A=A; path=/; secure", transaction.SetCookie[0]);
|
||||
Assert.Equal("B=B; path=/; secure", transaction.SetCookie[1]);
|
||||
Assert.Equal("C=C; path=/; secure", transaction.SetCookie[2]);
|
||||
Assert.Equal("D=D; path=/; secure", transaction.SetCookie[3]);
|
||||
}));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SecureNoneLeavesSecureUnchanged()
|
||||
{
|
||||
await RunTest("/secureNone",
|
||||
options => options.Secure = SecurePolicy.None,
|
||||
SecureCookieAppends,
|
||||
new RequestTest("http://example.com/secureNone",
|
||||
transaction =>
|
||||
{
|
||||
Assert.NotNull(transaction.SetCookie);
|
||||
Assert.NotNull(transaction.SetCookie);
|
||||
Assert.Equal("A=A; path=/", transaction.SetCookie[0]);
|
||||
Assert.Equal("B=B; path=/", transaction.SetCookie[1]);
|
||||
Assert.Equal("C=C; path=/", transaction.SetCookie[2]);
|
||||
Assert.Equal("D=D; path=/; secure", transaction.SetCookie[3]);
|
||||
}));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SecureSameUsesRequest()
|
||||
{
|
||||
await RunTest("/secureSame",
|
||||
options => options.Secure = SecurePolicy.SameAsRequest,
|
||||
SecureCookieAppends,
|
||||
new RequestTest("http://example.com/secureSame",
|
||||
transaction =>
|
||||
{
|
||||
Assert.NotNull(transaction.SetCookie);
|
||||
Assert.Equal("A=A; path=/", transaction.SetCookie[0]);
|
||||
Assert.Equal("B=B; path=/", transaction.SetCookie[1]);
|
||||
Assert.Equal("C=C; path=/", transaction.SetCookie[2]);
|
||||
Assert.Equal("D=D; path=/", transaction.SetCookie[3]);
|
||||
}),
|
||||
new RequestTest("https://example.com/secureSame",
|
||||
transaction =>
|
||||
{
|
||||
Assert.NotNull(transaction.SetCookie);
|
||||
Assert.Equal("A=A; path=/; secure", transaction.SetCookie[0]);
|
||||
Assert.Equal("B=B; path=/; secure", transaction.SetCookie[1]);
|
||||
Assert.Equal("C=C; path=/; secure", transaction.SetCookie[2]);
|
||||
Assert.Equal("D=D; path=/; secure", transaction.SetCookie[3]);
|
||||
}));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HttpOnlyAlwaysSetsItAlways()
|
||||
{
|
||||
await RunTest("/httpOnlyAlways",
|
||||
options => options.HttpOnly = HttpOnlyPolicy.Always,
|
||||
HttpCookieAppends,
|
||||
new RequestTest("http://example.com/httpOnlyAlways",
|
||||
transaction =>
|
||||
{
|
||||
Assert.NotNull(transaction.SetCookie);
|
||||
Assert.Equal("A=A; path=/; httponly", transaction.SetCookie[0]);
|
||||
Assert.Equal("B=B; path=/; httponly", transaction.SetCookie[1]);
|
||||
Assert.Equal("C=C; path=/; httponly", transaction.SetCookie[2]);
|
||||
Assert.Equal("D=D; path=/; httponly", transaction.SetCookie[3]);
|
||||
}));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HttpOnlyNoneLeavesItAlone()
|
||||
{
|
||||
await RunTest("/httpOnlyNone",
|
||||
options => options.HttpOnly = HttpOnlyPolicy.None,
|
||||
HttpCookieAppends,
|
||||
new RequestTest("http://example.com/httpOnlyNone",
|
||||
transaction =>
|
||||
{
|
||||
Assert.NotNull(transaction.SetCookie);
|
||||
Assert.Equal("A=A; path=/", transaction.SetCookie[0]);
|
||||
Assert.Equal("B=B; path=/", transaction.SetCookie[1]);
|
||||
Assert.Equal("C=C; path=/", transaction.SetCookie[2]);
|
||||
Assert.Equal("D=D; path=/; httponly", transaction.SetCookie[3]);
|
||||
}));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CookiePolicyCanHijackAppend()
|
||||
{
|
||||
var server = TestServer.Create(app =>
|
||||
{
|
||||
app.UseCookiePolicy(options => options.OnAppendCookie = ctx => ctx.CookieName = ctx.CookieValue = "Hao");
|
||||
app.Run(context =>
|
||||
{
|
||||
context.Response.Cookies.Append("A", "A");
|
||||
context.Response.Cookies.Append("B", "B", new CookieOptions { Secure = false });
|
||||
context.Response.Cookies.Append("C", "C", new CookieOptions());
|
||||
context.Response.Cookies.Append("D", "D", new CookieOptions { Secure = true });
|
||||
return Task.FromResult(0);
|
||||
});
|
||||
});
|
||||
|
||||
var transaction = await server.SendAsync("http://example.com/login");
|
||||
|
||||
Assert.NotNull(transaction.SetCookie);
|
||||
Assert.Equal("Hao=Hao; path=/", transaction.SetCookie[0]);
|
||||
Assert.Equal("Hao=Hao; path=/", transaction.SetCookie[1]);
|
||||
Assert.Equal("Hao=Hao; path=/", transaction.SetCookie[2]);
|
||||
Assert.Equal("Hao=Hao; path=/; secure", transaction.SetCookie[3]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CookiePolicyCanHijackDelete()
|
||||
{
|
||||
var server = TestServer.Create(app =>
|
||||
{
|
||||
app.UseCookiePolicy(options => options.OnDeleteCookie = ctx => ctx.CookieName = "A");
|
||||
app.Run(context =>
|
||||
{
|
||||
context.Response.Cookies.Delete("A");
|
||||
context.Response.Cookies.Delete("B", new CookieOptions { Secure = false });
|
||||
context.Response.Cookies.Delete("C", new CookieOptions());
|
||||
context.Response.Cookies.Delete("D", new CookieOptions { Secure = true });
|
||||
return Task.FromResult(0);
|
||||
});
|
||||
});
|
||||
|
||||
var transaction = await server.SendAsync("http://example.com/login");
|
||||
|
||||
Assert.NotNull(transaction.SetCookie);
|
||||
Assert.Equal(1, transaction.SetCookie.Count);
|
||||
Assert.Equal("A=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/", transaction.SetCookie[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CookiePolicyCallsCookieFeature()
|
||||
{
|
||||
var server = TestServer.Create(app =>
|
||||
{
|
||||
app.Use(next => context =>
|
||||
{
|
||||
context.Features.Set<IResponseCookiesFeature>(new TestCookieFeature());
|
||||
return next(context);
|
||||
});
|
||||
app.UseCookiePolicy(options => options.OnDeleteCookie = ctx => ctx.CookieName = "A");
|
||||
app.Run(context =>
|
||||
{
|
||||
Assert.Throws<NotImplementedException>(() => context.Response.Cookies.Delete("A"));
|
||||
Assert.Throws<NotImplementedException>(() => context.Response.Cookies.Delete("A", new CookieOptions()));
|
||||
Assert.Throws<NotImplementedException>(() => context.Response.Cookies.Append("A", "A"));
|
||||
Assert.Throws<NotImplementedException>(() => context.Response.Cookies.Append("A", "A", new CookieOptions()));
|
||||
return context.Response.WriteAsync("Done");
|
||||
});
|
||||
});
|
||||
|
||||
var transaction = await server.SendAsync("http://example.com/login");
|
||||
Assert.Equal("Done", transaction.ResponseText);
|
||||
}
|
||||
|
||||
private class TestCookieFeature : IResponseCookiesFeature
|
||||
{
|
||||
public IResponseCookies Cookies { get; } = new BadCookies();
|
||||
|
||||
private class BadCookies : IResponseCookies
|
||||
{
|
||||
public void Append(string key, string value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Append(string key, string value, CookieOptions options)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Delete(string key)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Delete(string key, CookieOptions options)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class RequestTest
|
||||
{
|
||||
public RequestTest(string testUri, Action<Transaction> verify)
|
||||
{
|
||||
TestUri = testUri;
|
||||
Verification = verify;
|
||||
}
|
||||
|
||||
public async Task Execute(TestServer server)
|
||||
{
|
||||
var transaction = await server.SendAsync(TestUri);
|
||||
Verification(transaction);
|
||||
}
|
||||
|
||||
public string TestUri { get; set; }
|
||||
public Action<Transaction> Verification { get; set; }
|
||||
}
|
||||
|
||||
private async Task RunTest(
|
||||
string path,
|
||||
Action<CookiePolicyOptions> configureCookiePolicy,
|
||||
RequestDelegate configureSetup,
|
||||
params RequestTest[] tests)
|
||||
{
|
||||
var server = TestServer.Create(app =>
|
||||
{
|
||||
app.Map(path, map =>
|
||||
{
|
||||
map.UseCookiePolicy(configureCookiePolicy);
|
||||
map.Run(configureSetup);
|
||||
});
|
||||
});
|
||||
foreach (var test in tests)
|
||||
{
|
||||
await test.Execute(server);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>1790e052-646f-4529-b90e-6fea95520d69</ProjectGuid>
|
||||
<RootNamespace>Microsoft.AspNet.CookiePolicy.Test</RootNamespace>
|
||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
|
||||
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.TestHost;
|
||||
|
||||
namespace Microsoft.AspNet.CookiePolicy
|
||||
{
|
||||
// REVIEW: Should find a shared home for these potentially (Copied from Auth tests)
|
||||
public static class TestExtensions
|
||||
{
|
||||
public const string CookieAuthenticationScheme = "External";
|
||||
|
||||
public static async Task<Transaction> SendAsync(this TestServer server, string uri, string cookieHeader = null)
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, uri);
|
||||
if (!string.IsNullOrEmpty(cookieHeader))
|
||||
{
|
||||
request.Headers.Add("Cookie", cookieHeader);
|
||||
}
|
||||
var transaction = new Transaction
|
||||
{
|
||||
Request = request,
|
||||
Response = await server.CreateClient().SendAsync(request),
|
||||
};
|
||||
if (transaction.Response.Headers.Contains("Set-Cookie"))
|
||||
{
|
||||
transaction.SetCookie = transaction.Response.Headers.GetValues("Set-Cookie").ToList();
|
||||
}
|
||||
transaction.ResponseText = await transaction.Response.Content.ReadAsStringAsync();
|
||||
|
||||
if (transaction.Response.Content != null &&
|
||||
transaction.Response.Content.Headers.ContentType != null &&
|
||||
transaction.Response.Content.Headers.ContentType.MediaType == "text/xml")
|
||||
{
|
||||
transaction.ResponseElement = XElement.Parse(transaction.ResponseText);
|
||||
}
|
||||
return transaction;
|
||||
}
|
||||
|
||||
public static void Describe(this HttpResponse res, ClaimsPrincipal principal)
|
||||
{
|
||||
res.StatusCode = 200;
|
||||
res.ContentType = "text/xml";
|
||||
var xml = new XElement("xml");
|
||||
if (principal != null)
|
||||
{
|
||||
foreach (var identity in principal.Identities)
|
||||
{
|
||||
xml.Add(identity.Claims.Select(claim =>
|
||||
new XElement("claim", new XAttribute("type", claim.Type),
|
||||
new XAttribute("value", claim.Value),
|
||||
new XAttribute("issuer", claim.Issuer))));
|
||||
}
|
||||
}
|
||||
using (var memory = new MemoryStream())
|
||||
{
|
||||
using (var writer = new XmlTextWriter(memory, Encoding.UTF8))
|
||||
{
|
||||
xml.WriteTo(writer);
|
||||
}
|
||||
res.Body.Write(memory.ToArray(), 0, memory.ToArray().Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
// 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.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Microsoft.AspNet.CookiePolicy
|
||||
{
|
||||
// REVIEW: Should find a shared home for these potentially (Copied from Auth tests)
|
||||
public class Transaction
|
||||
{
|
||||
public HttpRequestMessage Request { get; set; }
|
||||
public HttpResponseMessage Response { get; set; }
|
||||
|
||||
public IList<string> SetCookie { get; set; }
|
||||
|
||||
public string ResponseText { get; set; }
|
||||
public XElement ResponseElement { get; set; }
|
||||
|
||||
public string AuthenticationCookieValue
|
||||
{
|
||||
get
|
||||
{
|
||||
if (SetCookie != null && SetCookie.Count > 0)
|
||||
{
|
||||
var authCookie = SetCookie.SingleOrDefault(c => c.Contains(".AspNet." + TestExtensions.CookieAuthenticationScheme + "="));
|
||||
if (authCookie != null)
|
||||
{
|
||||
return authCookie.Substring(0, authCookie.IndexOf(';'));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public string FindClaimValue(string claimType, string issuer = null)
|
||||
{
|
||||
var claim = ResponseElement.Elements("claim")
|
||||
.SingleOrDefault(elt => elt.Attribute("type").Value == claimType &&
|
||||
(issuer == null || elt.Attribute("issuer").Value == issuer));
|
||||
if (claim == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return claim.Attribute("value").Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"compilationOptions": {
|
||||
"warningsAsErrors": true
|
||||
},
|
||||
"dependencies": {
|
||||
"Microsoft.AspNet.CookiePolicy": "1.0.0-*",
|
||||
"Microsoft.AspNet.TestHost": "1.0.0-*",
|
||||
"Microsoft.Framework.DependencyInjection": "1.0.0-*",
|
||||
"xunit.runner.aspnet": "2.0.0-aspnet-*"
|
||||
},
|
||||
"commands": {
|
||||
"test": "xunit.runner.aspnet"
|
||||
},
|
||||
"frameworks": {
|
||||
"dnx451": { },
|
||||
"dnxcore50": { }
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue