// Copyright (c) Microsoft Open Technologies, Inc. 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.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Framework.OptionsModel; namespace Microsoft.AspNet.Identity { /// /// Exposes user related api which will automatically save changes to the UserStore /// /// public class UserManager : IDisposable where TUser : class { private readonly Dictionary> _tokenProviders = new Dictionary>(); private TimeSpan _defaultLockout = TimeSpan.Zero; private bool _disposed; private IPasswordHasher _passwordHasher; private IdentityOptions _options; /// /// Constructor which takes a service provider and user store /// /// /// /// /// /// /// public UserManager(IUserStore store, IOptions optionsAccessor, IPasswordHasher passwordHasher, IUserValidator userValidator, IPasswordValidator passwordValidator, IUserNameNormalizer userNameNormalizer, IEnumerable> tokenProviders) { if (store == null) { throw new ArgumentNullException(nameof(store)); } if (optionsAccessor == null || optionsAccessor.Options == null) { throw new ArgumentNullException(nameof(optionsAccessor)); } if (passwordHasher == null) { throw new ArgumentNullException(nameof(passwordHasher)); } Store = store; Options = optionsAccessor.Options; PasswordHasher = passwordHasher; UserValidator = userValidator; PasswordValidator = passwordValidator; UserNameNormalizer = userNameNormalizer; // TODO: Email/Sms/Token services if (tokenProviders != null) { foreach (var tokenProvider in tokenProviders) { RegisterTokenProvider(tokenProvider); } } } /// /// Persistence abstraction that the Manager operates against /// protected internal IUserStore Store { get; set; } /// /// Used to hash/verify passwords /// public IPasswordHasher PasswordHasher { get { ThrowIfDisposed(); return _passwordHasher; } set { ThrowIfDisposed(); if (value == null) { throw new ArgumentNullException("value"); } _passwordHasher = value; } } /// /// Used to validate users before persisting changes /// public IUserValidator UserValidator { get; set; } /// /// Used to validate passwords before persisting changes /// public IPasswordValidator PasswordValidator { get; set; } /// /// Used to normalize user names for uniqueness /// public IUserNameNormalizer UserNameNormalizer { get; set; } /// /// Used to send email /// public IIdentityMessageService EmailService { get; set; } /// /// Used to send a sms message /// public IIdentityMessageService SmsService { get; set; } public IdentityOptions Options { get { ThrowIfDisposed(); return _options; } set { ThrowIfDisposed(); if (value == null) { throw new ArgumentNullException("value"); } _options = value; } } /// /// Returns true if the store is an IUserTwoFactorStore /// public virtual bool SupportsUserTwoFactor { get { ThrowIfDisposed(); return Store is IUserTwoFactorStore; } } /// /// Returns true if the store is an IUserPasswordStore /// public virtual bool SupportsUserPassword { get { ThrowIfDisposed(); return Store is IUserPasswordStore; } } /// /// Returns true if the store is an IUserSecurityStore /// public virtual bool SupportsUserSecurityStamp { get { ThrowIfDisposed(); return Store is IUserSecurityStampStore; } } /// /// Returns true if the store is an IUserRoleStore /// public virtual bool SupportsUserRole { get { ThrowIfDisposed(); return Store is IUserRoleStore; } } /// /// Returns true if the store is an IUserLoginStore /// public virtual bool SupportsUserLogin { get { ThrowIfDisposed(); return Store is IUserLoginStore; } } /// /// Returns true if the store is an IUserEmailStore /// public virtual bool SupportsUserEmail { get { ThrowIfDisposed(); return Store is IUserEmailStore; } } /// /// Returns true if the store is an IUserPhoneNumberStore /// public virtual bool SupportsUserPhoneNumber { get { ThrowIfDisposed(); return Store is IUserPhoneNumberStore; } } /// /// Returns true if the store is an IUserClaimStore /// public virtual bool SupportsUserClaim { get { ThrowIfDisposed(); return Store is IUserClaimStore; } } /// /// Returns true if the store is an IUserLockoutStore /// public virtual bool SupportsUserLockout { get { ThrowIfDisposed(); return Store is IUserLockoutStore; } } /// /// Returns true if the store is an IQueryableUserStore /// 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; } } /// /// Dispose the store context /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private async Task ValidateUserInternal(TUser user, CancellationToken cancellationToken) { return (UserValidator == null) ? IdentityResult.Success : await UserValidator.ValidateAsync(this, user, cancellationToken); } /// /// Create a user with no password /// /// /// /// public virtual async Task CreateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); await UpdateSecurityStampInternal(user, cancellationToken); var result = await ValidateUserInternal(user, cancellationToken); if (!result.Succeeded) { return result; } if (Options.Lockout.EnabledByDefault && SupportsUserLockout) { await GetUserLockoutStore().SetLockoutEnabledAsync(user, true, cancellationToken); } await UpdateNormalizedUserNameAsync(user, cancellationToken); await Store.CreateAsync(user, cancellationToken); return IdentityResult.Success; } /// /// Update a user /// /// /// /// public virtual async Task UpdateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); if (user == null) { throw new ArgumentNullException("user"); } var result = await ValidateUserInternal(user, cancellationToken); if (!result.Succeeded) { return result; } await UpdateNormalizedUserNameAsync(user, cancellationToken); await Store.UpdateAsync(user, cancellationToken); return IdentityResult.Success; } /// /// Delete a user /// /// /// /// public virtual async Task DeleteAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); if (user == null) { throw new ArgumentNullException("user"); } await Store.DeleteAsync(user, cancellationToken); return IdentityResult.Success; } /// /// Find a user by id /// /// /// /// public virtual Task FindByIdAsync(string userId, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); return Store.FindByIdAsync(userId, cancellationToken); } /// /// Find a user by name /// /// /// /// public virtual Task FindByNameAsync(string userName, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); if (userName == null) { throw new ArgumentNullException("userName"); } userName = NormalizeUserName(userName); return Store.FindByNameAsync(userName, cancellationToken); } // IUserPasswordStore methods private IUserPasswordStore GetPasswordStore() { var cast = Store as IUserPasswordStore; if (cast == null) { throw new NotSupportedException(Resources.StoreNotIUserPasswordStore); } return cast; } /// /// Create a user and associates it with the given password /// /// /// /// /// public virtual async Task CreateAsync(TUser user, string password, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var passwordStore = GetPasswordStore(); if (user == null) { throw new ArgumentNullException("user"); } if (password == null) { throw new ArgumentNullException("password"); } var result = await UpdatePasswordInternal(passwordStore, user, password, cancellationToken); if (!result.Succeeded) { return result; } return await CreateAsync(user, cancellationToken); } /// /// Normalize a user name for uniqueness comparisons /// /// /// public virtual string NormalizeUserName(string userName) { return (UserNameNormalizer == null) ? userName : UserNameNormalizer.Normalize(userName); } /// /// Update the user's normalized user name /// /// /// /// public virtual async Task UpdateNormalizedUserNameAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { var userName = await GetUserNameAsync(user, cancellationToken); await Store.SetNormalizedUserNameAsync(user, NormalizeUserName(userName), cancellationToken); } /// /// Get the user's name /// /// /// /// public virtual async Task GetUserNameAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); if (user == null) { throw new ArgumentNullException("user"); } return await Store.GetUserNameAsync(user, cancellationToken); } /// /// Set the user's name /// /// /// /// /// public virtual async Task SetUserNameAsync(TUser user, string userName, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); if (user == null) { throw new ArgumentNullException("user"); } await UpdateUserName(user, userName, cancellationToken); return await UpdateAsync(user, cancellationToken); } private async Task UpdateUserName(TUser user, string userName, CancellationToken cancellationToken) { await Store.SetUserNameAsync(user, userName, cancellationToken); await UpdateNormalizedUserNameAsync(user, cancellationToken); } /// /// Get the user's id /// /// /// /// public virtual async Task GetUserIdAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); return await Store.GetUserIdAsync(user, cancellationToken); } /// /// Return a user with the specified username and password or null if there is no match. /// /// /// /// /// public virtual async Task FindByUserNamePasswordAsync(string userName, string password, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); userName = NormalizeUserName(userName); var user = await FindByNameAsync(userName, cancellationToken); if (user == null) { return null; } return await CheckPasswordAsync(user, password, cancellationToken) ? user : null; } /// /// Returns true if the password combination is valid for the user /// /// /// /// /// public virtual async Task CheckPasswordAsync(TUser user, string password, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var passwordStore = GetPasswordStore(); if (user == null) { return false; } return await VerifyPasswordAsync(passwordStore, user, password, cancellationToken); } /// /// Returns true if the user has a password /// /// /// /// public virtual async Task HasPasswordAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var passwordStore = GetPasswordStore(); if (user == null) { throw new ArgumentNullException("user"); } return await passwordStore.HasPasswordAsync(user, cancellationToken); } /// /// Add a user password only if one does not already exist /// /// /// /// /// public virtual async Task AddPasswordAsync(TUser user, string password, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var passwordStore = GetPasswordStore(); if (user == null) { throw new ArgumentNullException("user"); } var hash = await passwordStore.GetPasswordHashAsync(user, cancellationToken); if (hash != null) { return new IdentityResult(Resources.UserAlreadyHasPassword); } var result = await UpdatePasswordInternal(passwordStore, user, password, cancellationToken); if (!result.Succeeded) { return result; } return await UpdateAsync(user, cancellationToken); } /// /// Change a user password /// /// /// /// /// /// public virtual async Task ChangePasswordAsync(TUser user, string currentPassword, string newPassword, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var passwordStore = GetPasswordStore(); if (user == null) { throw new ArgumentNullException("user"); } if (await VerifyPasswordAsync(passwordStore, user, currentPassword, cancellationToken)) { var result = await UpdatePasswordInternal(passwordStore, user, newPassword, cancellationToken); if (!result.Succeeded) { return result; } return await UpdateAsync(user, cancellationToken); } return IdentityResult.Failed(Resources.PasswordMismatch); } /// /// Remove a user's password /// /// /// /// public virtual async Task RemovePasswordAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var passwordStore = GetPasswordStore(); if (user == null) { throw new ArgumentNullException("user"); } await passwordStore.SetPasswordHashAsync(user, null, cancellationToken); await UpdateSecurityStampInternal(user, cancellationToken); return await UpdateAsync(user, cancellationToken); } internal async Task UpdatePasswordInternal(IUserPasswordStore passwordStore, TUser user, string newPassword, CancellationToken cancellationToken) { if (PasswordValidator != null) { var result = await PasswordValidator.ValidateAsync(user, newPassword, this, cancellationToken); if (!result.Succeeded) { return result; } } await passwordStore.SetPasswordHashAsync(user, PasswordHasher.HashPassword(user, newPassword), cancellationToken); await UpdateSecurityStampInternal(user, cancellationToken); return IdentityResult.Success; } /// /// By default, retrieves the hashed password from the user store and calls PasswordHasher.VerifyHashPassword /// /// /// /// /// /// protected virtual async Task VerifyPasswordAsync(IUserPasswordStore store, TUser user, string password, CancellationToken cancellationToken = default(CancellationToken)) { var hash = await store.GetPasswordHashAsync(user, cancellationToken); return PasswordHasher.VerifyHashedPassword(user, hash, password) != PasswordVerificationResult.Failed; } // IUserSecurityStampStore methods private IUserSecurityStampStore GetSecurityStore() { var cast = Store as IUserSecurityStampStore; if (cast == null) { throw new NotSupportedException(Resources.StoreNotIUserSecurityStampStore); } return cast; } /// /// Returns the current security stamp for a user /// /// /// /// public virtual async Task GetSecurityStampAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var securityStore = GetSecurityStore(); if (user == null) { throw new ArgumentNullException("user"); } return await securityStore.GetSecurityStampAsync(user, cancellationToken); } /// /// GenerateAsync a new security stamp for a user, used for SignOutEverywhere functionality /// /// /// /// public virtual async Task UpdateSecurityStampAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); GetSecurityStore(); if (user == null) { throw new ArgumentNullException("user"); } await UpdateSecurityStampInternal(user, cancellationToken); return await UpdateAsync(user, cancellationToken); } /// /// GenerateAsync a password reset token for the user using the UserTokenProvider /// /// /// /// public virtual async Task GeneratePasswordResetTokenAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); return await GenerateUserTokenAsync(user, Options.PasswordResetTokenProvider, "ResetPassword", cancellationToken); } /// /// Reset a user's password using a reset password token /// /// /// /// /// /// public virtual async Task ResetPasswordAsync(TUser user, string token, string newPassword, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); if (user == null) { throw new ArgumentNullException("user"); } // Make sure the token is valid and the stamp matches if (!await VerifyUserTokenAsync(user, Options.PasswordResetTokenProvider, "ResetPassword", token, cancellationToken)) { return IdentityResult.Failed(Resources.InvalidToken); } var passwordStore = GetPasswordStore(); var result = await UpdatePasswordInternal(passwordStore, user, newPassword, cancellationToken); if (!result.Succeeded) { return result; } return await UpdateAsync(user, cancellationToken); } // Update the security stamp if the store supports it internal async Task UpdateSecurityStampInternal(TUser user, CancellationToken cancellationToken) { if (SupportsUserSecurityStamp) { await GetSecurityStore().SetSecurityStampAsync(user, NewSecurityStamp(), cancellationToken); } } 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; } /// /// Returns the user associated with this login /// /// /// /// /// public virtual Task FindByLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var loginStore = GetLoginStore(); if (loginProvider == null) { throw new ArgumentNullException("loginProvider"); } if (providerKey == null) { throw new ArgumentNullException("providerKey"); } return loginStore.FindByLoginAsync(loginProvider, providerKey, cancellationToken); } /// /// Remove a user login /// /// /// /// /// public virtual async Task RemoveLoginAsync(TUser user, string loginProvider, string providerKey, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var loginStore = GetLoginStore(); if (loginProvider == null) { throw new ArgumentNullException("loginProvider"); } if (providerKey == null) { throw new ArgumentNullException("providerKey"); } if (user == null) { throw new ArgumentNullException("user"); } await loginStore.RemoveLoginAsync(user, loginProvider, providerKey, cancellationToken); await UpdateSecurityStampInternal(user, cancellationToken); return await UpdateAsync(user, cancellationToken); } /// /// Associate a login with a user /// /// /// /// /// public virtual async Task AddLoginAsync(TUser user, UserLoginInfo login, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var loginStore = GetLoginStore(); if (login == null) { throw new ArgumentNullException("login"); } if (user == null) { throw new ArgumentNullException("user"); } var existingUser = await FindByLoginAsync(login.LoginProvider, login.ProviderKey, cancellationToken); if (existingUser != null) { return IdentityResult.Failed(Resources.ExternalLoginExists); } await loginStore.AddLoginAsync(user, login, cancellationToken); return await UpdateAsync(user, cancellationToken); } /// /// Gets the logins for a user. /// /// /// /// public virtual async Task> GetLoginsAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var loginStore = GetLoginStore(); if (user == null) { throw new ArgumentNullException("user"); } return await loginStore.GetLoginsAsync(user, cancellationToken); } // IUserClaimStore methods private IUserClaimStore GetClaimStore() { var cast = Store as IUserClaimStore; if (cast == null) { throw new NotSupportedException(Resources.StoreNotIUserClaimStore); } return cast; } /// /// Add a user claim /// /// /// /// /// public virtual Task AddClaimAsync(TUser user, Claim claim, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var claimStore = GetClaimStore(); if (claim == null) { throw new ArgumentNullException("claim"); } if (user == null) { throw new ArgumentNullException("user"); } return AddClaimsAsync(user, new Claim[] { claim }, cancellationToken); } /// /// Add a user claim /// /// /// /// /// public virtual async Task AddClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var claimStore = GetClaimStore(); if (claims == null) { throw new ArgumentNullException("claims"); } if (user == null) { throw new ArgumentNullException("user"); } await claimStore.AddClaimsAsync(user, claims, cancellationToken); return await UpdateAsync(user, cancellationToken); } /// /// Updates the give claim information with the given new claim information /// /// /// /// /// /// public virtual async Task ReplaceClaimAsync(TUser user, Claim claim, Claim newClaim, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var claimStore = GetClaimStore(); if (claim == null) { throw new ArgumentNullException("claim"); } if (newClaim == null) { throw new ArgumentNullException("newClaim"); } if (user == null) { throw new ArgumentNullException("user"); } await claimStore.ReplaceClaimAsync(user, claim, newClaim, cancellationToken); return await UpdateAsync(user, cancellationToken); } /// /// Remove a user claim /// /// /// /// /// public virtual Task RemoveClaimAsync(TUser user, Claim claim, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var claimStore = GetClaimStore(); if (user == null) { throw new ArgumentNullException("user"); } if (claim == null) { throw new ArgumentNullException("claim"); } return RemoveClaimsAsync(user, new Claim[] { claim }, cancellationToken); } /// /// Remove a user claim /// /// /// /// /// public virtual async Task RemoveClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var claimStore = GetClaimStore(); if (user == null) { throw new ArgumentNullException("user"); } if (claims == null) { throw new ArgumentNullException("claims"); } await claimStore.RemoveClaimsAsync(user, claims, cancellationToken); return await UpdateAsync(user, cancellationToken); } /// /// Get a users's claims /// /// /// /// public virtual async Task> GetClaimsAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var claimStore = GetClaimStore(); if (user == null) { throw new ArgumentNullException("user"); } return await claimStore.GetClaimsAsync(user, cancellationToken); } private IUserRoleStore GetUserRoleStore() { var cast = Store as IUserRoleStore; if (cast == null) { throw new NotSupportedException(Resources.StoreNotIUserRoleStore); } return cast; } /// /// Add a user to a role /// /// /// /// /// public virtual async Task AddToRoleAsync(TUser user, string role, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var userRoleStore = GetUserRoleStore(); if (user == null) { throw new ArgumentNullException("user"); } var userRoles = await userRoleStore.GetRolesAsync(user, cancellationToken); if (userRoles.Contains(role)) { return new IdentityResult(Resources.UserAlreadyInRole); } await userRoleStore.AddToRoleAsync(user, role, cancellationToken); return await UpdateAsync(user, cancellationToken); } /// /// Add a user to roles /// /// /// /// /// public virtual async Task AddToRolesAsync(TUser user, IEnumerable roles, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var userRoleStore = GetUserRoleStore(); if (user == null) { throw new ArgumentNullException("user"); } if (roles == null) { throw new ArgumentNullException("roles"); } var userRoles = await userRoleStore.GetRolesAsync(user, cancellationToken); foreach (var role in roles) { if (userRoles.Contains(role)) { return new IdentityResult(Resources.UserAlreadyInRole); } await userRoleStore.AddToRoleAsync(user, role, cancellationToken); } return await UpdateAsync(user, cancellationToken); } /// /// Remove a user from a role. /// /// /// /// /// public virtual async Task RemoveFromRoleAsync(TUser user, string role, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var userRoleStore = GetUserRoleStore(); if (user == null) { throw new ArgumentNullException("user"); } if (!await userRoleStore.IsInRoleAsync(user, role, cancellationToken)) { return new IdentityResult(Resources.UserNotInRole); } await userRoleStore.RemoveFromRoleAsync(user, role, cancellationToken); return await UpdateAsync(user, cancellationToken); } /// /// Remove a user from a specified roles. /// /// /// /// /// public virtual async Task RemoveFromRolesAsync(TUser user, IEnumerable roles, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var userRoleStore = GetUserRoleStore(); if (user == null) { throw new ArgumentNullException("user"); } if (roles == null) { throw new ArgumentNullException("roles"); } foreach (var role in roles) { if (!await userRoleStore.IsInRoleAsync(user, role, cancellationToken)) { return new IdentityResult(Resources.UserNotInRole); } await userRoleStore.RemoveFromRoleAsync(user, role, cancellationToken); } return await UpdateAsync(user, cancellationToken); } /// /// Returns the roles for the user /// /// /// /// public virtual async Task> GetRolesAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var userRoleStore = GetUserRoleStore(); if (user == null) { throw new ArgumentNullException("user"); } return await userRoleStore.GetRolesAsync(user, cancellationToken); } /// /// Returns true if the user is in the specified role /// /// /// /// /// public virtual async Task IsInRoleAsync(TUser user, string role, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var userRoleStore = GetUserRoleStore(); if (user == null) { throw new ArgumentNullException("user"); } return await userRoleStore.IsInRoleAsync(user, role, cancellationToken); } // IUserEmailStore methods internal IUserEmailStore GetEmailStore() { var cast = Store as IUserEmailStore; if (cast == null) { throw new NotSupportedException(Resources.StoreNotIUserEmailStore); } return cast; } /// /// Get a user's email /// /// /// /// public virtual async Task GetEmailAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var store = GetEmailStore(); if (user == null) { throw new ArgumentNullException("user"); } return await store.GetEmailAsync(user, cancellationToken); } /// /// Set a user's email /// /// /// /// /// public virtual async Task SetEmailAsync(TUser user, string email, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var store = GetEmailStore(); if (user == null) { throw new ArgumentNullException("user"); } await store.SetEmailAsync(user, email, cancellationToken); await store.SetEmailConfirmedAsync(user, false, cancellationToken); await UpdateSecurityStampInternal(user, cancellationToken); return await UpdateAsync(user, cancellationToken); } /// /// FindByLoginAsync a user by his email /// /// /// /// public virtual Task FindByEmailAsync(string email, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var store = GetEmailStore(); if (email == null) { throw new ArgumentNullException("email"); } return store.FindByEmailAsync(email, cancellationToken); } /// /// Get the confirmation token for the user /// /// /// /// public virtual Task GenerateEmailConfirmationTokenAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); return GenerateUserTokenAsync(user, Options.EmailConfirmationTokenProvider, "Confirmation", cancellationToken); } /// /// Confirm the user with confirmation token /// /// /// /// /// public virtual async Task ConfirmEmailAsync(TUser user, string token, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var store = GetEmailStore(); if (user == null) { throw new ArgumentNullException("user"); } if (!await VerifyUserTokenAsync(user, Options.EmailConfirmationTokenProvider, "Confirmation", token, cancellationToken)) { return IdentityResult.Failed(Resources.InvalidToken); } await store.SetEmailConfirmedAsync(user, true, cancellationToken); return await UpdateAsync(user, cancellationToken); } /// /// Returns true if the user's email has been confirmed /// /// /// /// public virtual async Task IsEmailConfirmedAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var store = GetEmailStore(); if (user == null) { throw new ArgumentNullException("user"); } return await store.GetEmailConfirmedAsync(user, cancellationToken); } // IUserPhoneNumberStore methods internal IUserPhoneNumberStore GetPhoneNumberStore() { var cast = Store as IUserPhoneNumberStore; if (cast == null) { throw new NotSupportedException(Resources.StoreNotIUserPhoneNumberStore); } return cast; } /// /// Get a user's phoneNumber /// /// /// /// public virtual async Task GetPhoneNumberAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var store = GetPhoneNumberStore(); if (user == null) { throw new ArgumentNullException("user"); } return await store.GetPhoneNumberAsync(user, cancellationToken); } /// /// Set a user's phoneNumber /// /// /// /// /// public virtual async Task SetPhoneNumberAsync(TUser user, string phoneNumber, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var store = GetPhoneNumberStore(); if (user == null) { throw new ArgumentNullException("user"); } await store.SetPhoneNumberAsync(user, phoneNumber, cancellationToken); await store.SetPhoneNumberConfirmedAsync(user, false, cancellationToken); await UpdateSecurityStampInternal(user, cancellationToken); return await UpdateAsync(user, cancellationToken); } /// /// Set a user's phoneNumber with the verification token /// /// /// /// /// /// public virtual async Task ChangePhoneNumberAsync(TUser user, string phoneNumber, string token, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var store = GetPhoneNumberStore(); if (user == null) { throw new ArgumentNullException("user"); } if (!await VerifyChangePhoneNumberTokenAsync(user, token, phoneNumber, cancellationToken)) { return IdentityResult.Failed(Resources.InvalidToken); } await store.SetPhoneNumberAsync(user, phoneNumber, cancellationToken); await store.SetPhoneNumberConfirmedAsync(user, true, cancellationToken); await UpdateSecurityStampInternal(user, cancellationToken); return await UpdateAsync(user, cancellationToken); } /// /// Returns true if the user's phone number has been confirmed /// /// /// /// public virtual async Task IsPhoneNumberConfirmedAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var store = GetPhoneNumberStore(); if (user == null) { throw new ArgumentNullException("user"); } return await store.GetPhoneNumberConfirmedAsync(user, cancellationToken); } // Two factor APIS internal async Task CreateSecurityTokenAsync(TUser user, CancellationToken cancellationToken) { return new SecurityToken(Encoding.Unicode.GetBytes(await GetSecurityStampAsync(user, cancellationToken))); } /// /// Get a phone number code for a user and phone number /// /// /// /// public virtual async Task GenerateChangePhoneNumberTokenAsync(TUser user, string phoneNumber, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); return Rfc6238AuthenticationService.GenerateCode( await CreateSecurityTokenAsync(user, cancellationToken), phoneNumber) .ToString(CultureInfo.InvariantCulture); } /// /// Verify a phone number code for a specific user and phone number /// /// /// /// /// public virtual async Task VerifyChangePhoneNumberTokenAsync(TUser user, string token, string phoneNumber, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var securityToken = await CreateSecurityTokenAsync(user, cancellationToken); int code; if (securityToken != null && Int32.TryParse(token, out code)) { return Rfc6238AuthenticationService.ValidateCode(securityToken, code, phoneNumber); } return false; } /// /// Verify a user token with the specified purpose /// /// /// /// /// /// public virtual async Task VerifyUserTokenAsync(TUser user, string tokenProvider, string purpose, string token, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); if (user == null) { throw new ArgumentNullException("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 return await _tokenProviders[tokenProvider].ValidateAsync(purpose, token, this, user, cancellationToken); } /// /// Get a user token for a specific purpose /// /// /// /// /// public virtual async Task GenerateUserTokenAsync(TUser user, string tokenProvider, string purpose, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); if (user == null) { throw new ArgumentNullException("user"); } if (tokenProvider == null) { throw new ArgumentNullException(nameof(tokenProvider)); } if (!_tokenProviders.ContainsKey(tokenProvider)) { throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, Resources.NoTokenProvider, tokenProvider)); } return await _tokenProviders[tokenProvider].GenerateAsync(purpose, this, user, cancellationToken); } /// /// Register a user token provider /// /// /// public virtual void RegisterTokenProvider(IUserTokenProvider provider) { ThrowIfDisposed(); if (provider == null) { throw new ArgumentNullException("provider"); } _tokenProviders[provider.Name] = provider; } /// /// Returns a list of valid two factor providers for a user /// /// /// /// public virtual async Task> GetValidTwoFactorProvidersAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); if (user == null) { throw new ArgumentNullException("user"); } var results = new List(); foreach (var f in _tokenProviders) { if (await f.Value.CanGenerateTwoFactorTokenAsync(this, user, cancellationToken)) { results.Add(f.Key); } } return results; } /// /// Verify a user token with the specified provider /// /// /// /// /// /// public virtual async Task VerifyTwoFactorTokenAsync(TUser user, string tokenProvider, string token, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); if (user == null) { throw new ArgumentNullException("user"); } if (!_tokenProviders.ContainsKey(tokenProvider)) { throw new NotSupportedException(String.Format(CultureInfo.CurrentCulture, Resources.NoTokenProvider, tokenProvider)); } // Make sure the token is valid return await _tokenProviders[tokenProvider].ValidateAsync("TwoFactor", token, this, user, cancellationToken); } /// /// Get a user token for a specific user factor provider /// /// /// /// /// public virtual async Task GenerateTwoFactorTokenAsync(TUser user, string tokenProvider, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); if (user == null) { throw new ArgumentNullException("user"); } if (!_tokenProviders.ContainsKey(tokenProvider)) { throw new NotSupportedException(String.Format(CultureInfo.CurrentCulture, Resources.NoTokenProvider, tokenProvider)); } return await _tokenProviders[tokenProvider].GenerateAsync("TwoFactor", this, user, cancellationToken); } /// /// Notify a user with a token from a specific user factor provider /// /// /// /// /// /// public virtual async Task NotifyTwoFactorTokenAsync(TUser user, string tokenProvider, string token, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); if (user == null) { throw new ArgumentNullException("user"); } if (tokenProvider == null) { throw new ArgumentNullException(nameof(tokenProvider)); } if (!_tokenProviders.ContainsKey(tokenProvider)) { throw new NotSupportedException(String.Format(CultureInfo.CurrentCulture, Resources.NoTokenProvider, tokenProvider)); } await _tokenProviders[tokenProvider].NotifyAsync(token, this, user, cancellationToken); return IdentityResult.Success; } // IUserFactorStore methods internal IUserTwoFactorStore GetUserTwoFactorStore() { var cast = Store as IUserTwoFactorStore; if (cast == null) { throw new NotSupportedException(Resources.StoreNotIUserTwoFactorStore); } return cast; } /// /// Get a user's two factor provider /// /// /// /// public virtual async Task GetTwoFactorEnabledAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var store = GetUserTwoFactorStore(); if (user == null) { throw new ArgumentNullException("user"); } return await store.GetTwoFactorEnabledAsync(user, cancellationToken); } /// /// Set whether a user has two factor enabled or not /// /// /// /// /// public virtual async Task SetTwoFactorEnabledAsync(TUser user, bool enabled, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var store = GetUserTwoFactorStore(); if (user == null) { throw new ArgumentNullException("user"); } await store.SetTwoFactorEnabledAsync(user, enabled, cancellationToken); await UpdateSecurityStampInternal(user, cancellationToken); return await UpdateAsync(user, cancellationToken); } // SMS/Email methods /// /// Send an email to the user /// /// /// /// /// /// public virtual async Task SendEmailAsync(TUser user, string subject, string body, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); if (user == null) { throw new ArgumentNullException("user"); } if (EmailService != null) { var msg = new IdentityMessage { Destination = await GetEmailAsync(user, cancellationToken), Subject = subject, Body = body, }; await EmailService.SendAsync(msg, cancellationToken); } } /// /// Send a user a sms message /// /// /// /// /// public virtual async Task SendSmsAsync(TUser user, string message, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); if (user == null) { throw new ArgumentNullException("user"); } if (SmsService != null) { var msg = new IdentityMessage { Destination = await GetPhoneNumberAsync(user, cancellationToken), Body = message }; await SmsService.SendAsync(msg, cancellationToken); } } // IUserLockoutStore methods internal IUserLockoutStore GetUserLockoutStore() { var cast = Store as IUserLockoutStore; if (cast == null) { throw new NotSupportedException(Resources.StoreNotIUserLockoutStore); } return cast; } /// /// Returns true if the user is locked out /// /// /// /// public virtual async Task IsLockedOutAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var store = GetUserLockoutStore(); if (user == null) { throw new ArgumentNullException("user"); } if (!await store.GetLockoutEnabledAsync(user, cancellationToken)) { return false; } var lockoutTime = await store.GetLockoutEndDateAsync(user, cancellationToken).ConfigureAwait((false)); return lockoutTime >= DateTimeOffset.UtcNow; } /// /// Sets whether the user allows lockout /// /// /// /// /// public virtual async Task SetLockoutEnabledAsync(TUser user, bool enabled, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var store = GetUserLockoutStore(); if (user == null) { throw new ArgumentNullException("user"); } await store.SetLockoutEnabledAsync(user, enabled, cancellationToken); return await UpdateAsync(user, cancellationToken); } /// /// Returns whether the user allows lockout /// /// /// /// public virtual async Task GetLockoutEnabledAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var store = GetUserLockoutStore(); if (user == null) { throw new ArgumentNullException("user"); } return await store.GetLockoutEnabledAsync(user, cancellationToken); } /// /// Returns the user lockout end date /// /// /// /// public virtual async Task GetLockoutEndDateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var store = GetUserLockoutStore(); if (user == null) { throw new ArgumentNullException("user"); } return await store.GetLockoutEndDateAsync(user, cancellationToken); } /// /// Sets the user lockout end date /// /// /// /// /// public virtual async Task SetLockoutEndDateAsync(TUser user, DateTimeOffset lockoutEnd, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var store = GetUserLockoutStore(); if (user == null) { throw new ArgumentNullException("user"); } if (!await store.GetLockoutEnabledAsync(user, cancellationToken).ConfigureAwait((false))) { return IdentityResult.Failed(Resources.LockoutNotEnabled); } await store.SetLockoutEndDateAsync(user, lockoutEnd, cancellationToken); return await UpdateAsync(user, cancellationToken); } /// /// Increments the access failed count for the user and if the failed access account is greater than or equal /// to the MaxFailedAccessAttempsBeforeLockout, the user will be locked out for the next /// DefaultAccountLockoutTimeSpan and the AccessFailedCount will be reset to 0. /// /// /// /// public virtual async Task AccessFailedAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var store = GetUserLockoutStore(); if (user == null) { throw new ArgumentNullException("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 UpdateAsync(user, cancellationToken); } await store.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow.Add(Options.Lockout.DefaultLockoutTimeSpan), cancellationToken); await store.ResetAccessFailedCountAsync(user, cancellationToken); return await UpdateAsync(user, cancellationToken); } /// /// Resets the access failed count for the user to 0 /// /// /// /// public virtual async Task ResetAccessFailedCountAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var store = GetUserLockoutStore(); if (user == null) { throw new ArgumentNullException("user"); } await store.ResetAccessFailedCountAsync(user, cancellationToken); return await UpdateAsync(user, cancellationToken); } /// /// Returns the number of failed access attempts for the user /// /// /// /// public virtual async Task GetAccessFailedCountAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); var store = GetUserLockoutStore(); if (user == null) { throw new ArgumentNullException("user"); } return await store.GetAccessFailedCountAsync(user, cancellationToken); } private void ThrowIfDisposed() { if (_disposed) { throw new ObjectDisposedException(GetType().Name); } } /// /// When disposing, actually dipose the store context /// /// protected virtual void Dispose(bool disposing) { if (disposing && !_disposed) { Store.Dispose(); _disposed = true; } } } }