// 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); } } } }