Skip to content

Commit 280f398

Browse files
committed
Prepare for nullable annotations in Microsoft.Extensions.Identity.Core
Nullable annotations have been added to Microsoft.Extensions.Identity.Core [1] in .NET 7, necessitating these changes to avoid CS8604 and CS8634 compiler warnings on upgrading. [1] dotnet/aspnetcore#40007
1 parent 17a80e5 commit 280f398

File tree

3 files changed

+89
-83
lines changed

3 files changed

+89
-83
lines changed

src/Buttercup.Web.Tests/Authentication/AuthenticationManagerTests.cs

Lines changed: 44 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ public async Task ChangePasswordDoesNotChangePasswordIfCurrentPasswordDoesNotMat
227227
await fixture.ChangePassword();
228228

229229
fixture.MockPasswordHasher.Verify(
230-
x => x.HashPassword(null, fixture.NewPassword), Times.Never);
230+
x => x.HashPassword(fixture.User, fixture.NewPassword), Times.Never);
231231
}
232232

233233
[Fact]
@@ -311,7 +311,7 @@ private ChangePasswordFixture(string? hashedPassword)
311311
this.HttpContext.SetCurrentUser(this.User);
312312

313313
this.MockPasswordHasher
314-
.Setup(x => x.HashPassword(null, this.NewPassword))
314+
.Setup(x => x.HashPassword(this.User, this.NewPassword))
315315
.Returns(this.HashedNewPassword);
316316

