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