Add OnSecurityStampReplacingPrincipal event

This commit is contained in:
Hao Kung 2016-10-13 15:07:58 -07:00
parent a664d32c5c
commit aba7cf96b9
4 changed files with 94 additions and 2 deletions

View File

@ -2,6 +2,7 @@
// 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.Identity;
namespace Microsoft.AspNetCore.Builder
@ -74,5 +75,10 @@ namespace Microsoft.AspNetCore.Builder
/// The <see cref="TimeSpan"/> after which security stamps are re-validated.
/// </value>
public TimeSpan SecurityStampValidationInterval { get; set; } = TimeSpan.FromMinutes(30);
/// <summary>
/// Invoked when the default security stamp validator replaces the user's ClaimsPrincipal in the cookie.
/// </summary>
public Func<SecurityStampRefreshingPrincipalContext, Task> OnSecurityStampRefreshingPrincipal { get; set; }
}
}

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 System.Security.Claims;
namespace Microsoft.AspNetCore.Identity
{
/// <summary>
/// Used to pass information during the SecurityStamp validation event.
/// </summary>
public class SecurityStampRefreshingPrincipalContext
{
/// <summary>
/// The principal contained in the current cookie.
/// </summary>
public ClaimsPrincipal CurrentPrincipal { get; set; }
/// <summary>
/// The new principal which should replace the current.
/// </summary>
public ClaimsPrincipal NewPrincipal { get; set; }
}
}

View File

@ -66,8 +66,23 @@ namespace Microsoft.AspNetCore.Identity
var user = await _signInManager.ValidateSecurityStampAsync(context.Principal);
if (user != null)
{
// REVIEW: note we lost login authenticaiton method
context.ReplacePrincipal(await _signInManager.CreateUserPrincipalAsync(user));
var newPrincipal = await _signInManager.CreateUserPrincipalAsync(user);
if (_options.OnSecurityStampRefreshingPrincipal != null)
{
var replaceContext = new SecurityStampRefreshingPrincipalContext
{
CurrentPrincipal = context.Principal,
NewPrincipal = newPrincipal
};
// Note: a null principal is allowed and results in a failed authentication.
await _options.OnSecurityStampRefreshingPrincipal(replaceContext);
newPrincipal = replaceContext.NewPrincipal;
}
// REVIEW: note we lost login authentication method
context.ReplacePrincipal(newPrincipal);
context.ShouldRenew = true;
}
else

View File

@ -139,6 +139,54 @@ namespace Microsoft.AspNetCore.Identity.InMemory
Assert.Equal("hao", FindClaimValue(transaction6, ClaimTypes.Name));
}
[Fact]
public async Task CanAccessOldPrincipalDuringSecurityStampReplacement()
{
var clock = new TestClock();
var server = CreateServer(services => services.Configure<IdentityOptions>(options =>
{
options.Cookies.ApplicationCookie.SystemClock = clock;
options.OnSecurityStampRefreshingPrincipal = c =>
{
var newId = new ClaimsIdentity();
newId.AddClaim(new Claim("PreviousName", c.CurrentPrincipal.Identity.Name));
c.NewPrincipal.AddIdentity(newId);
return Task.FromResult(0);
};
}));
var transaction1 = await SendAsync(server, "http://example.com/createMe");
Assert.Equal(HttpStatusCode.OK, transaction1.Response.StatusCode);
Assert.Null(transaction1.SetCookie);
var transaction2 = await SendAsync(server, "http://example.com/pwdLogin/false" );
Assert.Equal(HttpStatusCode.OK, transaction2.Response.StatusCode);
Assert.NotNull(transaction2.SetCookie);
Assert.DoesNotContain("; expires=", transaction2.SetCookie);
var transaction3 = await SendAsync(server, "http://example.com/me", transaction2.CookieNameValue);
Assert.Equal("hao", FindClaimValue(transaction3, ClaimTypes.Name));
Assert.Null(transaction3.SetCookie);
// Make sure we don't get a new cookie yet
clock.Add(TimeSpan.FromMinutes(10));
var transaction4 = await SendAsync(server, "http://example.com/me", transaction2.CookieNameValue);
Assert.Equal("hao", FindClaimValue(transaction4, ClaimTypes.Name));
Assert.Null(transaction4.SetCookie);
// Go past SecurityStampValidation interval and ensure we get a new cookie
clock.Add(TimeSpan.FromMinutes(21));
var transaction5 = await SendAsync(server, "http://example.com/me", transaction2.CookieNameValue);
Assert.NotNull(transaction5.SetCookie);
Assert.Equal("hao", FindClaimValue(transaction5, ClaimTypes.Name));
Assert.Equal("hao", FindClaimValue(transaction5, "PreviousName"));
// Make sure new cookie is valid
var transaction6 = await SendAsync(server, "http://example.com/me", transaction5.CookieNameValue);
Assert.Equal("hao", FindClaimValue(transaction6, ClaimTypes.Name));
}
[Fact]
public async Task TwoFactorRememberCookieVerification()
{