From 2246a41978192770a08c77bab07a80c4d54a0800 Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Thu, 12 Mar 2015 16:30:32 -0700 Subject: [PATCH] Add functional tests + cleanup Fixes https://github.com/aspnet/Identity/issues/395 Fixes https://github.com/aspnet/Identity/issues/231 --- .../IdentityServiceCollectionExtensions.cs | 9 +- .../PrincipalExtensions.cs | 17 +- temp/Identity45.sln | 130 -------- .../FunctionalTest.cs | 304 ++++++++++++++++++ .../TestClock.cs | 23 ++ .../project.json | 8 +- .../IHttpContextAccessor.cs | 6 - .../PrincipalExtensionsTest.cs | 47 +-- 8 files changed, 348 insertions(+), 196 deletions(-) delete mode 100644 temp/Identity45.sln create mode 100644 test/Microsoft.AspNet.Identity.InMemory.Test/FunctionalTest.cs create mode 100644 test/Microsoft.AspNet.Identity.InMemory.Test/TestClock.cs delete mode 100644 test/Microsoft.AspNet.Identity.Test/IHttpContextAccessor.cs diff --git a/src/Microsoft.AspNet.Identity/IdentityServiceCollectionExtensions.cs b/src/Microsoft.AspNet.Identity/IdentityServiceCollectionExtensions.cs index df4b92f59d..9a09176614 100644 --- a/src/Microsoft.AspNet.Identity/IdentityServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNet.Identity/IdentityServiceCollectionExtensions.cs @@ -23,6 +23,11 @@ namespace Microsoft.Framework.DependencyInjection return services.Configure(config); } + public static IServiceCollection ConfigureIdentityApplicationCookie(this IServiceCollection services, Action configureOptions) + { + return services.Configure(configureOptions, IdentityOptions.ApplicationCookieAuthenticationScheme); + } + public static IdentityBuilder AddIdentity(this IServiceCollection services) { return services.AddIdentity(configureOptions: null); @@ -72,7 +77,7 @@ namespace Microsoft.Framework.DependencyInjection }); // Configure all of the cookie middlewares - services.Configure(options => + services.ConfigureIdentityApplicationCookie(options => { options.AuthenticationScheme = IdentityOptions.ApplicationCookieAuthenticationScheme; options.LoginPath = new PathString("/Account/Login"); @@ -80,7 +85,7 @@ namespace Microsoft.Framework.DependencyInjection { OnValidatePrincipal = SecurityStampValidator.ValidatePrincipalAsync }; - }, IdentityOptions.ApplicationCookieAuthenticationScheme); + }); services.Configure(options => { options.AuthenticationScheme = IdentityOptions.ExternalCookieAuthenticationScheme; diff --git a/src/Microsoft.AspNet.Identity/PrincipalExtensions.cs b/src/Microsoft.AspNet.Identity/PrincipalExtensions.cs index 669af4cfa3..c01cb0778c 100644 --- a/src/Microsoft.AspNet.Identity/PrincipalExtensions.cs +++ b/src/Microsoft.AspNet.Identity/PrincipalExtensions.cs @@ -18,14 +18,13 @@ namespace System.Security.Principal /// The instance this method extends. /// The Name claim value, or null if the claim is not present. /// The name claim is identified by . - public static string GetUserName(this IPrincipal principal) + public static string GetUserName(this ClaimsPrincipal principal) { if (principal == null) { throw new ArgumentNullException(nameof(principal)); } - var cp = principal as ClaimsPrincipal; - return cp != null ? cp.FindFirstValue(ClaimsIdentity.DefaultNameClaimType) : null; + return principal.FindFirstValue(ClaimsIdentity.DefaultNameClaimType); } /// @@ -34,14 +33,13 @@ namespace System.Security.Principal /// The instance this method extends. /// The User ID claim value, or null if the claim is not present. /// The name claim is identified by . - public static string GetUserId(this IPrincipal principal) + public static string GetUserId(this ClaimsPrincipal principal) { if (principal == null) { throw new ArgumentNullException(nameof(principal)); } - var ci = principal as ClaimsPrincipal; - return ci != null ? ci.FindFirstValue(ClaimTypes.NameIdentifier) : null; + return principal.FindFirstValue(ClaimTypes.NameIdentifier); } /// @@ -49,15 +47,14 @@ namespace System.Security.Principal /// /// The instance this method extends. /// True if the user is logged in with identity. - public static bool IsSignedIn(this IPrincipal principal) + public static bool IsSignedIn(this ClaimsPrincipal principal) { if (principal == null) { throw new ArgumentNullException(nameof(principal)); } - var p = principal as ClaimsPrincipal; - return p?.Identities != null && - p.Identities.Any(i => i.AuthenticationType == IdentityOptions.ApplicationCookieAuthenticationType); + return principal.Identities != null && + principal.Identities.Any(i => i.AuthenticationType == IdentityOptions.ApplicationCookieAuthenticationType); } /// diff --git a/temp/Identity45.sln b/temp/Identity45.sln deleted file mode 100644 index a3dff4a832..0000000000 --- a/temp/Identity45.sln +++ /dev/null @@ -1,130 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2013 -VisualStudioVersion = 12.0.30408.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.Identity.net45", "..\src\Microsoft.AspNet.Identity\Microsoft.AspNet.Identity.net45.csproj", "{91C0C8AC-F889-4EB9-B4C7-1EB951FC428A}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{31019886-9CDA-4072-B3D9-E513122CECD0}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.Identity.Entity.net45", "..\src\Microsoft.AspNet.Identity.Entity\Microsoft.AspNet.Identity.Entity.net45.csproj", "{B00CB809-C040-42A4-8AB2-4CFB924DD038}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.Identity.InMemory.net45", "..\src\Microsoft.AspNet.Identity.InMemory\Microsoft.AspNet.Identity.InMemory.net45.csproj", "{13A0C423-FD34-41B7-AB46-2BB16CF5DC1F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.Identity.Security.net45", "..\src\Microsoft.AspNet.Identity.Security\Microsoft.AspNet.Identity.Security.net45.csproj", "{ADE2B479-1D29-4D63-9B8B-D5E05943C4A8}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{394C0E59-7E24-453E-993C-7B914C62110A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.Identity.Test.net45", "..\test\Microsoft.AspNet.Identity.Test\Microsoft.AspNet.Identity.Test.net45.csproj", "{CD364B81-5435-4EF7-ADE7-B6E02E0B4486}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.Identity.Security.Test.net45", "..\test\Microsoft.AspNet.Identity.Security.Test\Microsoft.AspNet.Identity.Security.Test.net45.csproj", "{13883A27-8710-421A-86CE-298E34CE82DD}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.Identity.InMemory.Test.net45", "..\test\Microsoft.AspNet.Identity.InMemory.Test\Microsoft.AspNet.Identity.InMemory.Test.net45.csproj", "{47F00656-3F63-45CD-BBE5-1505CE7938D3}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.Identity.Entity.Test.net45", "..\test\Microsoft.AspNet.Identity.Entity.Test\Microsoft.AspNet.Identity.Entity.Test.net45.csproj", "{9CDAEF16-DD10-4307-9186-EC70BF8A5AD8}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|Mixed Platforms = Debug|Mixed Platforms - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|Mixed Platforms = Release|Mixed Platforms - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {91C0C8AC-F889-4EB9-B4C7-1EB951FC428A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {91C0C8AC-F889-4EB9-B4C7-1EB951FC428A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {91C0C8AC-F889-4EB9-B4C7-1EB951FC428A}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {91C0C8AC-F889-4EB9-B4C7-1EB951FC428A}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {91C0C8AC-F889-4EB9-B4C7-1EB951FC428A}.Debug|x86.ActiveCfg = Debug|Any CPU - {91C0C8AC-F889-4EB9-B4C7-1EB951FC428A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {91C0C8AC-F889-4EB9-B4C7-1EB951FC428A}.Release|Any CPU.Build.0 = Release|Any CPU - {91C0C8AC-F889-4EB9-B4C7-1EB951FC428A}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {91C0C8AC-F889-4EB9-B4C7-1EB951FC428A}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {91C0C8AC-F889-4EB9-B4C7-1EB951FC428A}.Release|x86.ActiveCfg = Release|Any CPU - {B00CB809-C040-42A4-8AB2-4CFB924DD038}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B00CB809-C040-42A4-8AB2-4CFB924DD038}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B00CB809-C040-42A4-8AB2-4CFB924DD038}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {B00CB809-C040-42A4-8AB2-4CFB924DD038}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {B00CB809-C040-42A4-8AB2-4CFB924DD038}.Debug|x86.ActiveCfg = Debug|Any CPU - {B00CB809-C040-42A4-8AB2-4CFB924DD038}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B00CB809-C040-42A4-8AB2-4CFB924DD038}.Release|Any CPU.Build.0 = Release|Any CPU - {B00CB809-C040-42A4-8AB2-4CFB924DD038}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {B00CB809-C040-42A4-8AB2-4CFB924DD038}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {B00CB809-C040-42A4-8AB2-4CFB924DD038}.Release|x86.ActiveCfg = Release|Any CPU - {13A0C423-FD34-41B7-AB46-2BB16CF5DC1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {13A0C423-FD34-41B7-AB46-2BB16CF5DC1F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {13A0C423-FD34-41B7-AB46-2BB16CF5DC1F}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {13A0C423-FD34-41B7-AB46-2BB16CF5DC1F}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {13A0C423-FD34-41B7-AB46-2BB16CF5DC1F}.Debug|x86.ActiveCfg = Debug|Any CPU - {13A0C423-FD34-41B7-AB46-2BB16CF5DC1F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {13A0C423-FD34-41B7-AB46-2BB16CF5DC1F}.Release|Any CPU.Build.0 = Release|Any CPU - {13A0C423-FD34-41B7-AB46-2BB16CF5DC1F}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {13A0C423-FD34-41B7-AB46-2BB16CF5DC1F}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {13A0C423-FD34-41B7-AB46-2BB16CF5DC1F}.Release|x86.ActiveCfg = Release|Any CPU - {ADE2B479-1D29-4D63-9B8B-D5E05943C4A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {ADE2B479-1D29-4D63-9B8B-D5E05943C4A8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {ADE2B479-1D29-4D63-9B8B-D5E05943C4A8}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {ADE2B479-1D29-4D63-9B8B-D5E05943C4A8}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {ADE2B479-1D29-4D63-9B8B-D5E05943C4A8}.Debug|x86.ActiveCfg = Debug|Any CPU - {ADE2B479-1D29-4D63-9B8B-D5E05943C4A8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {ADE2B479-1D29-4D63-9B8B-D5E05943C4A8}.Release|Any CPU.Build.0 = Release|Any CPU - {ADE2B479-1D29-4D63-9B8B-D5E05943C4A8}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {ADE2B479-1D29-4D63-9B8B-D5E05943C4A8}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {ADE2B479-1D29-4D63-9B8B-D5E05943C4A8}.Release|x86.ActiveCfg = Release|Any CPU - {CD364B81-5435-4EF7-ADE7-B6E02E0B4486}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CD364B81-5435-4EF7-ADE7-B6E02E0B4486}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CD364B81-5435-4EF7-ADE7-B6E02E0B4486}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {CD364B81-5435-4EF7-ADE7-B6E02E0B4486}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {CD364B81-5435-4EF7-ADE7-B6E02E0B4486}.Debug|x86.ActiveCfg = Debug|Any CPU - {CD364B81-5435-4EF7-ADE7-B6E02E0B4486}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CD364B81-5435-4EF7-ADE7-B6E02E0B4486}.Release|Any CPU.Build.0 = Release|Any CPU - {CD364B81-5435-4EF7-ADE7-B6E02E0B4486}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {CD364B81-5435-4EF7-ADE7-B6E02E0B4486}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {CD364B81-5435-4EF7-ADE7-B6E02E0B4486}.Release|x86.ActiveCfg = Release|Any CPU - {13883A27-8710-421A-86CE-298E34CE82DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {13883A27-8710-421A-86CE-298E34CE82DD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {13883A27-8710-421A-86CE-298E34CE82DD}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {13883A27-8710-421A-86CE-298E34CE82DD}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {13883A27-8710-421A-86CE-298E34CE82DD}.Debug|x86.ActiveCfg = Debug|Any CPU - {13883A27-8710-421A-86CE-298E34CE82DD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {13883A27-8710-421A-86CE-298E34CE82DD}.Release|Any CPU.Build.0 = Release|Any CPU - {13883A27-8710-421A-86CE-298E34CE82DD}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {13883A27-8710-421A-86CE-298E34CE82DD}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {13883A27-8710-421A-86CE-298E34CE82DD}.Release|x86.ActiveCfg = Release|Any CPU - {47F00656-3F63-45CD-BBE5-1505CE7938D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {47F00656-3F63-45CD-BBE5-1505CE7938D3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {47F00656-3F63-45CD-BBE5-1505CE7938D3}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {47F00656-3F63-45CD-BBE5-1505CE7938D3}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {47F00656-3F63-45CD-BBE5-1505CE7938D3}.Debug|x86.ActiveCfg = Debug|Any CPU - {47F00656-3F63-45CD-BBE5-1505CE7938D3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {47F00656-3F63-45CD-BBE5-1505CE7938D3}.Release|Any CPU.Build.0 = Release|Any CPU - {47F00656-3F63-45CD-BBE5-1505CE7938D3}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {47F00656-3F63-45CD-BBE5-1505CE7938D3}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {47F00656-3F63-45CD-BBE5-1505CE7938D3}.Release|x86.ActiveCfg = Release|Any CPU - {9CDAEF16-DD10-4307-9186-EC70BF8A5AD8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9CDAEF16-DD10-4307-9186-EC70BF8A5AD8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9CDAEF16-DD10-4307-9186-EC70BF8A5AD8}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {9CDAEF16-DD10-4307-9186-EC70BF8A5AD8}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {9CDAEF16-DD10-4307-9186-EC70BF8A5AD8}.Debug|x86.ActiveCfg = Debug|Any CPU - {9CDAEF16-DD10-4307-9186-EC70BF8A5AD8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9CDAEF16-DD10-4307-9186-EC70BF8A5AD8}.Release|Any CPU.Build.0 = Release|Any CPU - {9CDAEF16-DD10-4307-9186-EC70BF8A5AD8}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {9CDAEF16-DD10-4307-9186-EC70BF8A5AD8}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {9CDAEF16-DD10-4307-9186-EC70BF8A5AD8}.Release|x86.ActiveCfg = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {91C0C8AC-F889-4EB9-B4C7-1EB951FC428A} = {31019886-9CDA-4072-B3D9-E513122CECD0} - {B00CB809-C040-42A4-8AB2-4CFB924DD038} = {31019886-9CDA-4072-B3D9-E513122CECD0} - {13A0C423-FD34-41B7-AB46-2BB16CF5DC1F} = {31019886-9CDA-4072-B3D9-E513122CECD0} - {ADE2B479-1D29-4D63-9B8B-D5E05943C4A8} = {31019886-9CDA-4072-B3D9-E513122CECD0} - {CD364B81-5435-4EF7-ADE7-B6E02E0B4486} = {394C0E59-7E24-453E-993C-7B914C62110A} - {13883A27-8710-421A-86CE-298E34CE82DD} = {394C0E59-7E24-453E-993C-7B914C62110A} - {47F00656-3F63-45CD-BBE5-1505CE7938D3} = {394C0E59-7E24-453E-993C-7B914C62110A} - {9CDAEF16-DD10-4307-9186-EC70BF8A5AD8} = {394C0E59-7E24-453E-993C-7B914C62110A} - EndGlobalSection -EndGlobal diff --git a/test/Microsoft.AspNet.Identity.InMemory.Test/FunctionalTest.cs b/test/Microsoft.AspNet.Identity.InMemory.Test/FunctionalTest.cs new file mode 100644 index 0000000000..3d85027e94 --- /dev/null +++ b/test/Microsoft.AspNet.Identity.InMemory.Test/FunctionalTest.cs @@ -0,0 +1,304 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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 System.Linq; +using System.Net; +using System.Net.Http; +using System.Security.Claims; +using System.Security.Principal; +using System.Text; +using System.Threading.Tasks; +using System.Xml; +using System.Xml.Linq; +using System.Diagnostics; +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.Http.Authentication; +using Microsoft.AspNet.TestHost; +using Microsoft.Framework.DependencyInjection; +using Microsoft.AspNet.Authentication.Cookies; +using Shouldly; +using Xunit; + +namespace Microsoft.AspNet.Identity.InMemory +{ + public class FunctionalTest + { + const string TestPassword = "1qaz!QAZ"; + + [Fact] + public async Task CanCreateMeLoginAndCookieStopsWorkingAfterExpiration() + { + var clock = new TestClock(); + TestServer server = CreateServer(appCookieOptions => + { + appCookieOptions.SystemClock = clock; + appCookieOptions.ExpireTimeSpan = TimeSpan.FromMinutes(10); + appCookieOptions.SlidingExpiration = false; + }); + + Transaction transaction1 = await SendAsync(server, "http://example.com/createMe"); + transaction1.Response.StatusCode.ShouldBe(HttpStatusCode.OK); + Assert.Null(transaction1.SetCookie); + + Transaction transaction2 = await SendAsync(server, "http://example.com/pwdLogin/false"); + transaction2.Response.StatusCode.ShouldBe(HttpStatusCode.OK); + Assert.NotNull(transaction2.SetCookie); + transaction2.SetCookie.ShouldNotContain("; expires="); + + Transaction transaction3 = await SendAsync(server, "http://example.com/me", transaction2.CookieNameValue); + FindClaimValue(transaction3, ClaimTypes.Name).ShouldBe("hao"); + Assert.Null(transaction3.SetCookie); + + clock.Add(TimeSpan.FromMinutes(7)); + + Transaction transaction4 = await SendAsync(server, "http://example.com/me", transaction2.CookieNameValue); + FindClaimValue(transaction4, ClaimTypes.Name).ShouldBe("hao"); + Assert.Null(transaction4.SetCookie); + + clock.Add(TimeSpan.FromMinutes(7)); + + Transaction transaction5 = await SendAsync(server, "http://example.com/me", transaction2.CookieNameValue); + FindClaimValue(transaction5, ClaimTypes.Name).ShouldBe(null); + Assert.Null(transaction5.SetCookie); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task CanCreateMeLoginAndSecurityStampExtendsExpiration(bool rememberMe) + { + var clock = new TestClock(); + TestServer server = CreateServer(appCookieOptions => + { + appCookieOptions.SystemClock = clock; + }); + + Transaction transaction1 = await SendAsync(server, "http://example.com/createMe"); + transaction1.Response.StatusCode.ShouldBe(HttpStatusCode.OK); + Assert.Null(transaction1.SetCookie); + + Transaction transaction2 = await SendAsync(server, "http://example.com/pwdLogin/" + rememberMe); + transaction2.Response.StatusCode.ShouldBe(HttpStatusCode.OK); + Assert.NotNull(transaction2.SetCookie); + if (rememberMe) + { + transaction2.SetCookie.ShouldContain("; expires="); + } + else + { + transaction2.SetCookie.ShouldNotContain("; expires="); + } + + Transaction transaction3 = await SendAsync(server, "http://example.com/me", transaction2.CookieNameValue); + FindClaimValue(transaction3, ClaimTypes.Name).ShouldBe("hao"); + Assert.Null(transaction3.SetCookie); + + // Make sure we don't get a new cookie yet + clock.Add(TimeSpan.FromMinutes(10)); + Transaction transaction4 = await SendAsync(server, "http://example.com/me", transaction2.CookieNameValue); + FindClaimValue(transaction4, ClaimTypes.Name).ShouldBe("hao"); + Assert.Null(transaction4.SetCookie); + + // Go past SecurityStampValidation interval and ensure we get a new cookie + clock.Add(TimeSpan.FromMinutes(21)); + + Transaction transaction5 = await SendAsync(server, "http://example.com/me", transaction2.CookieNameValue); + Assert.NotNull(transaction5.SetCookie); + FindClaimValue(transaction5, ClaimTypes.Name).ShouldBe("hao"); + + // Make sure new cookie is valid + Transaction transaction6 = await SendAsync(server, "http://example.com/me", transaction5.CookieNameValue); + FindClaimValue(transaction6, ClaimTypes.Name).ShouldBe("hao"); + } + + [Fact] + public async Task TwoFactorRememberCookieVerification() + { + TestServer server = CreateServer(appCookieOptions => { }); + + Transaction transaction1 = await SendAsync(server, "http://example.com/createMe"); + transaction1.Response.StatusCode.ShouldBe(HttpStatusCode.OK); + Assert.Null(transaction1.SetCookie); + + Transaction transaction2 = await SendAsync(server, "http://example.com/twofactorRememeber"); + transaction2.Response.StatusCode.ShouldBe(HttpStatusCode.OK); + + string setCookie = transaction2.SetCookie; + setCookie.ShouldContain(IdentityOptions.TwoFactorRememberMeCookieAuthenticationScheme+"="); + setCookie.ShouldContain("; expires="); + + Transaction transaction3 = await SendAsync(server, "http://example.com/isTwoFactorRememebered", transaction2.CookieNameValue); + transaction3.Response.StatusCode.ShouldBe(HttpStatusCode.OK); + } + + private static string FindClaimValue(Transaction transaction, string claimType) + { + XElement claim = transaction.ResponseElement.Elements("claim").SingleOrDefault(elt => elt.Attribute("type").Value == claimType); + if (claim == null) + { + return null; + } + return claim.Attribute("value").Value; + } + + private static async Task GetAuthData(TestServer server, string url, string cookie) + { + var request = new HttpRequestMessage(HttpMethod.Get, url); + request.Headers.Add("Cookie", cookie); + + HttpResponseMessage response2 = await server.CreateClient().SendAsync(request); + string text = await response2.Content.ReadAsStringAsync(); + XElement me = XElement.Parse(text); + return me; + } + + private static TestServer CreateServer(Action configureAppCookie, Func testpath = null, Uri baseAddress = null) + { + var server = TestServer.Create(app => + { + app.UseServices(services => + { + services.AddIdentity(); + services.AddSingleton, InMemoryUserStore>(); + services.AddSingleton, InMemoryRoleStore>(); + services.ConfigureIdentityApplicationCookie(configureAppCookie); + }); + app.UseIdentity(); + app.Use(async (context, next) => + { + var req = context.Request; + var res = context.Response; + var userManager = context.RequestServices.GetRequiredService>(); + var signInManager = context.RequestServices.GetRequiredService>(); + PathString remainder; + if (req.Path == new PathString("/normal")) + { + res.StatusCode = 200; + } + else if (req.Path == new PathString("/createMe")) + { + var result = await userManager.CreateAsync(new InMemoryUser("hao"), TestPassword); + res.StatusCode = result.Succeeded ? 200 : 500; + } + else if (req.Path == new PathString("/protected")) + { + res.StatusCode = 401; + } + else if (req.Path.StartsWithSegments(new PathString("/pwdLogin"), out remainder)) + { + var isPersistent = bool.Parse(remainder.Value.Substring(1)); + var result = await signInManager.PasswordSignInAsync("hao", TestPassword, isPersistent, false); + res.StatusCode = result.Succeeded ? 200 : 500; + } + else if (req.Path == new PathString("/twofactorRememeber")) + { + var user = await userManager.FindByNameAsync("hao"); + await signInManager.RememberTwoFactorClientAsync(user); + res.StatusCode = 200; + } + else if (req.Path == new PathString("/isTwoFactorRememebered")) + { + var user = await userManager.FindByNameAsync("hao"); + var result = await signInManager.IsTwoFactorClientRememberedAsync(user); + res.StatusCode = result ? 200 : 500; + } + else if (req.Path == new PathString("/twofactorSignIn")) + { + } + else if (req.Path == new PathString("/me")) + { + Describe(res, new AuthenticationResult(context.User, new AuthenticationProperties(), new AuthenticationDescription())); + } + else if (req.Path.StartsWithSegments(new PathString("/me"), out remainder)) + { + var result = await context.AuthenticateAsync(remainder.Value.Substring(1)); + Describe(res, result); + } + else if (req.Path == new PathString("/testpath") && testpath != null) + { + await testpath(context); + } + else + { + await next(); + } + }); + }); + server.BaseAddress = baseAddress; + return server; + } + + private static void Describe(HttpResponse res, AuthenticationResult result) + { + res.StatusCode = 200; + res.ContentType = "text/xml"; + var xml = new XElement("xml"); + if (result != null && result.Principal != null) + { + xml.Add(result.Principal.Claims.Select(claim => new XElement("claim", new XAttribute("type", claim.Type), new XAttribute("value", claim.Value)))); + } + if (result != null && result.Properties != null) + { + xml.Add(result.Properties.Dictionary.Select(extra => new XElement("extra", new XAttribute("type", extra.Key), new XAttribute("value", extra.Value)))); + } + 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); + } + } + + private static async Task SendAsync(TestServer server, string uri, string cookieHeader = null, bool ajaxRequest = false) + { + var request = new HttpRequestMessage(HttpMethod.Get, uri); + if (!string.IsNullOrEmpty(cookieHeader)) + { + request.Headers.Add("Cookie", cookieHeader); + } + if (ajaxRequest) + { + request.Headers.Add("X-Requested-With", "XMLHttpRequest"); + } + 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").SingleOrDefault(); + } + if (!string.IsNullOrEmpty(transaction.SetCookie)) + { + transaction.CookieNameValue = transaction.SetCookie.Split(new[] { ';' }, 2).First(); + } + 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; + } + + private class Transaction + { + public HttpRequestMessage Request { get; set; } + public HttpResponseMessage Response { get; set; } + + public string SetCookie { get; set; } + public string CookieNameValue { get; set; } + + public string ResponseText { get; set; } + public XElement ResponseElement { get; set; } + } + } +} diff --git a/test/Microsoft.AspNet.Identity.InMemory.Test/TestClock.cs b/test/Microsoft.AspNet.Identity.InMemory.Test/TestClock.cs new file mode 100644 index 0000000000..543efe4ada --- /dev/null +++ b/test/Microsoft.AspNet.Identity.InMemory.Test/TestClock.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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.Authentication; + +namespace Microsoft.AspNet.Identity.InMemory +{ + public class TestClock : ISystemClock + { + public TestClock() + { + UtcNow = new DateTimeOffset(2013, 6, 11, 12, 34, 56, 789, TimeSpan.Zero); + } + + public DateTimeOffset UtcNow { get; set; } + + public void Add(TimeSpan timeSpan) + { + UtcNow = UtcNow + timeSpan; + } + } +} diff --git a/test/Microsoft.AspNet.Identity.InMemory.Test/project.json b/test/Microsoft.AspNet.Identity.InMemory.Test/project.json index f0ed2441cf..ea2e1e38fc 100644 --- a/test/Microsoft.AspNet.Identity.InMemory.Test/project.json +++ b/test/Microsoft.AspNet.Identity.InMemory.Test/project.json @@ -1,22 +1,24 @@ { "dependencies": { + "Microsoft.AspNet.Authentication" : "1.0.0-*", + "Microsoft.AspNet.Authentication.Cookies" : "1.0.0-*", "Microsoft.AspNet.Hosting": "1.0.0-*", "Microsoft.AspNet.Http" : "1.0.0-*", "Microsoft.AspNet.Identity" : "3.0.0-*", "Microsoft.AspNet.RequestContainer" : "1.0.0-*", - "Microsoft.AspNet.Authentication" : "1.0.0-*", - "Microsoft.AspNet.Authentication.Cookies" : "1.0.0-*", + "Microsoft.AspNet.TestHost": "1.0.0-*", "Microsoft.AspNet.Testing" : "1.0.0-*", "Microsoft.Framework.ConfigurationModel": "1.0.0-*", "Microsoft.Framework.DependencyInjection" : "1.0.0-*", "Microsoft.Framework.OptionsModel" : "1.0.0-*", + "Moq": "4.2.1312.1622", "xunit.runner.kre": "1.0.0-*" }, "code": "**\\*.cs;..\\Shared\\*.cs", "frameworks": { "dnx451": { "dependencies": { - "Moq" : "4.2.1312.1622" + "Shouldly": "1.1.1.1" } } }, diff --git a/test/Microsoft.AspNet.Identity.Test/IHttpContextAccessor.cs b/test/Microsoft.AspNet.Identity.Test/IHttpContextAccessor.cs deleted file mode 100644 index b812ed6e8b..0000000000 --- a/test/Microsoft.AspNet.Identity.Test/IHttpContextAccessor.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Microsoft.AspNet.Identity.Test -{ - internal interface IHttpContextAccessor - { - } -} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.Test/PrincipalExtensionsTest.cs b/test/Microsoft.AspNet.Identity.Test/PrincipalExtensionsTest.cs index 8a2e5b3a47..6d63e70e49 100644 --- a/test/Microsoft.AspNet.Identity.Test/PrincipalExtensionsTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/PrincipalExtensionsTest.cs @@ -15,21 +15,12 @@ namespace Microsoft.AspNet.Identity.Test [Fact] public void IdentityNullCheckTest() { - IPrincipal p = null; - Assert.Throws("principal", () => p.GetUserId()); - Assert.Throws("principal", () => p.GetUserName()); ClaimsPrincipal cp = null; + Assert.Throws("principal", () => cp.GetUserId()); + Assert.Throws("principal", () => cp.GetUserName()); Assert.Throws("principal", () => cp.FindFirstValue(null)); } - [Fact] - public void IdentityNullIfNotClaimsIdentityTest() - { - IPrincipal identity = new TestPrincipal(); - Assert.Null(identity.GetUserId()); - Assert.Null(identity.GetUserName()); - } - [Fact] public void UserNameAndIdTest() { @@ -82,39 +73,5 @@ namespace Microsoft.AspNet.Identity.Test }, ExternalAuthenticationScheme)); } - - private class TestPrincipal : IPrincipal - { - public IIdentity Identity - { - get - { - throw new NotImplementedException(); - } - } - - public bool IsInRole(string role) - { - throw new NotImplementedException(); - } - } - - private class TestIdentity : IIdentity - { - public string AuthenticationType - { - get { throw new NotImplementedException(); } - } - - public bool IsAuthenticated - { - get { throw new NotImplementedException(); } - } - - public string Name - { - get { throw new NotImplementedException(); } - } - } } } \ No newline at end of file