Skip to content

Commit ec5ca09

Browse files
committed
Merge in 'release/5.0' changes
2 parents ee27b90 + 1d1f0dc commit ec5ca09

File tree

3 files changed

+114
-1
lines changed

3 files changed

+114
-1
lines changed

src/Identity/Core/src/SecurityStampValidator.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,13 @@ protected virtual async Task SecurityStampVerified(TUser user, CookieValidatePri
9090
// REVIEW: note we lost login authentication method
9191
context.ReplacePrincipal(newPrincipal);
9292
context.ShouldRenew = true;
93+
94+
if (!context.Options.SlidingExpiration)
95+
{
96+
// On renewal calculate the new ticket length relative to now to avoid
97+
// extending the expiration.
98+
context.Properties.IssuedUtc = Clock.UtcNow;
99+
}
93100
}
94101

95102
/// <summary>
@@ -110,7 +117,7 @@ protected virtual Task<TUser> VerifySecurityStamp(ClaimsPrincipal principal)
110117
public virtual async Task ValidateAsync(CookieValidatePrincipalContext context)
111118
{
112119
var currentUtc = DateTimeOffset.UtcNow;
113-
if (context.Options != null && Clock != null)
120+
if (Clock != null)
114121
{
115122
currentUtc = Clock.UtcNow;
116123
}

src/Identity/test/Identity.Test/SecurityStampValidatorTest.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,53 @@ public async Task OnValidateIdentityDoesNotRejectsWhenNotExpired()
264264
Assert.NotNull(context.Principal);
265265
}
266266

267+
[Fact]
268+
public async Task OnValidateIdentityDoesNotExtendExpirationWhenSlidingIsDisabled()
269+
{
270+
var user = new PocoUser("test");
271+
var httpContext = new Mock<HttpContext>();
272+
var userManager = MockHelpers.MockUserManager<PocoUser>();
273+
var identityOptions = new Mock<IOptions<IdentityOptions>>();
274+
identityOptions.Setup(a => a.Value).Returns(new IdentityOptions());
275+
var claimsManager = new Mock<IUserClaimsPrincipalFactory<PocoUser>>();
276+
var options = new Mock<IOptions<SecurityStampValidatorOptions>>();
277+
options.Setup(a => a.Value).Returns(new SecurityStampValidatorOptions { ValidationInterval = TimeSpan.FromMinutes(1) });
278+
var contextAccessor = new Mock<IHttpContextAccessor>();
279+
contextAccessor.Setup(a => a.HttpContext).Returns(httpContext.Object);
280+
var signInManager = new Mock<SignInManager<PocoUser>>(userManager.Object,
281+
contextAccessor.Object, claimsManager.Object, identityOptions.Object, null, new Mock<IAuthenticationSchemeProvider>().Object, new DefaultUserConfirmation<PocoUser>());
282+
signInManager.Setup(s => s.ValidateSecurityStampAsync(It.IsAny<ClaimsPrincipal>())).Returns(Task.FromResult(user));
283+
signInManager.Setup(s => s.CreateUserPrincipalAsync(It.IsAny<PocoUser>())).Returns(Task.FromResult(new ClaimsPrincipal(new ClaimsIdentity("auth"))));
284+
signInManager.Setup(s => s.SignInAsync(user, false, null)).Throws(new Exception("Shouldn't be called"));
285+
var services = new ServiceCollection();
286+
services.AddSingleton(options.Object);
287+
services.AddSingleton(signInManager.Object);
288+
var clock = new SystemClock();
289+
services.AddSingleton<ISecurityStampValidator>(new SecurityStampValidator<PocoUser>(options.Object, signInManager.Object, clock, new LoggerFactory()));
290+
httpContext.Setup(c => c.RequestServices).Returns(services.BuildServiceProvider());
291+
var id = new ClaimsIdentity(IdentityConstants.ApplicationScheme);
292+
id.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id));
293+
294+
var ticket = new AuthenticationTicket(new ClaimsPrincipal(id),
295+
new AuthenticationProperties
296+
{
297+
IssuedUtc = clock.UtcNow - TimeSpan.FromDays(1),
298+
ExpiresUtc = clock.UtcNow + TimeSpan.FromDays(1),
299+
},
300+
IdentityConstants.ApplicationScheme);
301+
var context = new CookieValidatePrincipalContext(httpContext.Object, new AuthenticationSchemeBuilder(IdentityConstants.ApplicationScheme) { HandlerType = typeof(NoopHandler) }.Build(),
302+
new CookieAuthenticationOptions() { SlidingExpiration = false }, ticket);
303+
Assert.NotNull(context.Properties);
304+
Assert.NotNull(context.Options);
305+
Assert.NotNull(context.Principal);
306+
await SecurityStampValidator.ValidatePrincipalAsync(context);
307+
308+
// Issued is moved forward, expires is not.
309+
Assert.Equal(clock.UtcNow, context.Properties.IssuedUtc);
310+
Assert.Equal(clock.UtcNow + TimeSpan.FromDays(1), context.Properties.ExpiresUtc);
311+
Assert.NotNull(context.Principal);
312+
}
313+
267314
private async Task RunRememberClientCookieTest(bool shouldStampValidate, bool validationSuccess)
268315
{
269316
var user = new PocoUser("test");

src/Security/Authentication/test/CookieTests.cs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,65 @@ public async Task CookieCanBeRenewedByValidatorWithModifiedProperties()
713713
Assert.Null(FindClaimValue(transaction5, "counter"));
714714
}
715715

716+
[Fact]
717+
public async Task CookieCanBeRenewedByValidatorWithModifiedLifetime()
718+
{
719+
using var host = await CreateHost(o =>
720+
{
721+
o.ExpireTimeSpan = TimeSpan.FromMinutes(10);
722+
o.Events = new CookieAuthenticationEvents
723+
{
724+
OnValidatePrincipal = ctx =>
725+
{
726+
ctx.ShouldRenew = true;
727+
var id = ctx.Principal.Identities.First();
728+
var claim = id.FindFirst("counter");
729+
if (claim == null)
730+
{
731+
id.AddClaim(new Claim("counter", "1"));
732+
}
733+
else
734+
{
735+
id.RemoveClaim(claim);
736+
id.AddClaim(new Claim("counter", claim.Value + "1"));
737+
}
738+
// Causes the expiry time to not be extended because the lifetime is
739+
// calculated relative to the issue time.
740+
ctx.Properties.IssuedUtc = _clock.UtcNow;
741+
return Task.FromResult(0);
742+
}
743+
};
744+
},
745+
context =>
746+
context.SignInAsync("Cookies",
747+
new ClaimsPrincipal(new ClaimsIdentity(new GenericIdentity("Alice", "Cookies")))));
748+
749+
using var server = host.GetTestServer();
750+
var transaction1 = await SendAsync(server, "http://example.com/testpath");
751+
752+
var transaction2 = await SendAsync(server, "http://example.com/me/Cookies", transaction1.CookieNameValue);
753+
Assert.NotNull(transaction2.SetCookie);
754+
Assert.Equal("1", FindClaimValue(transaction2, "counter"));
755+
756+
_clock.Add(TimeSpan.FromMinutes(1));
757+
758+
var transaction3 = await SendAsync(server, "http://example.com/me/Cookies", transaction2.CookieNameValue);
759+
Assert.NotNull(transaction3.SetCookie);
760+
Assert.Equal("11", FindClaimValue(transaction3, "counter"));
761+
762+
_clock.Add(TimeSpan.FromMinutes(1));
763+
764+
var transaction4 = await SendAsync(server, "http://example.com/me/Cookies", transaction3.CookieNameValue);
765+
Assert.NotNull(transaction4.SetCookie);
766+
Assert.Equal("111", FindClaimValue(transaction4, "counter"));
767+
768+
_clock.Add(TimeSpan.FromMinutes(9));
769+
770+
var transaction5 = await SendAsync(server, "http://example.com/me/Cookies", transaction4.CookieNameValue);
771+
Assert.Null(transaction5.SetCookie);
772+
Assert.Null(FindClaimValue(transaction5, "counter"));
773+
}
774+
716775
[Fact]
717776
public async Task CookieValidatorOnlyCalledOnce()
718777
{

0 commit comments

Comments
 (0)