317317
this.MockRandomTokenGenerator
@@ -451,7 +451,9 @@ public Task<bool> PasswordResetTokenIsValid() =>
451451
[Fact]
452452
public async Task ResetPasswordDeletesExpiredPasswordResetTokens()
453453
{
454-
var fixture = ResetPasswordFixture.ForSuccess();
454+
var fixture = new ResetPasswordFixture();
455+
456+
fixture.SetupSuccess();
455457

456458
await fixture.ResetPassword();
457459

@@ -462,7 +464,9 @@ public async Task ResetPasswordDeletesExpiredPasswordResetTokens()
462464
[Fact]
463465
public async Task ResetPasswordLogsIfTokenIsInvalid()
464466
{
465-
var fixture = ResetPasswordFixture.ForInvalidToken();
467+
var fixture = new ResetPasswordFixture();
468+
469+
fixture.SetupInvalidToken();
466470

467471
try
468472
{
@@ -482,40 +486,48 @@ public async Task ResetPasswordLogsIfTokenIsInvalid()
482486
[Fact]
483487
public async Task ResetPasswordThrowsIfTokenIsInvalid()
484488
{
485-
var fixture = ResetPasswordFixture.ForInvalidToken();
489+
var fixture = new ResetPasswordFixture();
490+
491+
fixture.SetupInvalidToken();
486492

487493
await Assert.ThrowsAsync<InvalidTokenException>(fixture.ResetPassword);
488494
}
489495

490496
[Fact]
491497
public async Task ResetPasswordUpdatesUserOnSuccess()
492498
{
493-
var fixture = ResetPasswordFixture.ForSuccess();
499+
var fixture = new ResetPasswordFixture();
500+
501+
fixture.SetupSuccess();
494502

495503
await fixture.ResetPassword();
496504

497505
fixture.MockUserDataProvider.Verify(x => x.UpdatePassword(
498506
fixture.MySqlConnection,
499-
fixture.UserId!.Value,
507+
fixture.User.Id,
500508
fixture.NewHashedPassword,
501509
fixture.NewSecurityStamp));
502510
}
503511

504512
[Fact]
505513
public async Task ResetPasswordDeletesPasswordResetTokensOnSuccess()
506514
{
507-
var fixture = ResetPasswordFixture.ForSuccess();
515+
var fixture = new ResetPasswordFixture();
516+
517+
fixture.SetupSuccess();
508518

509519
await fixture.ResetPassword();
510520

511521
fixture.MockPasswordResetTokenDataProvider.Verify(
512-
x => x.DeleteTokensForUser(fixture.MySqlConnection, fixture.UserId!.Value));
522+
x => x.DeleteTokensForUser(fixture.MySqlConnection, fixture.User.Id));
513523
}
514524

515525
[Fact]
516526
public async Task ResetPasswordSendsPasswordChangeNotificationOnSuccess()
517527
{
518-
var fixture = ResetPasswordFixture.ForSuccess();
528+
var fixture = new ResetPasswordFixture();
529+
530+
fixture.SetupSuccess();
519531

520532
await fixture.ResetPassword();
521533

@@ -526,72 +538,59 @@ public async Task ResetPasswordSendsPasswordChangeNotificationOnSuccess()
526538
[Fact]
527539
public async Task ResetPasswordLogsOnSuccess()
528540
{
529-
var fixture = ResetPasswordFixture.ForSuccess();
541+
var fixture = new ResetPasswordFixture();
542+
543+
fixture.SetupSuccess();
530544

531545
await fixture.ResetPassword();
532546

533547
fixture.Logger.AssertSingleEntry(
534548
LogLevel.Information,
535-
$"Password reset for user {fixture.UserId} using token {fixture.RedactedToken}");
549+
$"Password reset for user {fixture.User.Id} using token {fixture.RedactedToken}");
536550

537-
fixture.AssertAuthenticationEventLogged("password_reset_success", fixture.UserId);
551+
fixture.AssertAuthenticationEventLogged("password_reset_success", fixture.User.Id);
538552
}
539553

540554
[Fact]
541-
public async Task ResetPasswordReturnsUserOnSuccess()
555+
public async Task ResetPasswordReturnsUserWithNewSecurityStampOnSuccess()
542556
{
543-
var fixture = ResetPasswordFixture.ForSuccess();
557+
var fixture = new ResetPasswordFixture();
558+
559+
fixture.SetupSuccess();
544560

545561
var actual = await fixture.ResetPassword();
546562

547-
Assert.Equal(fixture.User, actual);
563+
Assert.Equal(fixture.User with { SecurityStamp = fixture.NewSecurityStamp }, actual);
548564
}
549565

550566
private class ResetPasswordFixture : AuthenticationManagerFixture
551567
{
552568
private const string NewPassword = "new-password";
553569
private const string Token = "password-reset-token";
554570

555-
private ResetPasswordFixture()
556-
{
557-
}
571+
public string RedactedToken { get; } = "passwo…";
558572

559-
private ResetPasswordFixture(long? userId)
560-
{
561-
this.UserId = userId;
562-
this.SetupGetUserIdForToken(Token, userId);
563-
}
573+
public User User { get; } = ModelFactory.CreateUser();
564574

565-
private ResetPasswordFixture(User user)
566-
: this(user.Id)
567-
{
568-
this.User = user;
575+
public string NewHashedPassword { get; } = "new-hashed-password";
576+
577+
public string NewSecurityStamp { get; } = "new-security-stamp";
569578

570-
this.SetupGetUser(user.Id, user);
579+
public void SetupInvalidToken() => this.SetupGetUserIdForToken(Token, null);
571580

581+
public void SetupSuccess()
582+
{
583+
this.SetupGetUserIdForToken(Token, this.User.Id);
584+
this.SetupGetUser(this.User.Id, this.User);
572585
this.MockPasswordHasher
573-
.Setup(x => x.HashPassword(null, NewPassword))
586+
.Setup(x => x.HashPassword(this.User, NewPassword))
574587
.Returns(this.NewHashedPassword);
575588

576589
this.MockRandomTokenGenerator
577590
.Setup(x => x.Generate(2))
578591
.Returns(this.NewSecurityStamp);
579592
}
580593

581-
public string RedactedToken { get; } = "passwo…";
582-
583-
public long? UserId { get; }
584-
585-
public User? User { get; }
586-
587-
public string NewHashedPassword { get; } = "new-hashed-password";
588-
589-
public string NewSecurityStamp { get; } = "new-security-stamp";
590-
591-
public static ResetPasswordFixture ForInvalidToken() => new();
592-
593-
public static ResetPasswordFixture ForSuccess() => new(ModelFactory.CreateUser());
594-
595594
public Task<User> ResetPassword() =>
596595
this.AuthenticationManager.ResetPassword(Token, NewPassword);
597596
}
@@ -1010,7 +1009,7 @@ public AuthenticationManagerFixture()
10101009

10111010
public Mock<IAuthenticationService> MockAuthenticationService { get; } = new();
10121011

1013-
public Mock<IPasswordHasher<User?>> MockPasswordHasher { get; } = new();
1012+
public Mock<IPasswordHasher<User>> MockPasswordHasher { get; } = new();
10141013

10151014
public Mock<IPasswordResetTokenDataProvider> MockPasswordResetTokenDataProvider { get; } = new();
10161015

src/Buttercup.Web/Authentication/AuthenticationManager.cs

Lines changed: 44 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public class AuthenticationManager : IAuthenticationManager
1818
private readonly IClock clock;
1919
private readonly IMySqlConnectionSource mySqlConnectionSource;
2020
private readonly ILogger<AuthenticationManager> logger;
21-
private readonly IPasswordHasher<User?> passwordHasher;
21+
private readonly IPasswordHasher<User> passwordHasher;
2222
private readonly IPasswordResetTokenDataProvider passwordResetTokenDataProvider;
2323
private readonly IRandomTokenGenerator randomTokenGenerator;
2424
private readonly IUrlHelperFactory urlHelperFactory;
@@ -32,7 +32,7 @@ public AuthenticationManager(
3232
IClock clock,
3333
IMySqlConnectionSource mySqlConnectionSource,
3434
ILogger<AuthenticationManager> logger,
35-
IPasswordHasher<User?> passwordHasher,
35+
IPasswordHasher<User> passwordHasher,
3636
IPasswordResetTokenDataProvider passwordResetTokenDataProvider,
3737
IRandomTokenGenerator randomTokenGenerator,
3838
IUrlHelperFactory urlHelperFactory,
@@ -79,7 +79,7 @@ await this.authenticationEventDataProvider.LogEvent(
7979
return null;
8080
}
8181

82-
if (!this.VerifyPassword(user, password))
82+
if (!this.VerifyPassword(user, user.HashedPassword, password))
8383
{
8484
AuthenticateLogMessages.IncorrectPassword(this.logger, user.Id, user.Email, null);
8585

@@ -113,7 +113,7 @@ await this.authenticationEventDataProvider.LogEvent(
113113
$"User {user.Id} ({user.Email}) does not have a password.");
114114
}
115115

116-
if (!this.VerifyPassword(user, currentPassword))
116+
if (!this.VerifyPassword(user, user.HashedPassword, currentPassword))
117117
{
118118
ChangePasswordLogMessages.IncorrectPassword(
119119
this.logger, user.Id, user.Email, null);
@@ -124,7 +124,7 @@ await this.authenticationEventDataProvider.LogEvent(
124124
return false;
125125
}
126126

127-
var newSecurityStamp = await this.SetPassword(connection, user.Id, newPassword);
127+
var newSecurityStamp = await this.SetPassword(connection, user, newPassword);
128128

129129
ChangePasswordLogMessages.Success(this.logger, user.Id, user.Email, null);
130130

@@ -176,18 +176,18 @@ await this.authenticationEventDataProvider.LogEvent(
176176
throw new InvalidTokenException("Password reset token is invalid");
177177
}
178178

179-
await this.SetPassword(connection, userId.Value, newPassword);
179+
var user = await this.userDataProvider.GetUser(connection, userId.Value);
180+
181+
var newSecurityStamp = await this.SetPassword(connection, user, newPassword);
180182

181183
ResetPasswordLogMessages.Success(this.logger, userId.Value, RedactToken(token), null);
182184

183185
await this.authenticationEventDataProvider.LogEvent(
184186
connection, "password_reset_success", userId.Value);
185187

186-
var user = await this.userDataProvider.GetUser(connection, userId.Value);
187-
188188
await this.authenticationMailer.SendPasswordChangeNotification(user.Email);
189189

190-
return user;
190+
return user with { SecurityStamp = newSecurityStamp };
191191
}
192192

193193
public async Task SendPasswordResetLink(ActionContext actionContext, string email)
@@ -258,50 +258,57 @@ public async Task ValidatePrincipal(CookieValidatePrincipalContext context)
258258
{
259259
var principal = context.Principal;
260260

261-
var userId = principal?.GetUserId();
261+
if (principal == null)
262+
{
263+
return;
264+
}
262265

263-
if (userId.HasValue)
266+
var userId = principal.GetUserId();
267+
268+
if (!userId.HasValue)
264269
{
265-
User user;
270+
return;
271+
}
266272

267-
using (var connection = await this.mySqlConnectionSource.OpenConnection())
268-
{
269-
user = await this.userDataProvider.GetUser(connection, userId.Value);
270-
}
273+
User user;
271274

272-
var securityStamp = principal.FindFirstValue(CustomClaimTypes.SecurityStamp);
275+
using (var connection = await this.mySqlConnectionSource.OpenConnection())
276+
{
277+
user = await this.userDataProvider.GetUser(connection, userId.Value);
278+
}
273279

274-
if (string.Equals(securityStamp, user.SecurityStamp, StringComparison.Ordinal))
275-
{
276-
context.HttpContext.SetCurrentUser(user);
280+
var securityStamp = principal.FindFirstValue(CustomClaimTypes.SecurityStamp);
277281

278-
ValidatePrincipalLogMessages.Success(this.logger, user.Id, user.Email, null);
279-
}
280-
else
281-
{
282-
ValidatePrincipalLogMessages.IncorrectSecurityStamp(
283-
this.logger, user.Id, user.Email, null);
282+
if (string.Equals(securityStamp, user.SecurityStamp, StringComparison.Ordinal))
283+
{
284+
context.HttpContext.SetCurrentUser(user);
284285

285-
context.RejectPrincipal();
286+
ValidatePrincipalLogMessages.Success(this.logger, user.Id, user.Email, null);
287+
}
288+
else
289+
{
290+
ValidatePrincipalLogMessages.IncorrectSecurityStamp(
291+
this.logger, user.Id, user.Email, null);
286292

287-
await this.SignOutCurrentUser(context.HttpContext);
288-
}
293+
context.RejectPrincipal();
294+
295+
await this.SignOutCurrentUser(context.HttpContext);
289296
}
290297
}
291298

292299
private static string RedactToken(string token) => $"{token[..6]}…";
293300

294301
private async Task<string> SetPassword(
295-
MySqlConnection connection, long userId, string newPassword)
302+
MySqlConnection connection, User user, string newPassword)
296303
{
297-
var hashedPassword = this.passwordHasher.HashPassword(null, newPassword);
304+
var hashedPassword = this.passwordHasher.HashPassword(user, newPassword);
298305

299306
var securityStamp = this.randomTokenGenerator.Generate(2);
300307

301308
await this.userDataProvider.UpdatePassword(
302-
connection, userId, hashedPassword, securityStamp);
309+
connection, user.Id, hashedPassword, securityStamp);
303310

304-
await this.passwordResetTokenDataProvider.DeleteTokensForUser(connection, userId);
311+
await this.passwordResetTokenDataProvider.DeleteTokensForUser(connection, user.Id);
305312

306313
return securityStamp;
307314
}
@@ -328,8 +335,8 @@ await this.passwordResetTokenDataProvider.DeleteExpiredTokens(
328335
return await this.passwordResetTokenDataProvider.GetUserIdForToken(connection, token);
329336
}
330337

331-
private bool VerifyPassword(User user, string password) =>
332-
this.passwordHasher.VerifyHashedPassword(user, user.HashedPassword, password) ==
338+
private bool VerifyPassword(User user, string hashedPassword, string password) =>
339+
this.passwordHasher.VerifyHashedPassword(user, hashedPassword, password) ==
333340
PasswordVerificationResult.Success;
334341

335342
private static class AuthenticateLogMessages
@@ -418,8 +425,8 @@ private static class SignInLogMessages
418425

419426
private static class SignOutLogMessages
420427
{
421-
public static readonly Action<ILogger, long, string, Exception?> SignedOut =
422-
LoggerMessage.Define<long, string>(
428+
public static readonly Action<ILogger, long, string?, Exception?> SignedOut =
429+
LoggerMessage.Define<long, string?>(
423430
LogLevel.Information, 213, "User {UserId} ({Email}) signed out");
424431
}
425432

src/Buttercup.Web/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@
7676
.AddBugsnag();
7777

7878
services
79-
.AddTransient<IPasswordHasher<User?>, PasswordHasher<User?>>()
79+
.AddTransient<IPasswordHasher<User>, PasswordHasher<User>>()
8080
.AddTransient<IAccessTokenEncoder, AccessTokenEncoder>()
8181
.AddTransient<IAccessTokenSerializer, AccessTokenSerializer>()
8282
.AddTransient<IAuthenticationMailer, AuthenticationMailer>()

0 commit comments

Comments
 (0)