Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance Lockout Functionality: Add Test for AccessFailedAsync Incrementing Count #60350

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
6 changes: 6 additions & 0 deletions src/Identity/Extensions.Core/src/LockoutOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,10 @@ public class LockoutOptions
/// </summary>
/// <value>The <see cref="TimeSpan"/> a user is locked out for when a lockout occurs.</value>
public TimeSpan DefaultLockoutTimeSpan { get; set; } = TimeSpan.FromMinutes(5);

/// <summary>
/// Specifies whether the lockout should be permanent.
/// If true, the user will be locked out indefinitely.
/// </summary>
public bool PermanentLockout { get; set; }
}
2 changes: 2 additions & 0 deletions src/Identity/Extensions.Core/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
#nullable enable
Microsoft.AspNetCore.Identity.LockoutOptions.PermanentLockout.get -> bool
Microsoft.AspNetCore.Identity.LockoutOptions.PermanentLockout.set -> void
21 changes: 18 additions & 3 deletions src/Identity/Extensions.Core/src/UserManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1818,15 +1818,30 @@ public virtual async Task<IdentityResult> AccessFailedAsync(TUser user)
var store = GetUserLockoutStore();
ArgumentNullThrowHelper.ThrowIfNull(user);

// If this puts the user over the threshold for lockout, lock them out and reset the access failed count
// If PermanentLockout is enabled, lock the user indefinitely
if (Options.Lockout.PermanentLockout)
{
Logger.LogDebug(LoggerEventIds.UserLockedOut, "User is permanently locked out.");
await store.SetLockoutEndDateAsync(user, DateTimeOffset.MaxValue, CancellationToken).ConfigureAwait(false);
return await UpdateUserAsync(user).ConfigureAwait(false);
}

// Increment access failed count
var count = await store.IncrementAccessFailedCountAsync(user, CancellationToken).ConfigureAwait(false);
if (count < Options.Lockout.MaxFailedAccessAttempts)
{
return await UpdateUserAsync(user).ConfigureAwait(false);
}

Logger.LogDebug(LoggerEventIds.UserLockedOut, "User is locked out.");
await store.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow.Add(Options.Lockout.DefaultLockoutTimeSpan),
CancellationToken).ConfigureAwait(false);

// Set the lockout time based on configuration
var now = DateTimeOffset.UtcNow;
DateTimeOffset lockoutEnd = Options.Lockout.DefaultLockoutTimeSpan == TimeSpan.MaxValue
? DateTimeOffset.MaxValue
: now.Add(Options.Lockout.DefaultLockoutTimeSpan);

await store.SetLockoutEndDateAsync(user, lockoutEnd, CancellationToken).ConfigureAwait(false);
await store.ResetAccessFailedCountAsync(user, CancellationToken).ConfigureAwait(false);
return await UpdateUserAsync(user).ConfigureAwait(false);
}
Expand Down
27 changes: 27 additions & 0 deletions src/Identity/test/Identity.Test/UserManagerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1012,6 +1012,33 @@ public async Task ResetTokenCallNoopForTokenValueZero()
IdentityResultAssert.IsSuccess(await manager.ResetAccessFailedCountAsync(user));
}

[Fact]
public async Task AccessFailedAsyncIncrementsAccessFailedCount()
{
// Arrange
var user = new PocoUser() { UserName = "testuser" };
var store = new Mock<IUserLockoutStore<PocoUser>>();
int failedCount = 1; // Simulated access failed count

store.Setup(x => x.GetAccessFailedCountAsync(user, It.IsAny<CancellationToken>()))
.ReturnsAsync(() => failedCount); // Return the updated value dynamically

store.Setup(x => x.IncrementAccessFailedCountAsync(user, It.IsAny<CancellationToken>()))
.ReturnsAsync(() => ++failedCount); // Increment and return the new count

var manager = MockHelpers.TestUserManager(store.Object);

// Act
var result = await manager.AccessFailedAsync(user);

// Assert
IdentityResultAssert.IsSuccess(result);
store.Verify(x => x.IncrementAccessFailedCountAsync(user, It.IsAny<CancellationToken>()), Times.Once);

var newFailedCount = await manager.GetAccessFailedCountAsync(user);
Assert.Equal(2, newFailedCount); // Ensure the count was actually updated
}

[Fact]
public async Task ManagerPublicNullChecks()
{
Expand Down
Loading