diff --git a/Security.sln b/Security.sln
index 7fd810f8b..ee8180ede 100644
--- a/Security.sln
+++ b/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
diff --git a/src/Microsoft.AspNet.CookiePolicy/AppendCookieContext.cs b/src/Microsoft.AspNet.CookiePolicy/AppendCookieContext.cs
new file mode 100644
index 000000000..f9d816661
--- /dev/null
+++ b/src/Microsoft.AspNet.CookiePolicy/AppendCookieContext.cs
@@ -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; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.CookiePolicy/CookiePolicyAppBuilderExtensions.cs b/src/Microsoft.AspNet.CookiePolicy/CookiePolicyAppBuilderExtensions.cs
new file mode 100644
index 000000000..aa8c9af01
--- /dev/null
+++ b/src/Microsoft.AspNet.CookiePolicy/CookiePolicyAppBuilderExtensions.cs
@@ -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
+{
+ ///
+ /// Extension methods provided by the cookie policy middleware
+ ///
+ public static class CookiePolicyAppBuilderExtensions
+ {
+ ///
+ /// Adds a cookie policy middleware to your web application pipeline.
+ ///
+ /// The IApplicationBuilder passed to your configuration method
+ /// The options for the middleware
+ /// The original app parameter
+ public static IApplicationBuilder UseCookiePolicy(this IApplicationBuilder app, CookiePolicyOptions options)
+ {
+ return app.UseMiddleware(options);
+ }
+
+ ///
+ /// Adds a cookie policy middleware to your web application pipeline.
+ ///
+ /// The IApplicationBuilder passed to your configuration method
+ /// Used to configure the options for the middleware
+ /// The original app parameter
+ public static IApplicationBuilder UseCookiePolicy(this IApplicationBuilder app, Action configureOptions)
+ {
+ var options = new CookiePolicyOptions();
+ if (configureOptions != null)
+ {
+ configureOptions(options);
+ }
+ return app.UseCookiePolicy(options);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.CookiePolicy/CookiePolicyMiddleware.cs b/src/Microsoft.AspNet.CookiePolicy/CookiePolicyMiddleware.cs
new file mode 100644
index 000000000..d726d80cd
--- /dev/null
+++ b/src/Microsoft.AspNet.CookiePolicy/CookiePolicyMiddleware.cs
@@ -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() ?? new ResponseCookiesFeature(context.Features);
+ context.Features.Set(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();
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.CookiePolicy/CookiePolicyOptions.cs b/src/Microsoft.AspNet.CookiePolicy/CookiePolicyOptions.cs
new file mode 100644
index 000000000..ce5a86698
--- /dev/null
+++ b/src/Microsoft.AspNet.CookiePolicy/CookiePolicyOptions.cs
@@ -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 OnAppendCookie { get; set; }
+ public Action OnDeleteCookie { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.CookiePolicy/DeleteCookieContext.cs b/src/Microsoft.AspNet.CookiePolicy/DeleteCookieContext.cs
new file mode 100644
index 000000000..c8cd208fb
--- /dev/null
+++ b/src/Microsoft.AspNet.CookiePolicy/DeleteCookieContext.cs
@@ -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; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.CookiePolicy/HttpOnlyPolicy.cs b/src/Microsoft.AspNet.CookiePolicy/HttpOnlyPolicy.cs
new file mode 100644
index 000000000..276e3ed3e
--- /dev/null
+++ b/src/Microsoft.AspNet.CookiePolicy/HttpOnlyPolicy.cs
@@ -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
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.CookiePolicy/Microsoft.AspNet.CookiePolicy.xproj b/src/Microsoft.AspNet.CookiePolicy/Microsoft.AspNet.CookiePolicy.xproj
new file mode 100644
index 000000000..7790eac27
--- /dev/null
+++ b/src/Microsoft.AspNet.CookiePolicy/Microsoft.AspNet.CookiePolicy.xproj
@@ -0,0 +1,19 @@
+
+
+
+ 14.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+
+
+
+ 86183dc3-02a8-4a68-8b60-71ecec066e79
+ Microsoft.AspNet.CookiePolicy
+ ..\..\artifacts\obj\$(MSBuildProjectName)
+ ..\..\artifacts\bin\$(MSBuildProjectName)\
+
+
+
+ 2.0
+
+
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.CookiePolicy/Properties/AssemblyInfo.cs b/src/Microsoft.AspNet.CookiePolicy/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..b2437d9ad
--- /dev/null
+++ b/src/Microsoft.AspNet.CookiePolicy/Properties/AssemblyInfo.cs
@@ -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")]
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.CookiePolicy/SecurePolicy.cs b/src/Microsoft.AspNet.CookiePolicy/SecurePolicy.cs
new file mode 100644
index 000000000..962ecddff
--- /dev/null
+++ b/src/Microsoft.AspNet.CookiePolicy/SecurePolicy.cs
@@ -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
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.CookiePolicy/project.json b/src/Microsoft.AspNet.CookiePolicy/project.json
new file mode 100644
index 000000000..6d78e5eb8
--- /dev/null
+++ b/src/Microsoft.AspNet.CookiePolicy/project.json
@@ -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": { }
+ }
+}
diff --git a/test/Microsoft.AspNet.Authentication.Test/Cookies/CookieMiddlewareTests.cs b/test/Microsoft.AspNet.Authentication.Test/Cookies/CookieMiddlewareTests.cs
index 4af812193..3fb293a59 100644
--- a/test/Microsoft.AspNet.Authentication.Test/Cookies/CookieMiddlewareTests.cs
+++ b/test/Microsoft.AspNet.Authentication.Test/Cookies/CookieMiddlewareTests.cs
@@ -731,7 +731,7 @@ public async Task ChallengeDoesNotSet401OnUnauthorized()
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.OK);
}
-/* [Fact]
+ [Fact]
public async Task UseCookieWithInstanceDoesntUseSharedOptions()
{
var server = TestServer.Create(app =>
@@ -761,7 +761,7 @@ public async Task UseCookieWithOutInstanceDoesUseSharedOptions()
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.OK);
Assert.True(transaction.SetCookie[0].StartsWith("One="));
- }*/
+ }
[Fact]
public async Task MapWithSignInOnlyRedirectToReturnUrlOnLoginPath()
diff --git a/test/Microsoft.AspNet.CookiePolicy.Test/CookiePolicyTests.cs b/test/Microsoft.AspNet.CookiePolicy.Test/CookiePolicyTests.cs
new file mode 100644
index 000000000..78f20c9cf
--- /dev/null
+++ b/test/Microsoft.AspNet.CookiePolicy.Test/CookiePolicyTests.cs
@@ -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(new TestCookieFeature());
+ return next(context);
+ });
+ app.UseCookiePolicy(options => options.OnDeleteCookie = ctx => ctx.CookieName = "A");
+ app.Run(context =>
+ {
+ Assert.Throws(() => context.Response.Cookies.Delete("A"));
+ Assert.Throws(() => context.Response.Cookies.Delete("A", new CookieOptions()));
+ Assert.Throws(() => context.Response.Cookies.Append("A", "A"));
+ Assert.Throws(() => 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 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 Verification { get; set; }
+ }
+
+ private async Task RunTest(
+ string path,
+ Action 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);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.CookiePolicy.Test/Microsoft.AspNet.CookiePolicy.Test.xproj b/test/Microsoft.AspNet.CookiePolicy.Test/Microsoft.AspNet.CookiePolicy.Test.xproj
new file mode 100644
index 000000000..b0a49fddd
--- /dev/null
+++ b/test/Microsoft.AspNet.CookiePolicy.Test/Microsoft.AspNet.CookiePolicy.Test.xproj
@@ -0,0 +1,21 @@
+
+
+
+ 14.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+
+
+
+ 1790e052-646f-4529-b90e-6fea95520d69
+ Microsoft.AspNet.CookiePolicy.Test
+ ..\..\artifacts\obj\$(MSBuildProjectName)
+ ..\..\artifacts\bin\$(MSBuildProjectName)\
+
+
+ 2.0
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.CookiePolicy.Test/TestExtensions.cs b/test/Microsoft.AspNet.CookiePolicy.Test/TestExtensions.cs
new file mode 100644
index 000000000..90b01af4b
--- /dev/null
+++ b/test/Microsoft.AspNet.CookiePolicy.Test/TestExtensions.cs
@@ -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 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);
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.AspNet.CookiePolicy.Test/Transaction.cs b/test/Microsoft.AspNet.CookiePolicy.Test/Transaction.cs
new file mode 100644
index 000000000..afa9c0c99
--- /dev/null
+++ b/test/Microsoft.AspNet.CookiePolicy.Test/Transaction.cs
@@ -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 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;
+ }
+ }
+}
diff --git a/test/Microsoft.AspNet.CookiePolicy.Test/project.json b/test/Microsoft.AspNet.CookiePolicy.Test/project.json
new file mode 100644
index 000000000..336c06a37
--- /dev/null
+++ b/test/Microsoft.AspNet.CookiePolicy.Test/project.json
@@ -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": { }
+ }
+}