// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Identity
{
///
/// Provides the APIs for managing user in a persistence store.
///
/// The type encapsulating a user.
public class UserManager : IDisposable where TUser : class
{
///
/// The data protection purpose used for the reset password related methods.
///
protected const string ResetPasswordTokenPurpose = "ResetPassword";
///
/// The data protection purpose used for the change phone number methods.
///
protected const string ChangePhoneNumberTokenPurpose = "ChangePhoneNumber";
///
/// The data protection purpose used for the email confirmation related methods.
///
protected const string ConfirmEmailTokenPurpose = "EmailConfirmation";
private readonly Dictionary> _tokenProviders =
new Dictionary>();
private TimeSpan _defaultLockout = TimeSpan.Zero;
private bool _disposed;
private static readonly RandomNumberGenerator _rng = RandomNumberGenerator.Create();
///
/// The cancellation token used to cancel operations.
///
protected virtual CancellationToken CancellationToken => CancellationToken.None;
///
/// Constructs a new instance of .
///
/// The persistence store the manager will operate over.
/// The accessor used to access the .
/// The password hashing implementation to use when saving passwords.
/// A collection of to validate users against.
/// A collection of to validate passwords against.
/// The to use when generating index keys for users.
/// The used to provider error messages.
/// The used to resolve services.
/// The logger used to log messages, warnings and errors.
public UserManager(IUserStore store,
IOptions optionsAccessor,
IPasswordHasher passwordHasher,
IEnumerable> userValidators,
IEnumerable> passwordValidators,
ILookupNormalizer keyNormalizer,
IdentityErrorDescriber errors,
IServiceProvider services,
ILogger> logger)
{
if (store == null)
{
throw new ArgumentNullException(nameof(store));
}
Store = store;
Options = optionsAccessor?.Value ?? new IdentityOptions();
PasswordHasher = passwordHasher;
KeyNormalizer = keyNormalizer;
ErrorDescriber = errors;
Logger = logger;
if (userValidators != null)
{
foreach (var v in userValidators)
{
UserValidators.Add(v);
}
}
if (passwordValidators != null)
{
foreach (var v in passwordValidators)
{
PasswordValidators.Add(v);
}
}
if (services != null)
{
foreach (var providerName in Options.Tokens.ProviderMap.Keys)
{
var description = Options.Tokens.ProviderMap[providerName];
var provider = (description.ProviderInstance ?? services.GetRequiredService(description.ProviderType))
as IUserTwoFactorTokenProvider;
if (provider != null)
{
RegisterTokenProvider(providerName, provider);
}
}
}
}
///
/// Gets or sets the persistence store the manager operates over.
///
/// The persistence store the manager operates over.
protected internal IUserStore Store { get; set; }
///
/// The used to log messages from the manager.
///
///
/// The used to log messages from the manager.
///
public virtual ILogger Logger { get; set; }
///
/// The used to hash passwords.
///
public IPasswordHasher PasswordHasher { get; set; }
///
/// The used to validate users.
///
public IList> UserValidators { get; } = new List>();
///
/// The used to validate passwords.
///
public IList> PasswordValidators { get; } = new List>();
///
/// The used to normalize things like user and role names.
///
public ILookupNormalizer KeyNormalizer { get; set; }
///
/// The used to generate error messages.
///
public IdentityErrorDescriber ErrorDescriber { get; set; }
///
/// The used to configure Identity.
///
public IdentityOptions Options { get; set; }
///
/// Gets a flag indicating whether the backing user store supports authentication tokens.
///
///
/// true if the backing user store supports authentication tokens, otherwise false.
///
public virtual bool SupportsUserAuthenticationTokens
{
get
{
ThrowIfDisposed();
return Store is IUserAuthenticationTokenStore;
}
}
///
/// Gets a flag indicating whether the backing user store supports a user authenticator.
///
///
/// true if the backing user store supports a user authenticatior, otherwise false.
///
public virtual bool SupportsUserAuthenticatorKey
{
get
{
ThrowIfDisposed();
return Store is IUserAuthenticatorKeyStore;
}
}
///
/// Gets a flag indicating whether the backing user store supports recovery codes.
///
///
/// true if the backing user store supports a user authenticatior, otherwise false.
///
public virtual bool SupportsUserTwoFactorRecoveryCodes
{
get
{
ThrowIfDisposed();
return Store is IUserTwoFactorRecoveryCodeStore;
}
}
///
/// Gets a flag indicating whether the backing user store supports two factor authentication.
///
///
/// true if the backing user store supports user two factor authentication, otherwise false.
///
public virtual bool SupportsUserTwoFactor
{
get
{
ThrowIfDisposed();
return Store is IUserTwoFactorStore;
}
}
///
/// Gets a flag indicating whether the backing user store supports user passwords.
///
///
/// true if the backing user store supports user passwords, otherwise false.
///
public virtual bool SupportsUserPassword
{
get
{
ThrowIfDisposed();
return Store is IUserPasswordStore;
}
}
///
/// Gets a flag indicating whether the backing user store supports security stamps.
///
///
/// true if the backing user store supports user security stamps, otherwise false.
///
public virtual bool SupportsUserSecurityStamp
{
get
{
ThrowIfDisposed();
return Store is IUserSecurityStampStore;
}
}
///
/// Gets a flag indicating whether the backing user store supports user roles.
///
///
/// true if the backing user store supports user roles, otherwise false.
///
public virtual bool SupportsUserRole
{
get
{
ThrowIfDisposed();
return Store is IUserRoleStore;
}
}
///
/// Gets a flag indicating whether the backing user store supports external logins.
///
///
/// true if the backing user store supports external logins, otherwise false.
///
public virtual bool SupportsUserLogin
{
get
{
ThrowIfDisposed();
return Store is IUserLoginStore;
}
}
///
/// Gets a flag indicating whether the backing user store supports user emails.
///
///
/// true if the backing user store supports user emails, otherwise false.
///
public virtual bool SupportsUserEmail
{
get
{
ThrowIfDisposed();
return Store is IUserEmailStore;
}
}
///
/// Gets a flag indicating whether the backing user store supports user telephone numbers.
///
///
/// true if the backing user store supports user telephone numbers, otherwise false.
///
public virtual bool SupportsUserPhoneNumber
{
get
{
ThrowIfDisposed();
return Store is IUserPhoneNumberStore;
}
}
///
/// Gets a flag indicating whether the backing user store supports user claims.
///
///
/// true if the backing user store supports user claims, otherwise false.
///
public virtual bool SupportsUserClaim
{
get
{
ThrowIfDisposed();
return Store is IUserClaimStore;
}
}
///
/// Gets a flag indicating whether the backing user store supports user lock-outs.
///
///
/// true if the backing user store supports user lock-outs, otherwise false.
///
public virtual bool SupportsUserLockout
{
get
{
ThrowIfDisposed();
return Store is IUserLockoutStore;
}
}
///
/// Gets a flag indicating whether the backing user store supports returning
/// collections of information.
///
///
/// true if the backing user store supports returning collections of
/// information, otherwise false.
///
public virtual bool SupportsQueryableUsers
{
get
{
ThrowIfDisposed();
return Store is IQueryableUserStore;
}
}
///
/// Returns an IQueryable of users if the store is an IQueryableUserStore
///
public virtual IQueryable Users
{
get
{
var queryableStore = Store as IQueryableUserStore;
if (queryableStore == null)
{
throw new NotSupportedException(Resources.StoreNotIQueryableUserStore);
}
return queryableStore.Users;
}
}
///
/// Releases all resources used by the user manager.
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Returns the Name claim value if present otherwise returns null.
///
/// The instance.
/// The Name claim value, or null if the claim is not present.
/// The Name claim is identified by .
public virtual string GetUserName(ClaimsPrincipal principal)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
return principal.FindFirstValue(Options.ClaimsIdentity.UserNameClaimType);
}
///
/// Returns the User ID claim value if present otherwise returns null.
///
/// The instance.
/// The User ID claim value, or null if the claim is not present.
/// The User ID claim is identified by .
public virtual string GetUserId(ClaimsPrincipal principal)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
return principal.FindFirstValue(Options.ClaimsIdentity.UserIdClaimType);
}
///
/// Returns the user corresponding to the IdentityOptions.ClaimsIdentity.UserIdClaimType claim in
/// the principal or null.
///
/// The principal which contains the user id claim.
/// The user corresponding to the IdentityOptions.ClaimsIdentity.UserIdClaimType claim in
/// the principal or null
public virtual Task GetUserAsync(ClaimsPrincipal principal)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
var id = GetUserId(principal);
return id == null ? Task.FromResult(null) : FindByIdAsync(id);
}
///
/// Generates a value suitable for use in concurrency tracking.
///
/// The user to generate the stamp for.
///
/// The that represents the asynchronous operation, containing the security
/// stamp for the specified .
///
public virtual Task GenerateConcurrencyStampAsync(TUser user)
{
return Task.FromResult(Guid.NewGuid().ToString());
}
///
/// Creates the specified in the backing store with no password,
/// as an asynchronous operation.
///
/// The user to create.
///
/// The that represents the asynchronous operation, containing the
/// of the operation.
///
public virtual async Task CreateAsync(TUser user)
{
ThrowIfDisposed();
await UpdateSecurityStampInternal(user);
var result = await ValidateUserInternal(user);
if (!result.Succeeded)
{
return result;
}
if (Options.Lockout.AllowedForNewUsers && SupportsUserLockout)
{
await GetUserLockoutStore().SetLockoutEnabledAsync(user, true, CancellationToken);
}
await UpdateNormalizedUserNameAsync(user);
await UpdateNormalizedEmailAsync(user);
return await Store.CreateAsync(user, CancellationToken);
}
///
/// Updates the specified in the backing store.
///
/// The user to update.
///
/// The that represents the asynchronous operation, containing the
/// of the operation.
///
public virtual Task UpdateAsync(TUser user)
{
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
return UpdateUserAsync(user);
}
///
/// Deletes the specified from the backing store.
///
/// The user to delete.
///
/// The that represents the asynchronous operation, containing the
/// of the operation.
///
public virtual Task DeleteAsync(TUser user)
{
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
return Store.DeleteAsync(user, CancellationToken);
}
///
/// Finds and returns a user, if any, who has the specified .
///
/// The user ID to search for.
///
/// The that represents the asynchronous operation, containing the user matching the specified if it exists.
///
public virtual Task FindByIdAsync(string userId)
{
ThrowIfDisposed();
return Store.FindByIdAsync(userId, CancellationToken);
}
///
/// Finds and returns a user, if any, who has the specified user name.
///
/// The user name to search for.
///
/// The that represents the asynchronous operation, containing the user matching the specified if it exists.
///
public virtual Task FindByNameAsync(string userName)
{
ThrowIfDisposed();
if (userName == null)
{
throw new ArgumentNullException(nameof(userName));
}
userName = NormalizeKey(userName);
return Store.FindByNameAsync(userName, CancellationToken);
}
///
/// Creates the specified in the backing store with given password,
/// as an asynchronous operation.
///
/// The user to create.
/// The password for the user to hash and store.
///
/// The that represents the asynchronous operation, containing the
/// of the operation.
///
public virtual async Task CreateAsync(TUser user, string password)
{
ThrowIfDisposed();
var passwordStore = GetPasswordStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (password == null)
{
throw new ArgumentNullException(nameof(password));
}
var result = await UpdatePasswordHash(passwordStore, user, password);
if (!result.Succeeded)
{
return result;
}
return await CreateAsync(user);
}
///
/// Normalize a key (user name, email) for consistent comparisons.
///
/// The key to normalize.
/// A normalized value representing the specified .
public virtual string NormalizeKey(string key)
{
return (KeyNormalizer == null) ? key : KeyNormalizer.Normalize(key);
}
///
/// Updates the normalized user name for the specified .
///
/// The user whose user name should be normalized and updated.
/// The that represents the asynchronous operation.
public virtual async Task UpdateNormalizedUserNameAsync(TUser user)
{
var normalizedName = NormalizeKey(await GetUserNameAsync(user));
await Store.SetNormalizedUserNameAsync(user, normalizedName, CancellationToken);
}
///
/// Gets the user name for the specified .
///
/// The user whose name should be retrieved.
/// The that represents the asynchronous operation, containing the name for the specified .
public virtual async Task GetUserNameAsync(TUser user)
{
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
return await Store.GetUserNameAsync(user, CancellationToken);
}
///
/// Sets the given for the specified .
///
/// The user whose name should be set.
/// The user name to set.
/// The that represents the asynchronous operation.
public virtual async Task SetUserNameAsync(TUser user, string userName)
{
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
await Store.SetUserNameAsync(user, userName, CancellationToken);
await UpdateSecurityStampInternal(user);
return await UpdateUserAsync(user);
}
///
/// Gets the user identifier for the specified .
///
/// The user whose identifier should be retrieved.
/// The that represents the asynchronous operation, containing the identifier for the specified .
public virtual async Task GetUserIdAsync(TUser user)
{
ThrowIfDisposed();
return await Store.GetUserIdAsync(user, CancellationToken);
}
///
/// Returns a flag indicating whether the given is valid for the
/// specified .
///
/// The user whose password should be validated.
/// The password to validate
/// The that represents the asynchronous operation, containing true if
/// the specified matches the one store for the ,
/// otherwise false.
public virtual async Task CheckPasswordAsync(TUser user, string password)
{
ThrowIfDisposed();
var passwordStore = GetPasswordStore();
if (user == null)
{
return false;
}
var result = await VerifyPasswordAsync(passwordStore, user, password);
if (result == PasswordVerificationResult.SuccessRehashNeeded)
{
await UpdatePasswordHash(passwordStore, user, password, validatePassword: false);
await UpdateUserAsync(user);
}
var success = result != PasswordVerificationResult.Failed;
if (!success)
{
Logger.LogWarning(0, "Invalid password for user {userId}.", await GetUserIdAsync(user));
}
return success;
}
///
/// Gets a flag indicating whether the specified has a password.
///
/// The user to return a flag for, indicating whether they have a password or not.
///
/// The that represents the asynchronous operation, returning true if the specified has a password
/// otherwise false.
///
public virtual Task HasPasswordAsync(TUser user)
{
ThrowIfDisposed();
var passwordStore = GetPasswordStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
return passwordStore.HasPasswordAsync(user, CancellationToken);
}
///
/// Adds the to the specified only if the user
/// does not already have a password.
///
/// The user whose password should be set.
/// The password to set.
///
/// The that represents the asynchronous operation, containing the
/// of the operation.
///
public virtual async Task AddPasswordAsync(TUser user, string password)
{
ThrowIfDisposed();
var passwordStore = GetPasswordStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var hash = await passwordStore.GetPasswordHashAsync(user, CancellationToken);
if (hash != null)
{
Logger.LogWarning(1, "User {userId} already has a password.", await GetUserIdAsync(user));
return IdentityResult.Failed(ErrorDescriber.UserAlreadyHasPassword());
}
var result = await UpdatePasswordHash(passwordStore, user, password);
if (!result.Succeeded)
{
return result;
}
return await UpdateUserAsync(user);
}
///
/// Changes a user's password after confirming the specified is correct,
/// as an asynchronous operation.
///
/// The user whose password should be set.
/// The current password to validate before changing.
/// The new password to set for the specified .
///
/// The that represents the asynchronous operation, containing the
/// of the operation.
///
public virtual async Task ChangePasswordAsync(TUser user, string currentPassword, string newPassword)
{
ThrowIfDisposed();
var passwordStore = GetPasswordStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (await VerifyPasswordAsync(passwordStore, user, currentPassword) != PasswordVerificationResult.Failed)
{
var result = await UpdatePasswordHash(passwordStore, user, newPassword);
if (!result.Succeeded)
{
return result;
}
return await UpdateUserAsync(user);
}
Logger.LogWarning(2, "Change password failed for user {userId}.", await GetUserIdAsync(user));
return IdentityResult.Failed(ErrorDescriber.PasswordMismatch());
}
///
/// Removes a user's password.
///
/// The user whose password should be removed.
///
/// The that represents the asynchronous operation, containing the
/// of the operation.
///
public virtual async Task RemovePasswordAsync(TUser user)
{
ThrowIfDisposed();
var passwordStore = GetPasswordStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
await UpdatePasswordHash(passwordStore, user, null, validatePassword: false);
return await UpdateUserAsync(user);
}
///
/// Returns a indicating the result of a password hash comparison.
///
/// The store containing a user's password.
/// The user whose password should be verified.
/// The password to verify.
///
/// The that represents the asynchronous operation, containing the
/// of the operation.
///
protected virtual async Task VerifyPasswordAsync(IUserPasswordStore store, TUser user, string password)
{
var hash = await store.GetPasswordHashAsync(user, CancellationToken);
if (hash == null)
{
return PasswordVerificationResult.Failed;
}
return PasswordHasher.VerifyHashedPassword(user, hash, password);
}
///
/// Get the security stamp for the specified .
///
/// The user whose security stamp should be set.
/// The that represents the asynchronous operation, containing the security stamp for the specified .
public virtual async Task GetSecurityStampAsync(TUser user)
{
ThrowIfDisposed();
var securityStore = GetSecurityStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
return await securityStore.GetSecurityStampAsync(user, CancellationToken);
}
///
/// Regenerates the security stamp for the specified .
///
/// The user whose security stamp should be regenerated.
///
/// The that represents the asynchronous operation, containing the
/// of the operation.
///
///
/// Regenerating a security stamp will sign out any saved login for the user.
///
public virtual async Task UpdateSecurityStampAsync(TUser user)
{
ThrowIfDisposed();
GetSecurityStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
await UpdateSecurityStampInternal(user);
return await UpdateUserAsync(user);
}
///
/// Generates a password reset token for the specified , using
/// the configured password reset token provider.
///
/// The user to generate a password reset token for.
/// The that represents the asynchronous operation,
/// containing a password reset token for the specified .
public virtual Task GeneratePasswordResetTokenAsync(TUser user)
{
ThrowIfDisposed();
return GenerateUserTokenAsync(user, Options.Tokens.PasswordResetTokenProvider, ResetPasswordTokenPurpose);
}
///
/// Resets the 's password to the specified after
/// validating the given password reset .
///
/// The user whose password should be reset.
/// The password reset token to verify.
/// The new password to set if reset token verification fails.
///
/// The that represents the asynchronous operation, containing the
/// of the operation.
///
public virtual async Task ResetPasswordAsync(TUser user, string token, string newPassword)
{
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
// Make sure the token is valid and the stamp matches
if (!await VerifyUserTokenAsync(user, Options.Tokens.PasswordResetTokenProvider, ResetPasswordTokenPurpose, token))
{
return IdentityResult.Failed(ErrorDescriber.InvalidToken());
}
var passwordStore = GetPasswordStore();
var result = await UpdatePasswordHash(passwordStore, user, newPassword);
if (!result.Succeeded)
{
return result;
}
return await UpdateUserAsync(user);
}
///
/// Retrieves the user associated with the specified external login provider and login provider key.
///
/// The login provider who provided the .
/// The key provided by the to identify a user.
///
/// The for the asynchronous operation, containing the user, if any which matched the specified login provider and key.
///
public virtual Task FindByLoginAsync(string loginProvider, string providerKey)
{
ThrowIfDisposed();
var loginStore = GetLoginStore();
if (loginProvider == null)
{
throw new ArgumentNullException(nameof(loginProvider));
}
if (providerKey == null)
{
throw new ArgumentNullException(nameof(providerKey));
}
return loginStore.FindByLoginAsync(loginProvider, providerKey, CancellationToken);
}
///
/// Attempts to remove the provided external login information from the specified .
/// and returns a flag indicating whether the removal succeed or not.
///
/// The user to remove the login information from.
/// The login provide whose information should be removed.
/// The key given by the external login provider for the specified user.
///
/// The that represents the asynchronous operation, containing the
/// of the operation.
///
public virtual async Task RemoveLoginAsync(TUser user, string loginProvider, string providerKey)
{
ThrowIfDisposed();
var loginStore = GetLoginStore();
if (loginProvider == null)
{
throw new ArgumentNullException(nameof(loginProvider));
}
if (providerKey == null)
{
throw new ArgumentNullException(nameof(providerKey));
}
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
await loginStore.RemoveLoginAsync(user, loginProvider, providerKey, CancellationToken);
await UpdateSecurityStampInternal(user);
return await UpdateUserAsync(user);
}
///
/// Adds an external to the specified .
///
/// The user to add the login to.
/// The external to add to the specified .
///
/// The that represents the asynchronous operation, containing the
/// of the operation.
///
public virtual async Task AddLoginAsync(TUser user, UserLoginInfo login)
{
ThrowIfDisposed();
var loginStore = GetLoginStore();
if (login == null)
{
throw new ArgumentNullException(nameof(login));
}
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var existingUser = await FindByLoginAsync(login.LoginProvider, login.ProviderKey);
if (existingUser != null)
{
Logger.LogWarning(4, "AddLogin for user {userId} failed because it was already assocated with another user.", await GetUserIdAsync(user));
return IdentityResult.Failed(ErrorDescriber.LoginAlreadyAssociated());
}
await loginStore.AddLoginAsync(user, login, CancellationToken);
return await UpdateUserAsync(user);
}
///
/// Retrieves the associated logins for the specified .
///
/// The user whose associated logins to retrieve.
///
/// The for the asynchronous operation, containing a list of for the specified , if any.
///
public virtual async Task> GetLoginsAsync(TUser user)
{
ThrowIfDisposed();
var loginStore = GetLoginStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
return await loginStore.GetLoginsAsync(user, CancellationToken);
}
///
/// Adds the specified to the .
///
/// The user to add the claim to.
/// The claim to add.
///
/// The that represents the asynchronous operation, containing the
/// of the operation.
///
public virtual Task AddClaimAsync(TUser user, Claim claim)
{
ThrowIfDisposed();
var claimStore = GetClaimStore();
if (claim == null)
{
throw new ArgumentNullException(nameof(claim));
}
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
return AddClaimsAsync(user, new Claim[] { claim });
}
///
/// Adds the specified to the .
///
/// The user to add the claim to.
/// The claims to add.
///
/// The that represents the asynchronous operation, containing the
/// of the operation.
///
public virtual async Task AddClaimsAsync(TUser user, IEnumerable claims)
{
ThrowIfDisposed();
var claimStore = GetClaimStore();
if (claims == null)
{
throw new ArgumentNullException(nameof(claims));
}
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
await claimStore.AddClaimsAsync(user, claims, CancellationToken);
return await UpdateUserAsync(user);
}
///
/// Replaces the given on the specified with the
///
/// The user to replace the claim on.
/// The claim to replace.
/// The new claim to replace the existing with.
///
/// The that represents the asynchronous operation, containing the
/// of the operation.
///
public virtual async Task ReplaceClaimAsync(TUser user, Claim claim, Claim newClaim)
{
ThrowIfDisposed();
var claimStore = GetClaimStore();
if (claim == null)
{
throw new ArgumentNullException(nameof(claim));
}
if (newClaim == null)
{
throw new ArgumentNullException(nameof(newClaim));
}
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
await claimStore.ReplaceClaimAsync(user, claim, newClaim, CancellationToken);
return await UpdateUserAsync(user);
}
///
/// Removes the specified from the given .
///
/// The user to remove the specified from.
/// The to remove.
///
/// The that represents the asynchronous operation, containing the
/// of the operation.
///
public virtual Task RemoveClaimAsync(TUser user, Claim claim)
{
ThrowIfDisposed();
var claimStore = GetClaimStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (claim == null)
{
throw new ArgumentNullException(nameof(claim));
}
return RemoveClaimsAsync(user, new Claim[] { claim });
}
///
/// Removes the specified from the given .
///
/// The user to remove the specified from.
/// A collection of s to remove.
///
/// The that represents the asynchronous operation, containing the
/// of the operation.
///
public virtual async Task RemoveClaimsAsync(TUser user, IEnumerable claims)
{
ThrowIfDisposed();
var claimStore = GetClaimStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (claims == null)
{
throw new ArgumentNullException(nameof(claims));
}
await claimStore.RemoveClaimsAsync(user, claims, CancellationToken);
return await UpdateUserAsync(user);
}
///
/// Gets a list of s to be belonging to the specified as an asynchronous operation.
///
/// The user whose claims to retrieve.
///
/// A that represents the result of the asynchronous query, a list of s.
///
public virtual async Task> GetClaimsAsync(TUser user)
{
ThrowIfDisposed();
var claimStore = GetClaimStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
return await claimStore.GetClaimsAsync(user, CancellationToken);
}
///
/// Add the specified to the named role.
///
/// The user to add to the named role.
/// The name of the role to add the user to.
///
/// The that represents the asynchronous operation, containing the
/// of the operation.
///
public virtual async Task AddToRoleAsync(TUser user, string role)
{
ThrowIfDisposed();
var userRoleStore = GetUserRoleStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var normalizedRole = NormalizeKey(role);
if (await userRoleStore.IsInRoleAsync(user, normalizedRole, CancellationToken))
{
return await UserAlreadyInRoleError(user, role);
}
await userRoleStore.AddToRoleAsync(user, normalizedRole, CancellationToken);
return await UpdateUserAsync(user);
}
///
/// Add the specified to the named roles.
///
/// The user to add to the named roles.
/// The name of the roles to add the user to.
///
/// The that represents the asynchronous operation, containing the
/// of the operation.
///
public virtual async Task AddToRolesAsync(TUser user, IEnumerable roles)
{
ThrowIfDisposed();
var userRoleStore = GetUserRoleStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (roles == null)
{
throw new ArgumentNullException(nameof(roles));
}
foreach (var role in roles.Distinct())
{
var normalizedRole = NormalizeKey(role);
if (await userRoleStore.IsInRoleAsync(user, normalizedRole, CancellationToken))
{
return await UserAlreadyInRoleError(user, role);
}
await userRoleStore.AddToRoleAsync(user, normalizedRole, CancellationToken);
}
return await UpdateUserAsync(user);
}
///
/// Removes the specified from the named role.
///
/// The user to remove from the named role.
/// The name of the role to remove the user from.
///
/// The that represents the asynchronous operation, containing the
/// of the operation.
///
public virtual async Task RemoveFromRoleAsync(TUser user, string role)
{
ThrowIfDisposed();
var userRoleStore = GetUserRoleStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var normalizedRole = NormalizeKey(role);
if (!await userRoleStore.IsInRoleAsync(user, normalizedRole, CancellationToken))
{
return await UserNotInRoleError(user, role);
}
await userRoleStore.RemoveFromRoleAsync(user, normalizedRole, CancellationToken);
return await UpdateUserAsync(user);
}
private async Task UserAlreadyInRoleError(TUser user, string role)
{
Logger.LogWarning(5, "User {userId} is already in role {role}.", await GetUserIdAsync(user), role);
return IdentityResult.Failed(ErrorDescriber.UserAlreadyInRole(role));
}
private async Task UserNotInRoleError(TUser user, string role)
{
Logger.LogWarning(6, "User {userId} is not in role {role}.", await GetUserIdAsync(user), role);
return IdentityResult.Failed(ErrorDescriber.UserNotInRole(role));
}
///
/// Removes the specified from the named roles.
///
/// The user to remove from the named roles.
/// The name of the roles to remove the user from.
///
/// The that represents the asynchronous operation, containing the
/// of the operation.
///
public virtual async Task RemoveFromRolesAsync(TUser user, IEnumerable roles)
{
ThrowIfDisposed();
var userRoleStore = GetUserRoleStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (roles == null)
{
throw new ArgumentNullException(nameof(roles));
}
foreach (var role in roles)
{
var normalizedRole = NormalizeKey(role);
if (!await userRoleStore.IsInRoleAsync(user, normalizedRole, CancellationToken))
{
return await UserNotInRoleError(user, role);
}
await userRoleStore.RemoveFromRoleAsync(user, normalizedRole, CancellationToken);
}
return await UpdateUserAsync(user);
}
///
/// Gets a list of role names the specified belongs to.
///
/// The user whose role names to retrieve.
/// The that represents the asynchronous operation, containing a list of role names.
public virtual async Task> GetRolesAsync(TUser user)
{
ThrowIfDisposed();
var userRoleStore = GetUserRoleStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
return await userRoleStore.GetRolesAsync(user, CancellationToken);
}
///
/// Returns a flag indicating whether the specified is a member of the give named role.
///
/// The user whose role membership should be checked.
/// The name of the role to be checked.
///
/// The that represents the asynchronous operation, containing a flag indicating whether the specified is
/// a member of the named role.
///
public virtual async Task IsInRoleAsync(TUser user, string role)
{
ThrowIfDisposed();
var userRoleStore = GetUserRoleStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
return await userRoleStore.IsInRoleAsync(user, NormalizeKey(role), CancellationToken);
}
///
/// Gets the email address for the specified .
///
/// The user whose email should be returned.
/// The task object containing the results of the asynchronous operation, the email address for the specified .
public virtual async Task GetEmailAsync(TUser user)
{
ThrowIfDisposed();
var store = GetEmailStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
return await store.GetEmailAsync(user, CancellationToken);
}
///
/// Sets the address for a .
///
/// The user whose email should be set.
/// The email to set.
///
/// The that represents the asynchronous operation, containing the
/// of the operation.
///
public virtual async Task SetEmailAsync(TUser user, string email)
{
ThrowIfDisposed();
var store = GetEmailStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
await store.SetEmailAsync(user, email, CancellationToken);
await store.SetEmailConfirmedAsync(user, false, CancellationToken);
await UpdateSecurityStampInternal(user);
return await UpdateUserAsync(user);
}
///
/// Gets the user, if any, associated with the normalized value of the specified email address.
///
/// The email address to return the user for.
///
/// The task object containing the results of the asynchronous lookup operation, the user, if any, associated with a normalized value of the specified email address.
///
public virtual Task FindByEmailAsync(string email)
{
ThrowIfDisposed();
var store = GetEmailStore();
if (email == null)
{
throw new ArgumentNullException(nameof(email));
}
return store.FindByEmailAsync(NormalizeKey(email), CancellationToken);
}
///
/// Updates the normalized email for the specified .
///
/// The user whose email address should be normalized and updated.
/// The task object representing the asynchronous operation.
public virtual async Task UpdateNormalizedEmailAsync(TUser user)
{
var store = GetEmailStore(throwOnFail: false);
if (store != null)
{
var email = await GetEmailAsync(user);
await store.SetNormalizedEmailAsync(user, NormalizeKey(email), CancellationToken);
}
}
///
/// Generates an email confirmation token for the specified user.
///
/// The user to generate an email confirmation token for.
///
/// The that represents the asynchronous operation, an email confirmation token.
///
public virtual Task GenerateEmailConfirmationTokenAsync(TUser user)
{
ThrowIfDisposed();
return GenerateUserTokenAsync(user, Options.Tokens.EmailConfirmationTokenProvider, ConfirmEmailTokenPurpose);
}
///
/// Validates that an email confirmation token matches the specified .
///
/// The user to validate the token against.
/// The email confirmation token to validate.
///
/// The that represents the asynchronous operation, containing the
/// of the operation.
///
public virtual async Task ConfirmEmailAsync(TUser user, string token)
{
ThrowIfDisposed();
var store = GetEmailStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (!await VerifyUserTokenAsync(user, Options.Tokens.EmailConfirmationTokenProvider, ConfirmEmailTokenPurpose, token))
{
return IdentityResult.Failed(ErrorDescriber.InvalidToken());
}
await store.SetEmailConfirmedAsync(user, true, CancellationToken);
return await UpdateUserAsync(user);
}
///
/// Gets a flag indicating whether the email address for the specified has been verified, true if the email address is verified otherwise
/// false.
///
/// The user whose email confirmation status should be returned.
///
/// The task object containing the results of the asynchronous operation, a flag indicating whether the email address for the specified
/// has been confirmed or not.
///
public virtual async Task IsEmailConfirmedAsync(TUser user)
{
ThrowIfDisposed();
var store = GetEmailStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
return await store.GetEmailConfirmedAsync(user, CancellationToken);
}
///
/// Generates an email change token for the specified user.
///
/// The user to generate an email change token for.
/// The new email address.
///
/// The that represents the asynchronous operation, an email change token.
///
public virtual Task GenerateChangeEmailTokenAsync(TUser user, string newEmail)
{
ThrowIfDisposed();
return GenerateUserTokenAsync(user, Options.Tokens.ChangeEmailTokenProvider, GetChangeEmailTokenPurpose(newEmail));
}
///
/// Updates a users emails if the specified email change is valid for the user.
///
/// The user whose email should be updated.
/// The new email address.
/// The change email token to be verified.
///
/// The that represents the asynchronous operation, containing the
/// of the operation.
///
public virtual async Task ChangeEmailAsync(TUser user, string newEmail, string token)
{
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
// Make sure the token is valid and the stamp matches
if (!await VerifyUserTokenAsync(user, Options.Tokens.ChangeEmailTokenProvider, GetChangeEmailTokenPurpose(newEmail), token))
{
return IdentityResult.Failed(ErrorDescriber.InvalidToken());
}
var store = GetEmailStore();
await store.SetEmailAsync(user, newEmail, CancellationToken);
await store.SetEmailConfirmedAsync(user, true, CancellationToken);
await UpdateSecurityStampInternal(user);
return await UpdateUserAsync(user);
}
///
/// Gets the telephone number, if any, for the specified .
///
/// The user whose telephone number should be retrieved.
/// The that represents the asynchronous operation, containing the user's telephone number, if any.
public virtual async Task GetPhoneNumberAsync(TUser user)
{
ThrowIfDisposed();
var store = GetPhoneNumberStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
return await store.GetPhoneNumberAsync(user, CancellationToken);
}
///
/// Sets the phone number for the specified .
///
/// The user whose phone number to set.
/// The phone number to set.
///
/// The that represents the asynchronous operation, containing the
/// of the operation.
///
public virtual async Task SetPhoneNumberAsync(TUser user, string phoneNumber)
{
ThrowIfDisposed();
var store = GetPhoneNumberStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
await store.SetPhoneNumberAsync(user, phoneNumber, CancellationToken);
await store.SetPhoneNumberConfirmedAsync(user, false, CancellationToken);
await UpdateSecurityStampInternal(user);
return await UpdateUserAsync(user);
}
///
/// Sets the phone number for the specified if the specified
/// change is valid.
///
/// The user whose phone number to set.
/// The phone number to set.
/// The phone number confirmation token to validate.
///
/// The that represents the asynchronous operation, containing the
/// of the operation.
///
public virtual async Task ChangePhoneNumberAsync(TUser user, string phoneNumber, string token)
{
ThrowIfDisposed();
var store = GetPhoneNumberStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (!await VerifyChangePhoneNumberTokenAsync(user, token, phoneNumber))
{
Logger.LogWarning(7, "Change phone number for user {userId} failed with invalid token.", await GetUserIdAsync(user));
return IdentityResult.Failed(ErrorDescriber.InvalidToken());
}
await store.SetPhoneNumberAsync(user, phoneNumber, CancellationToken);
await store.SetPhoneNumberConfirmedAsync(user, true, CancellationToken);
await UpdateSecurityStampInternal(user);
return await UpdateUserAsync(user);
}
///
/// Gets a flag indicating whether the specified 's telephone number has been confirmed.
///
/// The user to return a flag for, indicating whether their telephone number is confirmed.
///
/// The that represents the asynchronous operation, returning true if the specified has a confirmed
/// telephone number otherwise false.
///
public virtual Task IsPhoneNumberConfirmedAsync(TUser user)
{
ThrowIfDisposed();
var store = GetPhoneNumberStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
return store.GetPhoneNumberConfirmedAsync(user, CancellationToken);
}
///
/// Generates a telephone number change token for the specified user.
///
/// The user to generate a telephone number token for.
/// The new phone number the validation token should be sent to.
///
/// The that represents the asynchronous operation, containing the telephone change number token.
///
public virtual Task GenerateChangePhoneNumberTokenAsync(TUser user, string phoneNumber)
{
ThrowIfDisposed();
return GenerateUserTokenAsync(user, Options.Tokens.ChangePhoneNumberTokenProvider, ChangePhoneNumberTokenPurpose + ":" + phoneNumber);
}
///
/// Returns a flag indicating whether the specified 's phone number change verification
/// token is valid for the given .
///
/// The user to validate the token against.
/// The telephone number change token to validate.
/// The telephone number the token was generated for.
///
/// The that represents the asynchronous operation, returning true if the
/// is valid, otherwise false.
///
public virtual Task VerifyChangePhoneNumberTokenAsync(TUser user, string token, string phoneNumber)
{
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
// Make sure the token is valid and the stamp matches
return VerifyUserTokenAsync(user, Options.Tokens.ChangePhoneNumberTokenProvider, ChangePhoneNumberTokenPurpose+":"+ phoneNumber, token);
}
///
/// Returns a flag indicating whether the specified is valid for
/// the given and .
///
/// The user to validate the token against.
/// The token provider used to generate the token.
/// The purpose the token should be generated for.
/// The token to validate
///
/// The that represents the asynchronous operation, returning true if the
/// is valid, otherwise false.
///
public virtual async Task VerifyUserTokenAsync(TUser user, string tokenProvider, string purpose, string token)
{
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (tokenProvider == null)
{
throw new ArgumentNullException(nameof(tokenProvider));
}
if (!_tokenProviders.ContainsKey(tokenProvider))
{
throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, Resources.NoTokenProvider, tokenProvider));
}
// Make sure the token is valid
var result = await _tokenProviders[tokenProvider].ValidateAsync(purpose, token, this, user);
if (!result)
{
Logger.LogWarning(9, "VerifyUserTokenAsync() failed with purpose: {purpose} for user {userId}.", purpose, await GetUserIdAsync(user));
}
return result;
}
///
/// Generates a token for the given and .
///
/// The purpose the token will be for.
/// The user the token will be for.
/// The provider which will generate the token.
///
/// The that represents result of the asynchronous operation, a token for
/// the given user and purpose.
///
public virtual Task GenerateUserTokenAsync(TUser user, string tokenProvider, string purpose)
{
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (tokenProvider == null)
{
throw new ArgumentNullException(nameof(tokenProvider));
}
if (!_tokenProviders.ContainsKey(tokenProvider))
{
throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, Resources.NoTokenProvider, tokenProvider));
}
return _tokenProviders[tokenProvider].GenerateAsync(purpose, this, user);
}
///
/// Registers a token provider.
///
/// The name of the provider to register.
/// The provider to register.
public virtual void RegisterTokenProvider(string providerName, IUserTwoFactorTokenProvider provider)
{
ThrowIfDisposed();
if (provider == null)
{
throw new ArgumentNullException(nameof(provider));
}
_tokenProviders[providerName] = provider;
}
///
/// Gets a list of valid two factor token providers for the specified ,
/// as an asynchronous operation.
///
/// The user the whose two factor authentication providers will be returned.
///
/// The that represents result of the asynchronous operation, a list of two
/// factor authentication providers for the specified user.
///
public virtual async Task> GetValidTwoFactorProvidersAsync(TUser user)
{
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var results = new List();
foreach (var f in _tokenProviders)
{
if (await f.Value.CanGenerateTwoFactorTokenAsync(this, user))
{
results.Add(f.Key);
}
}
return results;
}
///
/// Verifies the specified two factor authentication against the .
///
/// The user the token is supposed to be for.
/// The provider which will verify the token.
/// The token to verify.
///
/// The that represents result of the asynchronous operation, true if the token is valid,
/// otherwise false.
///
public virtual async Task VerifyTwoFactorTokenAsync(TUser user, string tokenProvider, string token)
{
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (!_tokenProviders.ContainsKey(tokenProvider))
{
throw new NotSupportedException(String.Format(CultureInfo.CurrentCulture,
Resources.NoTokenProvider, tokenProvider));
}
// Make sure the token is valid
var result = await _tokenProviders[tokenProvider].ValidateAsync("TwoFactor", token, this, user);
if (!result)
{
Logger.LogWarning(10, $"{nameof(VerifyTwoFactorTokenAsync)}() failed for user {await GetUserIdAsync(user)}.");
}
return result;
}
///
/// Gets a two factor authentication token for the specified .
///
/// The user the token is for.
/// The provider which will generate the token.
///
/// The that represents result of the asynchronous operation, a two factor authentication token
/// for the user.
///
public virtual Task GenerateTwoFactorTokenAsync(TUser user, string tokenProvider)
{
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (!_tokenProviders.ContainsKey(tokenProvider))
{
throw new NotSupportedException(String.Format(CultureInfo.CurrentCulture,
Resources.NoTokenProvider, tokenProvider));
}
return _tokenProviders[tokenProvider].GenerateAsync("TwoFactor", this, user);
}
///
/// Returns a flag indicating whether the specified has two factor authentication enabled or not,
/// as an asynchronous operation.
///
/// The user whose two factor authentication enabled status should be retrieved.
///
/// The that represents the asynchronous operation, true if the specified
/// has two factor authentication enabled, otherwise false.
///
public virtual async Task GetTwoFactorEnabledAsync(TUser user)
{
ThrowIfDisposed();
var store = GetUserTwoFactorStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
return await store.GetTwoFactorEnabledAsync(user, CancellationToken);
}
///
/// Sets a flag indicating whether the specified has two factor authentication enabled or not,
/// as an asynchronous operation.
///
/// The user whose two factor authentication enabled status should be set.
/// A flag indicating whether the specified has two factor authentication enabled.
///
/// The that represents the asynchronous operation, the of the operation
///
public virtual async Task SetTwoFactorEnabledAsync(TUser user, bool enabled)
{
ThrowIfDisposed();
var store = GetUserTwoFactorStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
await store.SetTwoFactorEnabledAsync(user, enabled, CancellationToken);
await UpdateSecurityStampInternal(user);
return await UpdateUserAsync(user);
}
///
/// Returns a flag indicating whether the specified his locked out,
/// as an asynchronous operation.
///
/// The user whose locked out status should be retrieved.
///
/// The that represents the asynchronous operation, true if the specified
/// is locked out, otherwise false.
///
public virtual async Task IsLockedOutAsync(TUser user)
{
ThrowIfDisposed();
var store = GetUserLockoutStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (!await store.GetLockoutEnabledAsync(user, CancellationToken))
{
return false;
}
var lockoutTime = await store.GetLockoutEndDateAsync(user, CancellationToken);
return lockoutTime >= DateTimeOffset.UtcNow;
}
///
/// Sets a flag indicating whether the specified is locked out,
/// as an asynchronous operation.
///
/// The user whose locked out status should be set.
/// Flag indicating whether the user is locked out or not.
///
/// The that represents the asynchronous operation, the of the operation
///
public virtual async Task SetLockoutEnabledAsync(TUser user, bool enabled)
{
ThrowIfDisposed();
var store = GetUserLockoutStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
await store.SetLockoutEnabledAsync(user, enabled, CancellationToken);
return await UpdateUserAsync(user);
}
///
/// Retrieves a flag indicating whether user lockout can enabled for the specified user.
///
/// The user whose ability to be locked out should be returned.
///
/// The that represents the asynchronous operation, true if a user can be locked out, otherwise false.
///
public virtual async Task GetLockoutEnabledAsync(TUser user)
{
ThrowIfDisposed();
var store = GetUserLockoutStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
return await store.GetLockoutEnabledAsync(user, CancellationToken);
}
///
/// Gets the last a user's last lockout expired, if any.
/// Any time in the past should be indicates a user is not locked out.
///
/// The user whose lockout date should be retrieved.
///
/// A that represents the lookup, a containing the last time a user's lockout expired, if any.
///
public virtual async Task GetLockoutEndDateAsync(TUser user)
{
ThrowIfDisposed();
var store = GetUserLockoutStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
return await store.GetLockoutEndDateAsync(user, CancellationToken);
}
///
/// Locks out a user until the specified end date has passed. Setting a end date in the past immediately unlocks a user.
///
/// The user whose lockout date should be set.
/// The after which the 's lockout should end.
/// The that represents the asynchronous operation, containing the of the operation.
public virtual async Task SetLockoutEndDateAsync(TUser user, DateTimeOffset? lockoutEnd)
{
ThrowIfDisposed();
var store = GetUserLockoutStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (!await store.GetLockoutEnabledAsync(user, CancellationToken))
{
Logger.LogWarning(11, "Lockout for user {userId} failed because lockout is not enabled for this user.", await GetUserIdAsync(user));
return IdentityResult.Failed(ErrorDescriber.UserLockoutNotEnabled());
}
await store.SetLockoutEndDateAsync(user, lockoutEnd, CancellationToken);
return await UpdateUserAsync(user);
}
///
/// Increments the access failed count for the user as an asynchronous operation.
/// If the failed access account is greater than or equal to the configured maximum number of attempts,
/// the user will be locked out for the configured lockout time span.
///
/// The user whose failed access count to increment.
/// The that represents the asynchronous operation, containing the of the operation.
public virtual async Task AccessFailedAsync(TUser user)
{
ThrowIfDisposed();
var store = GetUserLockoutStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
// If this puts the user over the threshold for lockout, lock them out and reset the access failed count
var count = await store.IncrementAccessFailedCountAsync(user, CancellationToken);
if (count < Options.Lockout.MaxFailedAccessAttempts)
{
return await UpdateUserAsync(user);
}
Logger.LogWarning(12, "User {userId} is locked out.", await GetUserIdAsync(user));
await store.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow.Add(Options.Lockout.DefaultLockoutTimeSpan),
CancellationToken);
await store.ResetAccessFailedCountAsync(user, CancellationToken);
return await UpdateUserAsync(user);
}
///
/// Resets the access failed count for the specified .
///
/// The user whose failed access count should be reset.
/// The that represents the asynchronous operation, containing the of the operation.
public virtual async Task ResetAccessFailedCountAsync(TUser user)
{
ThrowIfDisposed();
var store = GetUserLockoutStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (await GetAccessFailedCountAsync(user) == 0)
{
return IdentityResult.Success;
}
await store.ResetAccessFailedCountAsync(user, CancellationToken);
return await UpdateUserAsync(user);
}
///
/// Retrieves the current number of failed accesses for the given .
///
/// The user whose access failed count should be retrieved for.
/// The that contains the result the asynchronous operation, the current failed access count
/// for the user.
public virtual async Task GetAccessFailedCountAsync(TUser user)
{
ThrowIfDisposed();
var store = GetUserLockoutStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
return await store.GetAccessFailedCountAsync(user, CancellationToken);
}
///
/// Returns a list of users from the user store who have the specified .
///
/// The claim to look for.
///
/// A that represents the result of the asynchronous query, a list of s who
/// have the specified claim.
///
public virtual Task> GetUsersForClaimAsync(Claim claim)
{
ThrowIfDisposed();
var store = GetClaimStore();
if (claim == null)
{
throw new ArgumentNullException(nameof(claim));
}
return store.GetUsersForClaimAsync(claim, CancellationToken);
}
///
/// Returns a list of users from the user store who are members of the specified .
///
/// The name of the role whose users should be returned.
///
/// A that represents the result of the asynchronous query, a list of s who
/// are members of the specified role.
///
public virtual Task> GetUsersInRoleAsync(string roleName)
{
ThrowIfDisposed();
var store = GetUserRoleStore();
if (roleName == null)
{
throw new ArgumentNullException(nameof(roleName));
}
return store.GetUsersInRoleAsync(NormalizeKey(roleName), CancellationToken);
}
///
/// Returns an authentication token for a user.
///
///
/// The authentication scheme for the provider the token is associated with.
/// The name of the token.
/// The authentication token for a user
public virtual Task GetAuthenticationTokenAsync(TUser user, string loginProvider, string tokenName)
{
ThrowIfDisposed();
var store = GetAuthenticationTokenStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (loginProvider == null)
{
throw new ArgumentNullException(nameof(loginProvider));
}
if (tokenName == null)
{
throw new ArgumentNullException(nameof(tokenName));
}
return store.GetTokenAsync(user, loginProvider, tokenName, CancellationToken);
}
///
/// Sets an authentication token for a user.
///
///
/// The authentication scheme for the provider the token is associated with.
/// The name of the token.
/// The value of the token.
/// Whether the user was successfully updated.
public virtual async Task SetAuthenticationTokenAsync(TUser user, string loginProvider, string tokenName, string tokenValue)
{
ThrowIfDisposed();
var store = GetAuthenticationTokenStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (loginProvider == null)
{
throw new ArgumentNullException(nameof(loginProvider));
}
if (tokenName == null)
{
throw new ArgumentNullException(nameof(tokenName));
}
// REVIEW: should updating any tokens affect the security stamp?
await store.SetTokenAsync(user, loginProvider, tokenName, tokenValue, CancellationToken);
return await UpdateUserAsync(user);
}
///
/// Remove an authentication token for a user.
///
///
/// The authentication scheme for the provider the token is associated with.
/// The name of the token.
/// Whether a token was removed.
public virtual async Task RemoveAuthenticationTokenAsync(TUser user, string loginProvider, string tokenName)
{
ThrowIfDisposed();
var store = GetAuthenticationTokenStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (loginProvider == null)
{
throw new ArgumentNullException(nameof(loginProvider));
}
if (tokenName == null)
{
throw new ArgumentNullException(nameof(tokenName));
}
await store.RemoveTokenAsync(user, loginProvider, tokenName, CancellationToken);
return await UpdateUserAsync(user);
}
///
/// Returns the authenticator key for the user.
///
/// The user.
/// The authenticator key
public virtual Task GetAuthenticatorKeyAsync(TUser user)
{
ThrowIfDisposed();
var store = GetAuthenticatorKeyStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
return store.GetAuthenticatorKeyAsync(user, CancellationToken);
}
///
/// Resets the authenticator key for the user.
///
/// The user.
/// Whether the user was successfully updated.
public virtual async Task ResetAuthenticatorKeyAsync(TUser user)
{
ThrowIfDisposed();
var store = GetAuthenticatorKeyStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
await store.SetAuthenticatorKeyAsync(user, GenerateNewAuthenticatorKey(), CancellationToken);
return await UpdateAsync(user);
}
///
/// Generates a new base32 encoded 160-bit security secret (size of SHA1 hash).
///
/// The new security secret.
public virtual string GenerateNewAuthenticatorKey()
{
byte[] bytes = new byte[20];
_rng.GetBytes(bytes);
return Base32.ToBase32(bytes);
}
///
/// Generates recovery codes for the user, this invalidates any previous recovery codes for the user.
///
/// The user to generate recovery codes for.
/// The number of codes to generate.
/// The new recovery codes for the user.
public virtual async Task> GenerateNewTwoFactorRecoveryCodesAsync(TUser user, int number)
{
ThrowIfDisposed();
var store = GetRecoveryCodeStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var newCodes = new List(number);
for (var i = 0; i < number; i++)
{
newCodes.Add(CreateTwoFactorRecoveryCode());
}
await store.ReplaceCodesAsync(user, newCodes, CancellationToken);
var update = await UpdateAsync(user);
if (update.Succeeded)
{
return newCodes;
}
return null;
}
///
/// Generate a new recovery code.
///
///
protected virtual string CreateTwoFactorRecoveryCode()
{
return Guid.NewGuid().ToString();
}
///
/// Returns whether a recovery code is valid for a user. Note: recovery codes are only valid
/// once, and will be invalid after use.
///
/// The user who owns the recovery code.
/// The recovery code to use.
/// True if the recovery code was found for the user.
public virtual async Task RedeemTwoFactorRecoveryCodeAsync(TUser user, string code)
{
ThrowIfDisposed();
var store = GetRecoveryCodeStore();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var success = await store.RedeemCodeAsync(user, code, CancellationToken);
if (success)
{
return await UpdateAsync(user);
}
return IdentityResult.Failed(ErrorDescriber.RecoveryCodeRedemptionFailed());
}
///
/// Releases the unmanaged resources used by the role manager and optionally releases the managed resources.
///
/// true to release both managed and unmanaged resources; false to release only unmanaged resources.
protected virtual void Dispose(bool disposing)
{
if (disposing && !_disposed)
{
Store.Dispose();
_disposed = true;
}
}
internal IUserTwoFactorStore GetUserTwoFactorStore()
{
var cast = Store as IUserTwoFactorStore;
if (cast == null)
{
throw new NotSupportedException(Resources.StoreNotIUserTwoFactorStore);
}
return cast;
}
internal IUserLockoutStore GetUserLockoutStore()
{
var cast = Store as IUserLockoutStore;
if (cast == null)
{
throw new NotSupportedException(Resources.StoreNotIUserLockoutStore);
}
return cast;
}
internal IUserEmailStore GetEmailStore(bool throwOnFail = true)
{
var cast = Store as IUserEmailStore;
if (throwOnFail && cast == null)
{
throw new NotSupportedException(Resources.StoreNotIUserEmailStore);
}
return cast;
}
internal IUserPhoneNumberStore GetPhoneNumberStore()
{
var cast = Store as IUserPhoneNumberStore;
if (cast == null)
{
throw new NotSupportedException(Resources.StoreNotIUserPhoneNumberStore);
}
return cast;
}
///
/// Creates bytes to use as a security token from the user's security stamp.
///
/// The user.
/// The security token bytes.
public virtual async Task CreateSecurityTokenAsync(TUser user)
{
return Encoding.Unicode.GetBytes(await GetSecurityStampAsync(user));
}
// Update the security stamp if the store supports it
internal async Task UpdateSecurityStampInternal(TUser user)
{
if (SupportsUserSecurityStamp)
{
await GetSecurityStore().SetSecurityStampAsync(user, NewSecurityStamp(), CancellationToken);
}
}
internal async Task UpdatePasswordHash(IUserPasswordStore passwordStore,
TUser user, string newPassword, bool validatePassword = true)
{
if (validatePassword)
{
var validate = await ValidatePasswordInternal(user, newPassword);
if (!validate.Succeeded)
{
return validate;
}
}
var hash = newPassword != null ? PasswordHasher.HashPassword(user, newPassword) : null;
await passwordStore.SetPasswordHashAsync(user, hash, CancellationToken);
await UpdateSecurityStampInternal(user);
return IdentityResult.Success;
}
private IUserRoleStore GetUserRoleStore()
{
var cast = Store as IUserRoleStore;
if (cast == null)
{
throw new NotSupportedException(Resources.StoreNotIUserRoleStore);
}
return cast;
}
private static string NewSecurityStamp()
{
return Guid.NewGuid().ToString();
}
// IUserLoginStore methods
private IUserLoginStore GetLoginStore()
{
var cast = Store as IUserLoginStore;
if (cast == null)
{
throw new NotSupportedException(Resources.StoreNotIUserLoginStore);
}
return cast;
}
private IUserSecurityStampStore GetSecurityStore()
{
var cast = Store as IUserSecurityStampStore;
if (cast == null)
{
throw new NotSupportedException(Resources.StoreNotIUserSecurityStampStore);
}
return cast;
}
private IUserClaimStore GetClaimStore()
{
var cast = Store as IUserClaimStore;
if (cast == null)
{
throw new NotSupportedException(Resources.StoreNotIUserClaimStore);
}
return cast;
}
///
/// Generates the token purpose used to change email
///
///
///
protected static string GetChangeEmailTokenPurpose(string newEmail)
{
return "ChangeEmail:" + newEmail;
}
private async Task ValidateUserInternal(TUser user)
{
if (SupportsUserSecurityStamp)
{
var stamp = await GetSecurityStampAsync(user);
if (stamp == null)
{
throw new InvalidOperationException(Resources.NullSecurityStamp);
}
}
var errors = new List();
foreach (var v in UserValidators)
{
var result = await v.ValidateAsync(this, user);
if (!result.Succeeded)
{
errors.AddRange(result.Errors);
}
}
if (errors.Count > 0)
{
Logger.LogWarning(13, "User {userId} validation failed: {errors}.", await GetUserIdAsync(user), string.Join(";", errors.Select(e => e.Code)));
return IdentityResult.Failed(errors.ToArray());
}
return IdentityResult.Success;
}
private async Task ValidatePasswordInternal(TUser user, string password)
{
var errors = new List();
foreach (var v in PasswordValidators)
{
var result = await v.ValidateAsync(this, user, password);
if (!result.Succeeded)
{
errors.AddRange(result.Errors);
}
}
if (errors.Count > 0)
{
Logger.LogWarning(14, "User {userId} password validation failed: {errors}.", await GetUserIdAsync(user), string.Join(";", errors.Select(e => e.Code)));
return IdentityResult.Failed(errors.ToArray());
}
return IdentityResult.Success;
}
private async Task UpdateUserAsync(TUser user)
{
var result = await ValidateUserInternal(user);
if (!result.Succeeded)
{
return result;
}
await UpdateNormalizedUserNameAsync(user);
await UpdateNormalizedEmailAsync(user);
return await Store.UpdateAsync(user, CancellationToken);
}
private IUserAuthenticatorKeyStore GetAuthenticatorKeyStore()
{
var cast = Store as IUserAuthenticatorKeyStore;
if (cast == null)
{
throw new NotSupportedException(Resources.StoreNotIUserAuthenticatorKeyStore);
}
return cast;
}
private IUserTwoFactorRecoveryCodeStore GetRecoveryCodeStore()
{
var cast = Store as IUserTwoFactorRecoveryCodeStore;
if (cast == null)
{
throw new NotSupportedException(Resources.StoreNotIUserTwoFactorRecoveryCodeStore);
}
return cast;
}
private IUserAuthenticationTokenStore GetAuthenticationTokenStore()
{
var cast = Store as IUserAuthenticationTokenStore;
if (cast == null)
{
throw new NotSupportedException(Resources.StoreNotIUserAuthenticationTokenStore);
}
return cast;
}
private IUserPasswordStore GetPasswordStore()
{
var cast = Store as IUserPasswordStore;
if (cast == null)
{
throw new NotSupportedException(Resources.StoreNotIUserPasswordStore);
}
return cast;
}
///
/// Throws if this class has been disposed.
///
protected void ThrowIfDisposed()
{
if (_disposed)
{
throw new ObjectDisposedException(GetType().Name);
}
}
}
}