using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using Microsoft.AspNet.DependencyInjection; using System.Security.Claims; using System.Text; using System.Threading.Tasks; namespace Microsoft.AspNet.Identity { /// /// Exposes user related api which will automatically save changes to the UserStore /// /// /// public class UserManager : IDisposable where TUser : class, IUser where TKey : IEquatable { private readonly Dictionary> _factors = new Dictionary>(); private IClaimsIdentityFactory _claimsFactory; private TimeSpan _defaultLockout = TimeSpan.Zero; private bool _disposed; private IPasswordHasher _passwordHasher; /// /// Constructor which takes a service provider to find the default interfaces to hook up /// /// public UserManager(IServiceProvider serviceProvider) { if (serviceProvider == null) { throw new ArgumentNullException("serviceProvider"); } Store = serviceProvider.GetService>(); ClaimsIdentityFactory = serviceProvider.GetService>(); PasswordHasher = serviceProvider.GetService(); UserValidator = serviceProvider.GetService>(); PasswordValidator = serviceProvider.GetService(); // TODO: validator interfaces, and maybe each optional store as well? Email and SMS services? } /// /// Constructor /// /// The IUserStore is responsible for commiting changes via the UpdateAsync/CreateAsync methods public UserManager(IUserStore store) { if (store == null) { throw new ArgumentNullException("store"); } Store = store; UserValidator = new UserValidator(this); //PasswordHasher = new PasswordHasher(); //ClaimsIdentityFactory = new ClaimsIdentityFactory(); } /// /// 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 create claims identities from users /// public IClaimsIdentityFactory ClaimsIdentityFactory { get { ThrowIfDisposed(); return _claimsFactory; } set { ThrowIfDisposed(); if (value == null) { throw new ArgumentNullException("value"); } _claimsFactory = value; } } /// /// Used to send email /// public IIdentityMessageService EmailService { get; set; } /// /// Used to send a sms message /// public IIdentityMessageService SmsService { get; set; } /// /// Used for generating ResetPassword and Confirmation Tokens /// public IUserTokenProvider UserTokenProvider { get; set; } /// /// If true, will enable user lockout when users are created /// public bool UserLockoutEnabledByDefault { get; set; } /// /// Number of access attempts allowed for a user before lockout (if enabled) /// public int MaxFailedAccessAttemptsBeforeLockout { get; set; } /// /// Default amount of time an user is locked out for after MaxFailedAccessAttempsBeforeLockout is reached /// public TimeSpan DefaultAccountLockoutTimeSpan { get { return _defaultLockout; } set { _defaultLockout = 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 false; //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; } } /// /// Dictionary mapping user two factor providers /// public IDictionary> TwoFactorProviders { get { return _factors; } } /// /// Dispose the store context /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Creates a ClaimsIdentity representing the user /// /// /// /// public virtual Task CreateIdentity(TUser user, string authenticationType) { ThrowIfDisposed(); if (user == null) { throw new ArgumentNullException("user"); } return ClaimsIdentityFactory.Create(this, user, authenticationType); } private async Task ValidateUserInternal(TUser user) { return (UserValidator == null) ? IdentityResult.Success : await UserValidator.Validate(user).ConfigureAwait(false); } /// /// Create a user with no password /// /// /// public virtual async Task Create(TUser user) { ThrowIfDisposed(); await UpdateSecurityStampInternal(user).ConfigureAwait(false); var result = await ValidateUserInternal(user); if (!result.Succeeded) { return result; } if (UserLockoutEnabledByDefault && SupportsUserLockout) { await GetUserLockoutStore().SetLockoutEnabled(user, true).ConfigureAwait(false); } await Store.Create(user).ConfigureAwait(false); return IdentityResult.Success; } /// /// Update a user /// /// /// public virtual async Task Update(TUser user) { ThrowIfDisposed(); if (user == null) { throw new ArgumentNullException("user"); } var result = await ValidateUserInternal(user); if (!result.Succeeded) { return result; } await Store.Update(user).ConfigureAwait(false); return IdentityResult.Success; } /// /// Delete a user /// /// /// public virtual async Task Delete(TUser user) { ThrowIfDisposed(); await Store.Delete(user).ConfigureAwait(false); return IdentityResult.Success; } /// /// Find a user by id /// /// /// public virtual Task FindById(TKey userId) { ThrowIfDisposed(); return Store.FindById(userId); } /// /// Find a user by name /// /// /// public virtual Task FindByName(string userName) { ThrowIfDisposed(); if (userName == null) { throw new ArgumentNullException("userName"); } return Store.FindByName(userName); } // 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 (if one is provided) /// /// /// /// public virtual async Task Create(TUser user, string password) { 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).ConfigureAwait(false); if (!result.Succeeded) { return result; } return await Create(user).ConfigureAwait(false); } /// /// Return a user with the specified username and password or null if there is no match. /// /// /// /// public virtual async Task Find(string userName, string password) { ThrowIfDisposed(); var user = await FindByName(userName).ConfigureAwait(false); if (user == null) { return null; } return await CheckPassword(user, password).ConfigureAwait(false) ? user : null; } /// /// Returns true if the password combination is valid for the user /// /// /// /// public virtual async Task CheckPassword(TUser user, string password) { ThrowIfDisposed(); var passwordStore = GetPasswordStore(); if (user == null) { return false; } return await VerifyPassword(passwordStore, user, password).ConfigureAwait(false); } /// /// Returns true if the user has a password /// /// /// public virtual async Task HasPassword(TKey userId) { ThrowIfDisposed(); var passwordStore = GetPasswordStore(); var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } return await passwordStore.HasPassword(user).ConfigureAwait(false); } /// /// Add a user password only if one does not already exist /// /// /// /// public virtual async Task AddPassword(TKey userId, string password) { ThrowIfDisposed(); var passwordStore = GetPasswordStore(); var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } var hash = await passwordStore.GetPasswordHash(user).ConfigureAwait(false); if (hash != null) { return new IdentityResult(Resources.UserAlreadyHasPassword); } var result = await UpdatePasswordInternal(passwordStore, user, password).ConfigureAwait(false); if (!result.Succeeded) { return result; } return await Update(user).ConfigureAwait(false); } /// /// Change a user password /// /// /// /// /// public virtual async Task ChangePassword(TKey userId, string currentPassword, string newPassword) { ThrowIfDisposed(); var passwordStore = GetPasswordStore(); var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } if (await VerifyPassword(passwordStore, user, currentPassword).ConfigureAwait(false)) { var result = await UpdatePasswordInternal(passwordStore, user, newPassword).ConfigureAwait(false); if (!result.Succeeded) { return result; } return await Update(user).ConfigureAwait(false); } return IdentityResult.Failed(Resources.PasswordMismatch); } /// /// Remove a user's password /// /// /// public virtual async Task RemovePassword(TKey userId) { ThrowIfDisposed(); var passwordStore = GetPasswordStore(); var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } await passwordStore.SetPasswordHash(user, null).ConfigureAwait(false); await UpdateSecurityStampInternal(user).ConfigureAwait(false); return await Update(user).ConfigureAwait(false); } internal async Task UpdatePasswordInternal(IUserPasswordStore passwordStore, TUser user, string newPassword) { if (PasswordValidator != null) { var result = await PasswordValidator.Validate(newPassword).ConfigureAwait(false); if (!result.Succeeded) { return result; } } await passwordStore.SetPasswordHash(user, PasswordHasher.HashPassword(newPassword)).ConfigureAwait(false); await UpdateSecurityStampInternal(user).ConfigureAwait(false); return IdentityResult.Success; } /// /// By default, retrieves the hashed password from the user store and calls PasswordHasher.VerifyHashPassword /// /// /// /// /// protected virtual async Task VerifyPassword(IUserPasswordStore store, TUser user, string password) { var hash = await store.GetPasswordHash(user).ConfigureAwait(false); return PasswordHasher.VerifyHashedPassword(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 GetSecurityStamp(TKey userId) { ThrowIfDisposed(); var securityStore = GetSecurityStore(); var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } return await securityStore.GetSecurityStamp(user).ConfigureAwait(false); } /// /// Generate a new security stamp for a user, used for SignOutEverywhere functionality /// /// /// public virtual async Task UpdateSecurityStamp(TKey userId) { ThrowIfDisposed(); var securityStore = GetSecurityStore(); var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } await securityStore.SetSecurityStamp(user, NewSecurityStamp()).ConfigureAwait(false); return await Update(user).ConfigureAwait(false); } /// /// Generate a password reset token for the user using the UserTokenProvider /// /// /// public virtual async Task GeneratePasswordResetToken(TKey userId) { ThrowIfDisposed(); return await GenerateUserToken("ResetPassword", userId); } /// /// Reset a user's password using a reset password token /// /// /// /// /// public virtual async Task ResetPassword(TKey userId, string token, string newPassword) { ThrowIfDisposed(); var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } // Make sure the token is valid and the stamp matches if (!await VerifyUserToken(userId, "ResetPassword", token).ConfigureAwait(false)) { return IdentityResult.Failed(Resources.InvalidToken); } var passwordStore = GetPasswordStore(); var result = await UpdatePasswordInternal(passwordStore, user, newPassword).ConfigureAwait(false); if (!result.Succeeded) { return result; } return await Update(user).ConfigureAwait(false); } // Update the security stamp if the store supports it internal async Task UpdateSecurityStampInternal(TUser user) { if (SupportsUserSecurityStamp) { await GetSecurityStore().SetSecurityStamp(user, NewSecurityStamp()).ConfigureAwait(false); } } 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 Find(UserLoginInfo login) { ThrowIfDisposed(); return GetLoginStore().Find(login); } /// /// Remove a user login /// /// /// /// public virtual async Task RemoveLogin(TKey userId, UserLoginInfo login) { ThrowIfDisposed(); var loginStore = GetLoginStore(); if (login == null) { throw new ArgumentNullException("login"); } var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } await loginStore.RemoveLogin(user, login).ConfigureAwait(false); await UpdateSecurityStampInternal(user).ConfigureAwait(false); return await Update(user).ConfigureAwait(false); } /// /// Associate a login with a user /// /// /// /// public virtual async Task AddLogin(TKey userId, UserLoginInfo login) { ThrowIfDisposed(); var loginStore = GetLoginStore(); if (login == null) { throw new ArgumentNullException("login"); } var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } var existingUser = await Find(login).ConfigureAwait(false); if (existingUser != null) { return IdentityResult.Failed(Resources.ExternalLoginExists); } await loginStore.AddLogin(user, login).ConfigureAwait(false); return await Update(user).ConfigureAwait(false); } /// /// Gets the logins for a user. /// /// /// public virtual async Task> GetLogins(TKey userId) { ThrowIfDisposed(); var loginStore = GetLoginStore(); var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } return await loginStore.GetLogins(user).ConfigureAwait(false); } // 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 async Task AddClaim(TKey userId, Claim claim) { ThrowIfDisposed(); var claimStore = GetClaimStore(); if (claim == null) { throw new ArgumentNullException("claim"); } var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } await claimStore.AddClaim(user, claim).ConfigureAwait(false); return await Update(user).ConfigureAwait(false); } /// /// Remove a user claim /// /// /// /// public virtual async Task RemoveClaim(TKey userId, Claim claim) { ThrowIfDisposed(); var claimStore = GetClaimStore(); var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } await claimStore.RemoveClaim(user, claim).ConfigureAwait(false); return await Update(user).ConfigureAwait(false); } /// /// Get a users's claims /// /// /// public virtual async Task> GetClaims(TKey userId) { ThrowIfDisposed(); var claimStore = GetClaimStore(); var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } return await claimStore.GetClaims(user).ConfigureAwait(false); } 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 AddToRole(TKey userId, string role) { ThrowIfDisposed(); var userRoleStore = GetUserRoleStore(); var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } var userRoles = await userRoleStore.GetRoles(user).ConfigureAwait(false); if (userRoles.Contains(role)) { return new IdentityResult(Resources.UserAlreadyInRole); } await userRoleStore.AddToRole(user, role).ConfigureAwait(false); return await Update(user).ConfigureAwait(false); } /// /// Remove a user from a role. /// /// /// /// public virtual async Task RemoveFromRole(TKey userId, string role) { ThrowIfDisposed(); var userRoleStore = GetUserRoleStore(); var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } if (!await userRoleStore.IsInRole(user, role).ConfigureAwait(false)) { return new IdentityResult(Resources.UserNotInRole); } await userRoleStore.RemoveFromRole(user, role).ConfigureAwait(false); return await Update(user).ConfigureAwait(false); } /// /// Returns the roles for the user /// /// /// public virtual async Task> GetRoles(TKey userId) { ThrowIfDisposed(); var userRoleStore = GetUserRoleStore(); var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } return await userRoleStore.GetRoles(user).ConfigureAwait(false); } /// /// Returns true if the user is in the specified role /// /// /// /// public virtual async Task IsInRole(TKey userId, string role) { ThrowIfDisposed(); var userRoleStore = GetUserRoleStore(); var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } return await userRoleStore.IsInRole(user, role).ConfigureAwait(false); } // 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 GetEmail(TKey userId) { ThrowIfDisposed(); var store = GetEmailStore(); var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } return await store.GetEmail(user).ConfigureAwait(false); } /// /// Set a user's email /// /// /// /// public virtual async Task SetEmail(TKey userId, string email) { ThrowIfDisposed(); var store = GetEmailStore(); var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } await store.SetEmail(user, email).ConfigureAwait(false); await store.SetEmailConfirmed(user, false).ConfigureAwait(false); await UpdateSecurityStampInternal(user).ConfigureAwait(false); return await Update(user).ConfigureAwait(false); } /// /// Find a user by his email /// /// /// public virtual Task FindByEmail(string email) { ThrowIfDisposed(); var store = GetEmailStore(); if (email == null) { throw new ArgumentNullException("email"); } return store.FindByEmail(email); } /// /// Get the confirmation token for the user /// /// /// public virtual Task GenerateEmailConfirmationToken(TKey userId) { ThrowIfDisposed(); return GenerateUserToken("Confirmation", userId); } /// /// Confirm the user with confirmation token /// /// /// /// public virtual async Task ConfirmEmail(TKey userId, string token) { ThrowIfDisposed(); var store = GetEmailStore(); var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } if (!await VerifyUserToken(userId, "Confirmation", token)) { return IdentityResult.Failed(Resources.InvalidToken); } await store.SetEmailConfirmed(user, true).ConfigureAwait(false); return await Update(user).ConfigureAwait(false); } /// /// Returns true if the user's email has been confirmed /// /// /// public virtual async Task IsEmailConfirmed(TKey userId) { ThrowIfDisposed(); var store = GetEmailStore(); var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } return await store.GetEmailConfirmed(user).ConfigureAwait(false); } // 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 GetPhoneNumber(TKey userId) { ThrowIfDisposed(); var store = GetPhoneNumberStore(); var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } return await store.GetPhoneNumber(user).ConfigureAwait(false); } /// /// Set a user's phoneNumber /// /// /// /// public virtual async Task SetPhoneNumber(TKey userId, string phoneNumber) { ThrowIfDisposed(); var store = GetPhoneNumberStore(); var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } await store.SetPhoneNumber(user, phoneNumber).ConfigureAwait(false); await store.SetPhoneNumberConfirmed(user, false).ConfigureAwait(false); await UpdateSecurityStampInternal(user).ConfigureAwait(false); return await Update(user).ConfigureAwait(false); } /// /// Set a user's phoneNumber with the verification token /// /// /// /// /// public virtual async Task ChangePhoneNumber(TKey userId, string phoneNumber, string token) { ThrowIfDisposed(); var store = GetPhoneNumberStore(); var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } if (await VerifyChangePhoneNumberToken(userId, token, phoneNumber).ConfigureAwait(false)) { await store.SetPhoneNumber(user, phoneNumber).ConfigureAwait(false); await store.SetPhoneNumberConfirmed(user, true).ConfigureAwait(false); await UpdateSecurityStampInternal(user).ConfigureAwait(false); return await Update(user).ConfigureAwait(false); } return IdentityResult.Failed(Resources.InvalidToken); } /// /// Returns true if the user's phone number has been confirmed /// /// /// public virtual async Task IsPhoneNumberConfirmed(TKey userId) { ThrowIfDisposed(); var store = GetPhoneNumberStore(); var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } return await store.GetPhoneNumberConfirmed(user).ConfigureAwait(false); } // Two factor APIS #if NET45 internal async Task CreateSecurityToken(TKey userId) { return new SecurityToken(Encoding.Unicode.GetBytes(await GetSecurityStamp(userId).ConfigureAwait(false))); } /// /// Get a phone number code for a user and phone number /// /// /// /// public virtual async Task GenerateChangePhoneNumberToken(TKey userId, string phoneNumber) { ThrowIfDisposed(); return Rfc6238AuthenticationService.GenerateCode(await CreateSecurityToken(userId), phoneNumber) .ToString(CultureInfo.InvariantCulture); } #endif /// /// Verify a phone number code for a specific user and phone number /// /// /// /// /// public virtual async Task VerifyChangePhoneNumberToken(TKey userId, string token, string phoneNumber) { ThrowIfDisposed(); #if NET45 var securityToken = await CreateSecurityToken(userId); int code; if (securityToken != null && Int32.TryParse(token, out code)) { return Rfc6238AuthenticationService.ValidateCode(securityToken, code, phoneNumber); } #endif return false; } /// /// Verify a user token with the specified purpose /// /// /// /// /// public virtual async Task VerifyUserToken(TKey userId, string purpose, string token) { ThrowIfDisposed(); if (UserTokenProvider == null) { throw new NotSupportedException(Resources.NoTokenProvider); } var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } // Make sure the token is valid return await UserTokenProvider.Validate(purpose, token, this, user).ConfigureAwait(false); } /// /// Get a user token for a specific purpose /// /// /// /// public virtual async Task GenerateUserToken(string purpose, TKey userId) { ThrowIfDisposed(); if (UserTokenProvider == null) { throw new NotSupportedException(Resources.NoTokenProvider); } var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } return await UserTokenProvider.Generate(purpose, this, user).ConfigureAwait(false); } /// /// Register a user two factor provider /// /// /// public virtual void RegisterTwoFactorProvider(string twoFactorProvider, IUserTokenProvider provider) { ThrowIfDisposed(); if (twoFactorProvider == null) { throw new ArgumentNullException("twoFactorProvider"); } if (provider == null) { throw new ArgumentNullException("provider"); } TwoFactorProviders[twoFactorProvider] = provider; } /// /// Returns a list of valid two factor providers for a user /// /// /// public virtual async Task> GetValidTwoFactorProviders(TKey userId) { ThrowIfDisposed(); var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } var results = new List(); foreach (var f in TwoFactorProviders) { if (await f.Value.IsValidProviderForUser(this, user)) { results.Add(f.Key); } } return results; } /// /// Verify a user token with the specified provider /// /// /// /// /// public virtual async Task VerifyTwoFactorToken(TKey userId, string twoFactorProvider, string token) { ThrowIfDisposed(); var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } if (!_factors.ContainsKey(twoFactorProvider)) { throw new NotSupportedException(String.Format(CultureInfo.CurrentCulture, Resources.NoTwoFactorProvider, twoFactorProvider)); } // Make sure the token is valid var provider = _factors[twoFactorProvider]; return await provider.Validate(twoFactorProvider, token, this, user).ConfigureAwait(false); } /// /// Get a user token for a specific user factor provider /// /// /// /// public virtual async Task GenerateTwoFactorToken(TKey userId, string twoFactorProvider) { ThrowIfDisposed(); var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } if (!_factors.ContainsKey(twoFactorProvider)) { throw new NotSupportedException(String.Format(CultureInfo.CurrentCulture, Resources.NoTwoFactorProvider, twoFactorProvider)); } return await _factors[twoFactorProvider].Generate(twoFactorProvider, this, user).ConfigureAwait(false); } /// /// Notify a user with a token from a specific user factor provider /// /// /// /// /// public virtual async Task NotifyTwoFactorToken(TKey userId, string twoFactorProvider, string token) { ThrowIfDisposed(); var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } if (!_factors.ContainsKey(twoFactorProvider)) { throw new NotSupportedException(String.Format(CultureInfo.CurrentCulture, Resources.NoTwoFactorProvider, twoFactorProvider)); } await _factors[twoFactorProvider].Notify(token, this, user).ConfigureAwait(false); 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 GetTwoFactorEnabled(TKey userId) { ThrowIfDisposed(); var store = GetUserTwoFactorStore(); var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } return await store.GetTwoFactorEnabled(user).ConfigureAwait(false); } /// /// Set whether a user has two factor enabled or not /// /// /// /// public virtual async Task SetTwoFactorEnabled(TKey userId, bool enabled) { ThrowIfDisposed(); var store = GetUserTwoFactorStore(); var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } await store.SetTwoFactorEnabled(user, enabled).ConfigureAwait(false); await UpdateSecurityStampInternal(user).ConfigureAwait(false); return await Update(user).ConfigureAwait(false); } // SMS/Email methods /// /// Send an email to the user /// /// /// /// /// public virtual async Task SendEmail(TKey userId, string subject, string body) { ThrowIfDisposed(); if (EmailService != null) { var msg = new IdentityMessage { Destination = await GetEmail(userId), Subject = subject, Body = body, }; await EmailService.Send(msg); } } /// /// Send a user a sms message /// /// /// /// public virtual async Task SendSms(TKey userId, string message) { ThrowIfDisposed(); if (SmsService != null) { var msg = new IdentityMessage { Destination = await GetPhoneNumber(userId), Body = message }; await SmsService.Send(msg); } } // 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 IsLockedOut(TKey userId) { ThrowIfDisposed(); var store = GetUserLockoutStore(); var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } if (await store.GetLockoutEnabled(user).ConfigureAwait(false)) { var lockoutTime = await store.GetLockoutEndDate(user).ConfigureAwait((false)); return lockoutTime >= DateTimeOffset.UtcNow; } return false; } /// /// Sets whether the user allows lockout /// /// /// /// public virtual async Task SetLockoutEnabled(TKey userId, bool enabled) { ThrowIfDisposed(); var store = GetUserLockoutStore(); var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } await store.SetLockoutEnabled(user, enabled).ConfigureAwait(false); return await Update(user).ConfigureAwait(false); } /// /// Returns whether the user allows lockout /// /// /// public virtual async Task GetLockoutEnabled(TKey userId) { ThrowIfDisposed(); var store = GetUserLockoutStore(); var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } return await store.GetLockoutEnabled(user).ConfigureAwait(false); } /// /// Returns the user lockout end date /// /// /// public virtual async Task GetLockoutEndDate(TKey userId) { ThrowIfDisposed(); var store = GetUserLockoutStore(); var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } return await store.GetLockoutEndDate(user).ConfigureAwait(false); } /// /// Sets the user lockout end date /// /// /// /// public virtual async Task SetLockoutEndDate(TKey userId, DateTimeOffset lockoutEnd) { ThrowIfDisposed(); var store = GetUserLockoutStore(); var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } if (!await store.GetLockoutEnabled(user).ConfigureAwait((false))) { return IdentityResult.Failed(Resources.LockoutNotEnabled); } await store.SetLockoutEndDate(user, lockoutEnd).ConfigureAwait(false); return await Update(user).ConfigureAwait(false); } /// /// 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 AccessFailed(TKey userId) { ThrowIfDisposed(); var store = GetUserLockoutStore(); var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } // If this puts the user over the threshold for lockout, lock them out and reset the access failed count var count = await store.IncrementAccessFailedCount(user).ConfigureAwait(false); if (count >= MaxFailedAccessAttemptsBeforeLockout) { await store.SetLockoutEndDate(user, DateTimeOffset.UtcNow.Add(DefaultAccountLockoutTimeSpan)) .ConfigureAwait(false); await store.ResetAccessFailedCount(user).ConfigureAwait(false); } return await Update(user).ConfigureAwait(false); } /// /// Resets the access failed count for the user to 0 /// /// /// public virtual async Task ResetAccessFailedCount(TKey userId) { ThrowIfDisposed(); var store = GetUserLockoutStore(); var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } await store.ResetAccessFailedCount(user).ConfigureAwait(false); return await Update(user).ConfigureAwait(false); } /// /// Returns the number of failed access attempts for the user /// /// /// public virtual async Task GetAccessFailedCount(TKey userId) { ThrowIfDisposed(); var store = GetUserLockoutStore(); var user = await FindById(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound, userId)); } return await store.GetAccessFailedCount(user).ConfigureAwait(false); } 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; } } } }