Rehash passwords when needed
Fixes https://github.com/aspnet/Identity/issues/17
This commit is contained in:
parent
41806a6067
commit
69ac9abcc5
|
|
@ -463,7 +463,7 @@ namespace Microsoft.AspNet.Identity
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException("password");
|
throw new ArgumentNullException("password");
|
||||||
}
|
}
|
||||||
var result = await UpdatePasswordInternal(passwordStore, user, password, cancellationToken);
|
var result = await UpdatePasswordHash(passwordStore, user, password, cancellationToken);
|
||||||
if (!result.Succeeded)
|
if (!result.Succeeded)
|
||||||
{
|
{
|
||||||
return result;
|
return result;
|
||||||
|
|
@ -565,7 +565,12 @@ namespace Microsoft.AspNet.Identity
|
||||||
{
|
{
|
||||||
return false;
|
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>
|
/// <summary>
|
||||||
|
|
@ -607,7 +612,7 @@ namespace Microsoft.AspNet.Identity
|
||||||
{
|
{
|
||||||
return IdentityResult.Failed(ErrorDescriber.UserAlreadyHasPassword());
|
return IdentityResult.Failed(ErrorDescriber.UserAlreadyHasPassword());
|
||||||
}
|
}
|
||||||
var result = await UpdatePasswordInternal(passwordStore, user, password, cancellationToken);
|
var result = await UpdatePasswordHash(passwordStore, user, password, cancellationToken);
|
||||||
if (!result.Succeeded)
|
if (!result.Succeeded)
|
||||||
{
|
{
|
||||||
return result;
|
return result;
|
||||||
|
|
@ -632,9 +637,9 @@ namespace Microsoft.AspNet.Identity
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException("user");
|
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)
|
if (!result.Succeeded)
|
||||||
{
|
{
|
||||||
return result;
|
return result;
|
||||||
|
|
@ -659,21 +664,24 @@ namespace Microsoft.AspNet.Identity
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException("user");
|
throw new ArgumentNullException("user");
|
||||||
}
|
}
|
||||||
await passwordStore.SetPasswordHashAsync(user, null, cancellationToken);
|
await UpdatePasswordHash(passwordStore, user, null, cancellationToken, validatePassword: false);
|
||||||
await UpdateSecurityStampInternal(user, cancellationToken);
|
|
||||||
return await UpdateAsync(user, cancellationToken);
|
return await UpdateAsync(user, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task<IdentityResult> UpdatePasswordInternal(IUserPasswordStore<TUser> passwordStore,
|
internal async Task<IdentityResult> UpdatePasswordHash(IUserPasswordStore<TUser> passwordStore,
|
||||||
TUser user, string newPassword, CancellationToken cancellationToken)
|
TUser user, string newPassword, CancellationToken cancellationToken, bool validatePassword = true)
|
||||||
{
|
{
|
||||||
var validate = await ValidatePasswordInternal(user, newPassword, cancellationToken);
|
if (validatePassword)
|
||||||
if (!validate.Succeeded)
|
|
||||||
{
|
{
|
||||||
return validate;
|
var validate = await ValidatePasswordInternal(user, newPassword, cancellationToken);
|
||||||
|
if (!validate.Succeeded)
|
||||||
|
{
|
||||||
|
return validate;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
var hash = newPassword != null ? PasswordHasher.HashPassword(user, newPassword) : null;
|
||||||
await
|
await
|
||||||
passwordStore.SetPasswordHashAsync(user, PasswordHasher.HashPassword(user, newPassword), cancellationToken);
|
passwordStore.SetPasswordHashAsync(user, hash, cancellationToken);
|
||||||
await UpdateSecurityStampInternal(user, cancellationToken);
|
await UpdateSecurityStampInternal(user, cancellationToken);
|
||||||
return IdentityResult.Success;
|
return IdentityResult.Success;
|
||||||
}
|
}
|
||||||
|
|
@ -686,11 +694,11 @@ namespace Microsoft.AspNet.Identity
|
||||||
/// <param name="password"></param>
|
/// <param name="password"></param>
|
||||||
/// <param name="cancellationToken"></param>
|
/// <param name="cancellationToken"></param>
|
||||||
/// <returns></returns>
|
/// <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))
|
string password, CancellationToken cancellationToken = default(CancellationToken))
|
||||||
{
|
{
|
||||||
var hash = await store.GetPasswordHashAsync(user, cancellationToken);
|
var hash = await store.GetPasswordHashAsync(user, cancellationToken);
|
||||||
return PasswordHasher.VerifyHashedPassword(user, hash, password) != PasswordVerificationResult.Failed;
|
return PasswordHasher.VerifyHashedPassword(user, hash, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
// IUserSecurityStampStore methods
|
// IUserSecurityStampStore methods
|
||||||
|
|
@ -776,7 +784,7 @@ namespace Microsoft.AspNet.Identity
|
||||||
return IdentityResult.Failed(ErrorDescriber.InvalidToken());
|
return IdentityResult.Failed(ErrorDescriber.InvalidToken());
|
||||||
}
|
}
|
||||||
var passwordStore = GetPasswordStore();
|
var passwordStore = GetPasswordStore();
|
||||||
var result = await UpdatePasswordInternal(passwordStore, user, newPassword, cancellationToken);
|
var result = await UpdatePasswordHash(passwordStore, user, newPassword, cancellationToken);
|
||||||
if (!result.Succeeded)
|
if (!result.Succeeded)
|
||||||
{
|
{
|
||||||
return result;
|
return result;
|
||||||
|
|
|
||||||
|
|
@ -415,6 +415,34 @@ namespace Microsoft.AspNet.Identity.Test
|
||||||
store.VerifyAll();
|
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]
|
[Fact]
|
||||||
public async Task RemoveClaimsCallsStore()
|
public async Task RemoveClaimsCallsStore()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue