Skip to content

Commit 3bc8a31

Browse files
author
Adit Sheth
committed
Fixed bug 60181.
1 parent ff827d2 commit 3bc8a31

File tree

3 files changed

+54
-15
lines changed

3 files changed

+54
-15
lines changed

src/Identity/Extensions.Core/src/LockoutOptions.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,10 @@ public class LockoutOptions
3232
/// </summary>
3333
/// <value>The <see cref="TimeSpan"/> a user is locked out for when a lockout occurs.</value>
3434
public TimeSpan DefaultLockoutTimeSpan { get; set; } = TimeSpan.FromMinutes(5);
35+
36+
/// <summary>
37+
/// Specifies whether the lockout should be permanent.
38+
/// If true, the user will be locked out indefinitely.
39+
/// </summary>
40+
public bool PermanentLockout { get; set; } = false;
3541
}

src/Identity/Extensions.Core/src/UserManager.cs

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1812,24 +1812,39 @@ public virtual async Task<IdentityResult> SetLockoutEndDateAsync(TUser user, Dat
18121812
/// </summary>
18131813
/// <param name="user">The user whose failed access count to increment.</param>
18141814
/// <returns>The <see cref="Task"/> that represents the asynchronous operation, containing the <see cref="IdentityResult"/> of the operation.</returns>
1815-
public virtual async Task<IdentityResult> AccessFailedAsync(TUser user)
1816-
{
1817-
ThrowIfDisposed();
1818-
var store = GetUserLockoutStore();
1819-
ArgumentNullThrowHelper.ThrowIfNull(user);
1815+
public virtual async Task<IdentityResult> AccessFailedAsync(TUser user)
1816+
{
1817+
ThrowIfDisposed();
1818+
var store = GetUserLockoutStore();
1819+
ArgumentNullThrowHelper.ThrowIfNull(user);
18201820

1821-
// If this puts the user over the threshold for lockout, lock them out and reset the access failed count
1822-
var count = await store.IncrementAccessFailedCountAsync(user, CancellationToken).ConfigureAwait(false);
1823-
if (count < Options.Lockout.MaxFailedAccessAttempts)
1824-
{
1825-
return await UpdateUserAsync(user).ConfigureAwait(false);
1826-
}
1827-
Logger.LogDebug(LoggerEventIds.UserLockedOut, "User is locked out.");
1828-
await store.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow.Add(Options.Lockout.DefaultLockoutTimeSpan),
1829-
CancellationToken).ConfigureAwait(false);
1830-
await store.ResetAccessFailedCountAsync(user, CancellationToken).ConfigureAwait(false);
1821+
// If this puts the user over the threshold for lockout, lock them out and reset the access failed count
1822+
var count = await store.IncrementAccessFailedCountAsync(user, CancellationToken).ConfigureAwait(false);
1823+
if (count < Options.Lockout.MaxFailedAccessAttempts)
1824+
{
18311825
return await UpdateUserAsync(user).ConfigureAwait(false);
18321826
}
1827+
Logger.LogDebug(LoggerEventIds.UserLockedOut, "User is locked out.");
1828+
1829+
// Prevent overflow when setting lockout end date
1830+
var now = DateTimeOffset.UtcNow;
1831+
DateTimeOffset lockoutEnd;
1832+
1833+
if (Options.Lockout.DefaultLockoutTimeSpan == TimeSpan.MaxValue)
1834+
{
1835+
lockoutEnd = DateTimeOffset.MaxValue;
1836+
}
1837+
else
1838+
{
1839+
lockoutEnd = now > (DateTimeOffset.MaxValue - Options.Lockout.DefaultLockoutTimeSpan)
1840+
? DateTimeOffset.MaxValue
1841+
: now.Add(Options.Lockout.DefaultLockoutTimeSpan);
1842+
}
1843+
1844+
await store.SetLockoutEndDateAsync(user, lockoutEnd, CancellationToken).ConfigureAwait(false);
1845+
await store.ResetAccessFailedCountAsync(user, CancellationToken).ConfigureAwait(false);
1846+
return await UpdateUserAsync(user).ConfigureAwait(false);
1847+
}
18331848

18341849
/// <summary>
18351850
/// Resets the access failed count for the specified <paramref name="user"/>.

src/Identity/test/Identity.Test/UserManagerTest.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1012,6 +1012,24 @@ public async Task ResetTokenCallNoopForTokenValueZero()
10121012
IdentityResultAssert.IsSuccess(await manager.ResetAccessFailedCountAsync(user));
10131013
}
10141014

1015+
[Fact]
1016+
public async Task AccessFailedAsync_IncrementsAccessFailedCount()
1017+
{
1018+
// Arrange
1019+
var user = new PocoUser() { UserName = "testuser" };
1020+
var store = new Mock<IUserLockoutStore<PocoUser>>();
1021+
store.Setup(x => x.GetAccessFailedCountAsync(user, It.IsAny<CancellationToken>())).ReturnsAsync(1);
1022+
store.Setup(x => x.IncrementAccessFailedCountAsync(user, It.IsAny<CancellationToken>())).ReturnsAsync(2);
1023+
1024+
var manager = MockHelpers.TestUserManager(store.Object);
1025+
1026+
// Act
1027+
var result = await manager.AccessFailedAsync(user);
1028+
1029+
// Assert
1030+
Assert.Equal(2, result);
1031+
}
1032+
10151033
[Fact]
10161034
public async Task ManagerPublicNullChecks()
10171035
{

0 commit comments

Comments
 (0)