Rehash passwords when needed

Fixes https://github.com/aspnet/Identity/issues/17
This commit is contained in:
Hao Kung 2015-01-12 12:47:08 -08:00
parent 41806a6067
commit 69ac9abcc5
2 changed files with 52 additions and 16 deletions

View File

@ -463,7 +463,7 @@ namespace Microsoft.AspNet.Identity
{
throw new ArgumentNullException("password");
}
var result = await UpdatePasswordInternal(passwordStore, user, password, cancellationToken);
var result = await UpdatePasswordHash(passwordStore, user, password, cancellationToken);
if (!result.Succeeded)
{
return result;
@ -565,7 +565,12 @@ namespace Microsoft.AspNet.Identity
{
return false;
}
return await VerifyPasswordAsync(passwordStore, user, password, cancellationToken);
var result = await VerifyPasswordAsync(passwordStore, user, password, cancellationToken);
if (result == PasswordVerificationResult.SuccessRehashNeeded) {
await UpdatePasswordHash(passwordStore, user, password, cancellationToken, validatePassword: false);
await UpdateAsync(user, cancellationToken);
}
return result != PasswordVerificationResult.Failed;
}
/// <summary>
@ -607,7 +612,7 @@ namespace Microsoft.AspNet.Identity
{
return IdentityResult.Failed(ErrorDescriber.UserAlreadyHasPassword());
}
var result = await UpdatePasswordInternal(passwordStore, user, password, cancellationToken);
var result = await UpdatePasswordHash(passwordStore, user, password, cancellationToken);
if (!result.Succeeded)
{
return result;
@ -632,9 +637,9 @@ namespace Microsoft.AspNet.Identity
{
throw new ArgumentNullException("user");
}
if (await VerifyPasswordAsync(passwordStore, user, currentPassword, cancellationToken))
if (await VerifyPasswordAsync(passwordStore, user, currentPassword, cancellationToken) != PasswordVerificationResult.Failed)
{
var result = await UpdatePasswordInternal(passwordStore, user, newPassword, cancellationToken);
var result = await UpdatePasswordHash(passwordStore, user, newPassword, cancellationToken);
if (!result.Succeeded)
{
return result;
@ -659,21 +664,24 @@ namespace Microsoft.AspNet.Identity
{
throw new ArgumentNullException("user");
}
await passwordStore.SetPasswordHashAsync(user, null, cancellationToken);
await UpdateSecurityStampInternal(user, cancellationToken);
await UpdatePasswordHash(passwordStore, user, null, cancellationToken, validatePassword: false);
return await UpdateAsync(user, cancellationToken);
}
internal async Task<IdentityResult> UpdatePasswordInternal(IUserPasswordStore<TUser> passwordStore,
TUser user, string newPassword, CancellationToken cancellationToken)
internal async Task<IdentityResult> UpdatePasswordHash(IUserPasswordStore<TUser> passwordStore,
TUser user, string newPassword, CancellationToken cancellationToken, bool validatePassword = true)
{
var validate = await ValidatePasswordInternal(user, newPassword, cancellationToken);
if (!validate.Succeeded)
if (validatePassword)
{
return validate;
var validate = await ValidatePasswordInternal(user, newPassword, cancellationToken);
if (!validate.Succeeded)
{
return validate;
}
}
var hash = newPassword != null ? PasswordHasher.HashPassword(user, newPassword) : null;
await
passwordStore.SetPasswordHashAsync(user, PasswordHasher.HashPassword(user, newPassword), cancellationToken);
passwordStore.SetPasswordHashAsync(user, hash, cancellationToken);
await UpdateSecurityStampInternal(user, cancellationToken);
return IdentityResult.Success;
}
@ -686,11 +694,11 @@ namespace Microsoft.AspNet.Identity
/// <param name="password"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
protected virtual async Task<bool> VerifyPasswordAsync(IUserPasswordStore<TUser> store, TUser user,
protected virtual async Task<PasswordVerificationResult> VerifyPasswordAsync(IUserPasswordStore<TUser> store, TUser user,
string password, CancellationToken cancellationToken = default(CancellationToken))
{
var hash = await store.GetPasswordHashAsync(user, cancellationToken);
return PasswordHasher.VerifyHashedPassword(user, hash, password) != PasswordVerificationResult.Failed;
return PasswordHasher.VerifyHashedPassword(user, hash, password);
}
// IUserSecurityStampStore methods
@ -776,7 +784,7 @@ namespace Microsoft.AspNet.Identity
return IdentityResult.Failed(ErrorDescriber.InvalidToken());
}
var passwordStore = GetPasswordStore();
var result = await UpdatePasswordInternal(passwordStore, user, newPassword, cancellationToken);
var result = await UpdatePasswordHash(passwordStore, user, newPassword, cancellationToken);
if (!result.Succeeded)
{
return result;

View File

@ -415,6 +415,34 @@ namespace Microsoft.AspNet.Identity.Test
store.VerifyAll();
}
[Fact]
public async Task CheckPasswordWillRehashPasswordWhenNeeded()
{
// Setup
var store = new Mock<IUserPasswordStore<TestUser>>();
var hasher = new Mock<IPasswordHasher<TestUser>>();
var user = new TestUser { UserName = "Foo" };
var pwd = "password";
var hashed = "hashed";
var rehashed = "rehashed";
store.Setup(s => s.GetPasswordHashAsync(user, CancellationToken.None))
.ReturnsAsync(hashed)
.Verifiable();
hasher.Setup(s => s.VerifyHashedPassword(user, hashed, pwd)).Returns(PasswordVerificationResult.SuccessRehashNeeded).Verifiable();
hasher.Setup(s => s.HashPassword(user, pwd)).Returns(rehashed).Verifiable();
var userManager = MockHelpers.TestUserManager(store.Object);
userManager.PasswordHasher = hasher.Object;
// Act
var result = await userManager.CheckPasswordAsync(user, pwd);
// Assert
Assert.True(result);
store.VerifyAll();
hasher.VerifyAll();
}
[Fact]
public async Task RemoveClaimsCallsStore()
{