diff --git a/Identity.sln b/Identity.sln
index f5ad78907b..dd2aeb9d02 100644
--- a/Identity.sln
+++ b/Identity.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2013
-VisualStudioVersion = 12.0.21126.0
+VisualStudioVersion = 12.0.21005.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{0F647068-6602-4E24-B1DC-8ED91481A50A}"
EndProject
@@ -46,11 +46,11 @@ Global
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
- {77CEDA6C-A833-455D-8357-649BFD944724} = {0F647068-6602-4E24-B1DC-8ED91481A50A}
{F6B0C0E9-C346-49D0-B583-95B6CE04BB1B} = {0F647068-6602-4E24-B1DC-8ED91481A50A}
- {6211450F-FFB8-431F-84E2-9A7620875260} = {77CEDA6C-A833-455D-8357-649BFD944724}
- {D32483A4-B617-480C-81E6-49CD596B9A34} = {77CEDA6C-A833-455D-8357-649BFD944724}
+ {77CEDA6C-A833-455D-8357-649BFD944724} = {0F647068-6602-4E24-B1DC-8ED91481A50A}
{B72401D7-47F6-4A98-89D5-CCBFEFC5B2B8} = {F6B0C0E9-C346-49D0-B583-95B6CE04BB1B}
{E52361C9-1F0B-4229-86A0-E5C7C12A5429} = {F6B0C0E9-C346-49D0-B583-95B6CE04BB1B}
+ {6211450F-FFB8-431F-84E2-9A7620875260} = {77CEDA6C-A833-455D-8357-649BFD944724}
+ {D32483A4-B617-480C-81E6-49CD596B9A34} = {77CEDA6C-A833-455D-8357-649BFD944724}
EndGlobalSection
EndGlobal
diff --git a/src/Microsoft.AspNet.Identity/IClaimsIdentityFactory.cs b/src/Microsoft.AspNet.Identity/IClaimsIdentityFactory.cs
new file mode 100644
index 0000000000..bd5562df13
--- /dev/null
+++ b/src/Microsoft.AspNet.Identity/IClaimsIdentityFactory.cs
@@ -0,0 +1,29 @@
+using System;
+#if NET45
+using System.Security.Claims;
+#endif
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNet.Identity
+{
+ ///
+ /// Interface for creating a ClaimsIdentity from an IUser
+ ///
+ ///
+ ///
+ public interface IClaimsIdentityFactory
+ where TUser : class, IUser
+ where TKey : IEquatable
+ {
+#if NET45
+ ///
+ /// Create a ClaimsIdentity from an user using a UserManager
+ ///
+ ///
+ ///
+ ///
+ ///
+ Task Create(UserManager manager, TUser user, string authenticationType);
+#endif
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/IIdentityMessageService.cs b/src/Microsoft.AspNet.Identity/IIdentityMessageService.cs
new file mode 100644
index 0000000000..5861580a68
--- /dev/null
+++ b/src/Microsoft.AspNet.Identity/IIdentityMessageService.cs
@@ -0,0 +1,38 @@
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNet.Identity
+{
+ ///
+ /// Expose a way to send messages (email/txt)
+ ///
+ public interface IIdentityMessageService
+ {
+ ///
+ /// This method should send the message
+ ///
+ ///
+ ///
+ Task Send(IdentityMessage message);
+ }
+
+ ///
+ /// Represents a message
+ ///
+ public class IdentityMessage
+ {
+ ///
+ /// Destination, i.e. To email, or SMS phone number
+ ///
+ public virtual string Destination { get; set; }
+
+ ///
+ /// Subject
+ ///
+ public virtual string Subject { get; set; }
+
+ ///
+ /// Message contents
+ ///
+ public virtual string Body { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/IIdentityValidator.cs b/src/Microsoft.AspNet.Identity/IIdentityValidator.cs
new file mode 100644
index 0000000000..a0ddf1e319
--- /dev/null
+++ b/src/Microsoft.AspNet.Identity/IIdentityValidator.cs
@@ -0,0 +1,18 @@
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNet.Identity
+{
+ ///
+ /// Used to validate an item
+ ///
+ ///
+ public interface IIdentityValidator
+ {
+ ///
+ /// Validate the item
+ ///
+ ///
+ ///
+ Task Validate(T item);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/IPasswordHasher.cs b/src/Microsoft.AspNet.Identity/IPasswordHasher.cs
new file mode 100644
index 0000000000..713fb44134
--- /dev/null
+++ b/src/Microsoft.AspNet.Identity/IPasswordHasher.cs
@@ -0,0 +1,23 @@
+namespace Microsoft.AspNet.Identity
+{
+ ///
+ /// Abstraction for password hashing methods
+ ///
+ public interface IPasswordHasher
+ {
+ ///
+ /// Hash a password
+ ///
+ ///
+ ///
+ string HashPassword(string password);
+
+ ///
+ /// Verify that a password matches the hashed password
+ ///
+ ///
+ ///
+ ///
+ PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string providedPassword);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/IQueryableRoleStore.cs b/src/Microsoft.AspNet.Identity/IQueryableRoleStore.cs
new file mode 100644
index 0000000000..93c30f3a92
--- /dev/null
+++ b/src/Microsoft.AspNet.Identity/IQueryableRoleStore.cs
@@ -0,0 +1,25 @@
+using System.Linq;
+
+namespace Microsoft.AspNet.Identity
+{
+ ///
+ /// Interface that exposes an IQueryable roles
+ ///
+ ///
+ public interface IQueryableRoleStore : IQueryableRoleStore where TRole : IRole
+ {
+ }
+
+ ///
+ /// Interface that exposes an IQueryable roles
+ ///
+ ///
+ ///
+ public interface IQueryableRoleStore : IRoleStore where TRole : IRole
+ {
+ ///
+ /// IQueryable users
+ ///
+ IQueryable Roles { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/IQueryableUserStore.cs b/src/Microsoft.AspNet.Identity/IQueryableUserStore.cs
new file mode 100644
index 0000000000..d8e26b7e6b
--- /dev/null
+++ b/src/Microsoft.AspNet.Identity/IQueryableUserStore.cs
@@ -0,0 +1,25 @@
+using System.Linq;
+
+namespace Microsoft.AspNet.Identity
+{
+ ///
+ /// Interface that exposes an IQueryable users
+ ///
+ ///
+ public interface IQueryableUserStore : IQueryableUserStore where TUser : class, IUser
+ {
+ }
+
+ ///
+ /// Interface that exposes an IQueryable users
+ ///
+ ///
+ ///
+ public interface IQueryableUserStore : IUserStore where TUser : class, IUser
+ {
+ ///
+ /// IQueryable users
+ ///
+ IQueryable Users { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/IRole.cs b/src/Microsoft.AspNet.Identity/IRole.cs
new file mode 100644
index 0000000000..8cebb0189c
--- /dev/null
+++ b/src/Microsoft.AspNet.Identity/IRole.cs
@@ -0,0 +1,19 @@
+namespace Microsoft.AspNet.Identity
+{
+ ///
+ /// Mimimal set of data needed to persist role data
+ ///
+ ///
+ public interface IRole
+ {
+ ///
+ /// Id of the role
+ ///
+ TKey Id { get; }
+
+ ///
+ /// Name of the role
+ ///
+ string Name { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/IRoleStore.cs b/src/Microsoft.AspNet.Identity/IRoleStore.cs
new file mode 100644
index 0000000000..8286b56b46
--- /dev/null
+++ b/src/Microsoft.AspNet.Identity/IRoleStore.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNet.Identity
+{
+ ///
+ /// Interface that exposes basic role management
+ ///
+ ///
+ ///
+ public interface IRoleStore : IDisposable where TRole : IRole
+ {
+ ///
+ /// Insert a new role
+ ///
+ ///
+ ///
+ Task Create(TRole role);
+
+ ///
+ /// Update a role
+ ///
+ ///
+ ///
+ Task Update(TRole role);
+
+ ///
+ /// Delete a role
+ ///
+ ///
+ ///
+ Task Delete(TRole role);
+
+ ///
+ /// Finds a role by id
+ ///
+ ///
+ ///
+ Task FindById(TKey roleId);
+
+ ///
+ /// Find a role by name
+ ///
+ ///
+ ///
+ Task FindByName(string roleName);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/IUser.cs b/src/Microsoft.AspNet.Identity/IUser.cs
new file mode 100644
index 0000000000..432c76534e
--- /dev/null
+++ b/src/Microsoft.AspNet.Identity/IUser.cs
@@ -0,0 +1,19 @@
+namespace Microsoft.AspNet.Identity
+{
+ ///
+ /// Minimal interface for a user with id and username
+ ///
+ ///
+ public interface IUser
+ {
+ ///
+ /// Unique key for the user
+ ///
+ TKey Id { get; }
+
+ ///
+ /// Unique username
+ ///
+ string UserName { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/IUserClaimStore.cs b/src/Microsoft.AspNet.Identity/IUserClaimStore.cs
new file mode 100644
index 0000000000..b1bffbbc59
--- /dev/null
+++ b/src/Microsoft.AspNet.Identity/IUserClaimStore.cs
@@ -0,0 +1,41 @@
+#if NET45
+
+using System.Collections.Generic;
+using System.Security.Claims;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNet.Identity
+{
+ ///
+ /// Stores user specific claims
+ ///
+ ///
+ ///
+ public interface IUserClaimStore : IUserStore where TUser : class, IUser
+ {
+ ///
+ /// Returns the claims for the user with the issuer set
+ ///
+ ///
+ ///
+ Task> GetClaims(TUser user);
+
+ ///
+ /// Add a new user claim
+ ///
+ ///
+ ///
+ ///
+ Task AddClaim(TUser user, Claim claim);
+
+ ///
+ /// Remove a user claim
+ ///
+ ///
+ ///
+ ///
+ Task RemoveClaim(TUser user, Claim claim);
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/IUserEmailStore.cs b/src/Microsoft.AspNet.Identity/IUserEmailStore.cs
new file mode 100644
index 0000000000..8a631f79cb
--- /dev/null
+++ b/src/Microsoft.AspNet.Identity/IUserEmailStore.cs
@@ -0,0 +1,49 @@
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNet.Identity
+{
+ ///
+ /// Stores a user's email
+ ///
+ ///
+ ///
+ public interface IUserEmailStore : IUserStore where TUser : class, IUser
+ {
+ ///
+ /// Set the user email
+ ///
+ ///
+ ///
+ ///
+ Task SetEmail(TUser user, string email);
+
+ ///
+ /// Get the user email
+ ///
+ ///
+ ///
+ Task GetEmail(TUser user);
+
+ ///
+ /// Returns true if the user email is confirmed
+ ///
+ ///
+ ///
+ Task GetEmailConfirmed(TUser user);
+
+ ///
+ /// Sets whether the user email is confirmed
+ ///
+ ///
+ ///
+ ///
+ Task SetEmailConfirmed(TUser user, bool confirmed);
+
+ ///
+ /// Returns the user associated with this email
+ ///
+ ///
+ ///
+ Task FindByEmail(string email);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/IUserLockoutStore.cs b/src/Microsoft.AspNet.Identity/IUserLockoutStore.cs
new file mode 100644
index 0000000000..813fcc12c0
--- /dev/null
+++ b/src/Microsoft.AspNet.Identity/IUserLockoutStore.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNet.Identity
+{
+ ///
+ /// Stores information which can be used to implement account lockout, including access failures and lockout status
+ ///
+ ///
+ ///
+ public interface IUserLockoutStore : IUserStore where TUser : class, IUser
+ {
+ ///
+ /// Returns the DateTimeOffset that represents the end of a user's lockout, any time in the past should be considered
+ /// not locked out.
+ ///
+ ///
+ ///
+ Task GetLockoutEndDate(TUser user);
+
+ ///
+ /// Locks a user out until the specified end date (set to a past date, to unlock a user)
+ ///
+ ///
+ ///
+ ///
+ Task SetLockoutEndDate(TUser user, DateTimeOffset lockoutEnd);
+
+ ///
+ /// Used to record when an attempt to access the user has failed
+ ///
+ ///
+ ///
+ Task IncrementAccessFailedCount(TUser user);
+
+ ///
+ /// Used to reset the account access count, typically after the account is successfully accessed
+ ///
+ ///
+ ///
+ Task ResetAccessFailedCount(TUser user);
+
+ ///
+ /// Returns the current number of failed access attempts. This number usually will be reset whenever the password is
+ /// verified or the account is locked out.
+ ///
+ ///
+ ///
+ Task GetAccessFailedCount(TUser user);
+
+ ///
+ /// Returns whether the user can be locked out.
+ ///
+ ///
+ ///
+ Task GetLockoutEnabled(TUser user);
+
+ ///
+ /// Sets whether the user can be locked out.
+ ///
+ ///
+ ///
+ ///
+ Task SetLockoutEnabled(TUser user, bool enabled);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/IUserLoginStore.cs b/src/Microsoft.AspNet.Identity/IUserLoginStore.cs
new file mode 100644
index 0000000000..1bb4c1b0f8
--- /dev/null
+++ b/src/Microsoft.AspNet.Identity/IUserLoginStore.cs
@@ -0,0 +1,42 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNet.Identity
+{
+ ///
+ /// Interface that maps users to login providers, i.e. Google, Facebook, Twitter, Microsoft
+ ///
+ ///
+ ///
+ public interface IUserLoginStore : IUserStore where TUser : class, IUser
+ {
+ ///
+ /// Adds a user login with the specified provider and key
+ ///
+ ///
+ ///
+ ///
+ Task AddLogin(TUser user, UserLoginInfo login);
+
+ ///
+ /// Removes the user login with the specified combination if it exists, returns true if found and removed
+ ///
+ ///
+ ///
+ ///
+ Task RemoveLogin(TUser user, UserLoginInfo login);
+
+ ///
+ /// Returns the linked accounts for this user
+ ///
+ ///
+ ///
+ Task> GetLogins(TUser user);
+
+ ///
+ /// Returns the user associated with this login
+ ///
+ ///
+ Task Find(UserLoginInfo login);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/IUserPasswordStore.cs b/src/Microsoft.AspNet.Identity/IUserPasswordStore.cs
new file mode 100644
index 0000000000..ce4bccb8cc
--- /dev/null
+++ b/src/Microsoft.AspNet.Identity/IUserPasswordStore.cs
@@ -0,0 +1,34 @@
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNet.Identity
+{
+ ///
+ /// Stores a user's password hash
+ ///
+ ///
+ ///
+ public interface IUserPasswordStore : IUserStore where TUser : class, IUser
+ {
+ ///
+ /// Set the user password hash
+ ///
+ ///
+ ///
+ ///
+ Task SetPasswordHash(TUser user, string passwordHash);
+
+ ///
+ /// Get the user password hash
+ ///
+ ///
+ ///
+ Task GetPasswordHash(TUser user);
+
+ ///
+ /// Returns true if a user has a password set
+ ///
+ ///
+ ///
+ Task HasPassword(TUser user);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/IUserPhoneNumberStore.cs b/src/Microsoft.AspNet.Identity/IUserPhoneNumberStore.cs
new file mode 100644
index 0000000000..c46e681cde
--- /dev/null
+++ b/src/Microsoft.AspNet.Identity/IUserPhoneNumberStore.cs
@@ -0,0 +1,42 @@
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNet.Identity
+{
+ ///
+ /// Stores a user's phoneNumber
+ ///
+ ///
+ ///
+ public interface IUserPhoneNumberStore : IUserStore where TUser : class, IUser
+ {
+ ///
+ /// Set the user PhoneNumber
+ ///
+ ///
+ ///
+ ///
+ Task SetPhoneNumber(TUser user, string phoneNumber);
+
+ ///
+ /// Get the user phoneNumber
+ ///
+ ///
+ ///
+ Task GetPhoneNumber(TUser user);
+
+ ///
+ /// Returns true if the user phone number is confirmed
+ ///
+ ///
+ ///
+ Task GetPhoneNumberConfirmed(TUser user);
+
+ ///
+ /// Sets whether the user phone number is confirmed
+ ///
+ ///
+ ///
+ ///
+ Task SetPhoneNumberConfirmed(TUser user, bool confirmed);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/IUserRoleStore.cs b/src/Microsoft.AspNet.Identity/IUserRoleStore.cs
new file mode 100644
index 0000000000..f2a356f121
--- /dev/null
+++ b/src/Microsoft.AspNet.Identity/IUserRoleStore.cs
@@ -0,0 +1,44 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNet.Identity
+{
+ ///
+ /// Interface that maps users to their roles
+ ///
+ ///
+ ///
+ public interface IUserRoleStore : IUserStore where TUser : class, IUser
+ {
+ ///
+ /// Adds a user to role
+ ///
+ ///
+ ///
+ ///
+ Task AddToRole(TUser user, string roleName);
+
+ ///
+ /// Removes the role for the user
+ ///
+ ///
+ ///
+ ///
+ Task RemoveFromRole(TUser user, string roleName);
+
+ ///
+ /// Returns the roles for this user
+ ///
+ ///
+ ///
+ Task> GetRoles(TUser user);
+
+ ///
+ /// Returns true if a user is in a role
+ ///
+ ///
+ ///
+ ///
+ Task IsInRole(TUser user, string roleName);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/IUserSecurityStampStore.cs b/src/Microsoft.AspNet.Identity/IUserSecurityStampStore.cs
new file mode 100644
index 0000000000..5dbdd38f81
--- /dev/null
+++ b/src/Microsoft.AspNet.Identity/IUserSecurityStampStore.cs
@@ -0,0 +1,27 @@
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNet.Identity
+{
+ ///
+ /// Stores a user's security stamp
+ ///
+ ///
+ ///
+ public interface IUserSecurityStampStore : IUserStore where TUser : class, IUser
+ {
+ ///
+ /// Set the security stamp for the user
+ ///
+ ///
+ ///
+ ///
+ Task SetSecurityStamp(TUser user, string stamp);
+
+ ///
+ /// Get the user security stamp
+ ///
+ ///
+ ///
+ Task GetSecurityStamp(TUser user);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/IUserStore.cs b/src/Microsoft.AspNet.Identity/IUserStore.cs
new file mode 100644
index 0000000000..1adc85665b
--- /dev/null
+++ b/src/Microsoft.AspNet.Identity/IUserStore.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNet.Identity
+{
+ ///
+ /// Interface that exposes basic user management apis
+ ///
+ ///
+ ///
+ public interface IUserStore : IDisposable where TUser : class, IUser
+ {
+ ///
+ /// Insert a new user
+ ///
+ ///
+ ///
+ Task Create(TUser user);
+
+ ///
+ /// Update a user
+ ///
+ ///
+ ///
+ Task Update(TUser user);
+
+ ///
+ /// Delete a user
+ ///
+ ///
+ ///
+ Task Delete(TUser user);
+
+ ///
+ /// Finds a user
+ ///
+ ///
+ ///
+ Task FindById(TKey userId);
+
+ ///
+ /// Find a user by name
+ ///
+ ///
+ ///
+ Task FindByName(string userName);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/IUserTokenProvider.cs b/src/Microsoft.AspNet.Identity/IUserTokenProvider.cs
new file mode 100644
index 0000000000..0a665c2342
--- /dev/null
+++ b/src/Microsoft.AspNet.Identity/IUserTokenProvider.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNet.Identity
+{
+ ///
+ /// Interface to generate user tokens
+ ///
+ public interface IUserTokenProvider where TUser : class, IUser where TKey : IEquatable
+ {
+ ///
+ /// Generate a token for a user
+ ///
+ ///
+ ///
+ ///
+ ///
+ Task Generate(string purpose, UserManager manager, TUser user);
+
+ ///
+ /// Validate and unprotect a token, returns null if invalid
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ Task Validate(string purpose, string token, UserManager manager, TUser user);
+
+ ///
+ /// Notifies the user that a token has been generated, i.e. via email or sms, or can no-op
+ ///
+ ///
+ ///
+ ///
+ ///
+ Task Notify(string token, UserManager manager, TUser user);
+
+ ///
+ /// Returns true if provider can be used for this user, i.e. could require a user to have an email
+ ///
+ ///
+ ///
+ ///
+ Task IsValidProviderForUser(UserManager manager, TUser user);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/IUserTwoFactorStore.cs b/src/Microsoft.AspNet.Identity/IUserTwoFactorStore.cs
new file mode 100644
index 0000000000..0ab772403c
--- /dev/null
+++ b/src/Microsoft.AspNet.Identity/IUserTwoFactorStore.cs
@@ -0,0 +1,27 @@
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNet.Identity
+{
+ ///
+ /// Stores whether two factor is enabled for a user
+ ///
+ ///
+ ///
+ public interface IUserTwoFactorStore : IUserStore where TUser : class, IUser
+ {
+ ///
+ /// Sets whether two factor is enabled for the user
+ ///
+ ///
+ ///
+ ///
+ Task SetTwoFactorEnabled(TUser user, bool enabled);
+
+ ///
+ /// Returns whether two factor is enabled for the user
+ ///
+ ///
+ ///
+ Task GetTwoFactorEnabled(TUser user);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/IdentityResult.cs b/src/Microsoft.AspNet.Identity/IdentityResult.cs
new file mode 100644
index 0000000000..3ff9bb4a1b
--- /dev/null
+++ b/src/Microsoft.AspNet.Identity/IdentityResult.cs
@@ -0,0 +1,69 @@
+using System.Collections.Generic;
+
+namespace Microsoft.AspNet.Identity
+{
+ ///
+ /// Represents the result of an identity operation
+ ///
+ public class IdentityResult
+ {
+ private static readonly IdentityResult _success = new IdentityResult(true);
+
+ ///
+ /// Failure constructor that takes error messages
+ ///
+ ///
+ public IdentityResult(params string[] errors) : this((IEnumerable) errors)
+ {
+ }
+
+ ///
+ /// Failure constructor that takes error messages
+ ///
+ ///
+ public IdentityResult(IEnumerable errors)
+ {
+ if (errors == null)
+ {
+ errors = new[] {"Resources.DefaultError"};
+ }
+ Succeeded = false;
+ Errors = errors;
+ }
+
+ private IdentityResult(bool success)
+ {
+ Succeeded = success;
+ Errors = new string[0];
+ }
+
+ ///
+ /// True if the operation was successful
+ ///
+ public bool Succeeded { get; private set; }
+
+ ///
+ /// List of errors
+ ///
+ public IEnumerable Errors { get; private set; }
+
+ ///
+ /// Static success result
+ ///
+ ///
+ public static IdentityResult Success
+ {
+ get { return _success; }
+ }
+
+ ///
+ /// Failed helper method
+ ///
+ ///
+ ///
+ public static IdentityResult Failed(params string[] errors)
+ {
+ return new IdentityResult(errors);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/PasswordVerificationResult.cs b/src/Microsoft.AspNet.Identity/PasswordVerificationResult.cs
new file mode 100644
index 0000000000..93817857d7
--- /dev/null
+++ b/src/Microsoft.AspNet.Identity/PasswordVerificationResult.cs
@@ -0,0 +1,23 @@
+namespace Microsoft.AspNet.Identity
+{
+ ///
+ /// Return result for IPasswordHasher
+ ///
+ public enum PasswordVerificationResult
+ {
+ ///
+ /// Password verification failed
+ ///
+ Failed = 0,
+
+ ///
+ /// Success
+ ///
+ Success = 1,
+
+ ///
+ /// Success but should update and rehash the password
+ ///
+ SuccessRehashNeeded = 2
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/Resources.Designer.cs b/src/Microsoft.AspNet.Identity/Resources.Designer.cs
new file mode 100644
index 0000000000..24c89d1dd1
--- /dev/null
+++ b/src/Microsoft.AspNet.Identity/Resources.Designer.cs
@@ -0,0 +1,382 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.34011
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace Microsoft.AspNet.Identity {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+#if NET45
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.AspNet.Identity.Resources", typeof(Resources).Assembly);
+#else
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.AspNet.Routing.Resources", System.Reflection.IntrospectionExtensions.GetTypeInfo(typeof(Resources)).Assembly);
+#endif
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to An unknown failure has occured..
+ ///
+ internal static string DefaultError {
+ get {
+ return ResourceManager.GetString("DefaultError", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Email '{0}' is already taken..
+ ///
+ internal static string DuplicateEmail {
+ get {
+ return ResourceManager.GetString("DuplicateEmail", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Name {0} is already taken..
+ ///
+ internal static string DuplicateName {
+ get {
+ return ResourceManager.GetString("DuplicateName", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to A user with that external login already exists..
+ ///
+ internal static string ExternalLoginExists {
+ get {
+ return ResourceManager.GetString("ExternalLoginExists", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Email '{0}' is invalid..
+ ///
+ internal static string InvalidEmail {
+ get {
+ return ResourceManager.GetString("InvalidEmail", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Invalid token..
+ ///
+ internal static string InvalidToken {
+ get {
+ return ResourceManager.GetString("InvalidToken", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to User name {0} is invalid, can only contain letters or digits..
+ ///
+ internal static string InvalidUserName {
+ get {
+ return ResourceManager.GetString("InvalidUserName", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Lockout is not enabled for this user..
+ ///
+ internal static string LockoutNotEnabled {
+ get {
+ return ResourceManager.GetString("LockoutNotEnabled", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to No IUserTokenProvider is registered..
+ ///
+ internal static string NoTokenProvider {
+ get {
+ return ResourceManager.GetString("NoTokenProvider", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to No IUserTwoFactorProvider for '{0}' is registered..
+ ///
+ internal static string NoTwoFactorProvider {
+ get {
+ return ResourceManager.GetString("NoTwoFactorProvider", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Incorrect password..
+ ///
+ internal static string PasswordMismatch {
+ get {
+ return ResourceManager.GetString("PasswordMismatch", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Passwords must have at least one digit ('0'-'9')..
+ ///
+ internal static string PasswordRequireDigit {
+ get {
+ return ResourceManager.GetString("PasswordRequireDigit", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Passwords must have at least one lowercase ('a'-'z')..
+ ///
+ internal static string PasswordRequireLower {
+ get {
+ return ResourceManager.GetString("PasswordRequireLower", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Passwords must have at least one non letter or digit character..
+ ///
+ internal static string PasswordRequireNonLetterOrDigit {
+ get {
+ return ResourceManager.GetString("PasswordRequireNonLetterOrDigit", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Passwords must have at least one uppercase ('A'-'Z')..
+ ///
+ internal static string PasswordRequireUpper {
+ get {
+ return ResourceManager.GetString("PasswordRequireUpper", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Passwords must be at least {0} characters..
+ ///
+ internal static string PasswordTooShort {
+ get {
+ return ResourceManager.GetString("PasswordTooShort", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to {0} cannot be null or empty..
+ ///
+ internal static string PropertyTooShort {
+ get {
+ return ResourceManager.GetString("PropertyTooShort", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Role {0} does not exist..
+ ///
+ internal static string RoleNotFound {
+ get {
+ return ResourceManager.GetString("RoleNotFound", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Store does not implement IQueryableRoleStore<TRole>..
+ ///
+ internal static string StoreNotIQueryableRoleStore {
+ get {
+ return ResourceManager.GetString("StoreNotIQueryableRoleStore", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Store does not implement IQueryableUserStore<TUser>..
+ ///
+ internal static string StoreNotIQueryableUserStore {
+ get {
+ return ResourceManager.GetString("StoreNotIQueryableUserStore", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Store does not implement IUserClaimStore<TUser>..
+ ///
+ internal static string StoreNotIUserClaimStore {
+ get {
+ return ResourceManager.GetString("StoreNotIUserClaimStore", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Store does not implement IUserConfirmationStore<TUser>..
+ ///
+ internal static string StoreNotIUserConfirmationStore {
+ get {
+ return ResourceManager.GetString("StoreNotIUserConfirmationStore", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Store does not implement IUserEmailStore<TUser>..
+ ///
+ internal static string StoreNotIUserEmailStore {
+ get {
+ return ResourceManager.GetString("StoreNotIUserEmailStore", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Store does not implement IUserLockoutStore<TUser>..
+ ///
+ internal static string StoreNotIUserLockoutStore {
+ get {
+ return ResourceManager.GetString("StoreNotIUserLockoutStore", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Store does not implement IUserLoginStore<TUser>..
+ ///
+ internal static string StoreNotIUserLoginStore {
+ get {
+ return ResourceManager.GetString("StoreNotIUserLoginStore", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Store does not implement IUserPasswordStore<TUser>..
+ ///
+ internal static string StoreNotIUserPasswordStore {
+ get {
+ return ResourceManager.GetString("StoreNotIUserPasswordStore", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Store does not implement IUserPhoneNumberStore<TUser>..
+ ///
+ internal static string StoreNotIUserPhoneNumberStore {
+ get {
+ return ResourceManager.GetString("StoreNotIUserPhoneNumberStore", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Store does not implement IUserRoleStore<TUser>..
+ ///
+ internal static string StoreNotIUserRoleStore {
+ get {
+ return ResourceManager.GetString("StoreNotIUserRoleStore", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Store does not implement IUserSecurityStampStore<TUser>..
+ ///
+ internal static string StoreNotIUserSecurityStampStore {
+ get {
+ return ResourceManager.GetString("StoreNotIUserSecurityStampStore", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Store does not implement IUserTwoFactorStore<TUser>..
+ ///
+ internal static string StoreNotIUserTwoFactorStore {
+ get {
+ return ResourceManager.GetString("StoreNotIUserTwoFactorStore", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to User already has a password set..
+ ///
+ internal static string UserAlreadyHasPassword {
+ get {
+ return ResourceManager.GetString("UserAlreadyHasPassword", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to User already in role..
+ ///
+ internal static string UserAlreadyInRole {
+ get {
+ return ResourceManager.GetString("UserAlreadyInRole", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to UserId not found..
+ ///
+ internal static string UserIdNotFound {
+ get {
+ return ResourceManager.GetString("UserIdNotFound", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to User {0} does not exist..
+ ///
+ internal static string UserNameNotFound {
+ get {
+ return ResourceManager.GetString("UserNameNotFound", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to User is not in role..
+ ///
+ internal static string UserNotInRole {
+ get {
+ return ResourceManager.GetString("UserNotInRole", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Identity/Resources.resx b/src/Microsoft.AspNet.Identity/Resources.resx
new file mode 100644
index 0000000000..c9b40f983d
--- /dev/null
+++ b/src/Microsoft.AspNet.Identity/Resources.resx
@@ -0,0 +1,260 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ An unknown failure has occured.
+ Default identity result error message
+
+
+ Email '{0}' is already taken.
+ error for duplicate emails
+
+
+ Name {0} is already taken.
+ error for duplicate usernames
+
+
+ A user with that external login already exists.
+ Error when a login already linked
+
+
+ Email '{0}' is invalid.
+ invalid email
+
+
+ Invalid token.
+ Error when a token is not recognized
+
+
+ User name {0} is invalid, can only contain letters or digits.
+ usernames can only contain letters or digits
+
+
+ Lockout is not enabled for this user.
+ error when lockout is not enabled
+
+
+ No IUserTokenProvider is registered.
+ Error when there is no IUserTokenProvider
+
+
+ No IUserTwoFactorProvider for '{0}' is registered.
+ Error when there is no provider found
+
+
+ Incorrect password.
+ Error when a password doesn't match
+
+
+ Passwords must have at least one digit ('0'-'9').
+ Error when passwords do not have a digit
+
+
+ Passwords must have at least one lowercase ('a'-'z').
+ Error when passwords do not have a lowercase letter
+
+
+ Passwords must have at least one non letter or digit character.
+ Error when password does not have enough letter or digit characters
+
+
+ Passwords must have at least one uppercase ('A'-'Z').
+ Error when passwords do not have an uppercase letter
+
+
+ Passwords must be at least {0} characters.
+ Error message for passwords that are too short
+
+
+ {0} cannot be null or empty.
+ error for empty or null usernames
+
+
+ Role {0} does not exist.
+ error when a role does not exist
+
+
+ Store does not implement IQueryableRoleStore<TRole>.
+ error when the store does not implement this interface
+
+
+ Store does not implement IQueryableUserStore<TUser>.
+ error when the store does not implement this interface
+
+
+ Store does not implement IUserClaimStore<TUser>.
+ error when the store does not implement this interface
+
+
+ Store does not implement IUserConfirmationStore<TUser>.
+ error when the store does not implement this interface
+
+
+ Store does not implement IUserEmailStore<TUser>.
+ error when the store does not implement this interface
+
+
+ Store does not implement IUserLockoutStore<TUser>.
+ error when the store does not implement this interface
+
+
+ Store does not implement IUserLoginStore<TUser>.
+ error when the store does not implement this interface
+
+
+ Store does not implement IUserPasswordStore<TUser>.
+ error when the store does not implement this interface
+
+
+ Store does not implement IUserPhoneNumberStore<TUser>.
+ error when the store does not implement this interface
+
+
+ Store does not implement IUserRoleStore<TUser>.
+ error when the store does not implement this interface
+
+
+ Store does not implement IUserSecurityStampStore<TUser>.
+ error when the store does not implement this interface
+
+
+ Store does not implement IUserTwoFactorStore<TUser>.
+ error when the store does not implement this interface
+
+
+ User already has a password set.
+ error when AddPassword called when a user already has a password
+
+
+ User already in role.
+ Error when a user is already in a role
+
+
+ UserId not found.
+ No user with this id found
+
+
+ User {0} does not exist.
+ error when a user does not exist
+
+
+ User is not in role.
+ Error when a user is not in the role
+
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/Rfc6238AuthenticationService.cs b/src/Microsoft.AspNet.Identity/Rfc6238AuthenticationService.cs
new file mode 100644
index 0000000000..d6facc691f
--- /dev/null
+++ b/src/Microsoft.AspNet.Identity/Rfc6238AuthenticationService.cs
@@ -0,0 +1,115 @@
+#if NET45
+
+using System;
+using System.Diagnostics;
+using System.Net;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace Microsoft.AspNet.Identity
+{
+ internal sealed class SecurityToken
+ {
+ private readonly byte[] _data;
+
+ public SecurityToken(byte[] data)
+ {
+ _data = (byte[]) data.Clone();
+ }
+
+ internal byte[] GetDataNoClone()
+ {
+ return _data;
+ }
+ }
+
+ internal static class Rfc6238AuthenticationService
+ {
+ private static readonly DateTime _unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+ private static readonly TimeSpan _timestep = TimeSpan.FromMinutes(3);
+ private static readonly Encoding _encoding = new UTF8Encoding(false, true);
+
+ private static int ComputeTotp(HashAlgorithm hashAlgorithm, ulong timestepNumber, string modifier)
+ {
+ // # of 0's = length of pin
+ const int mod = 1000000;
+
+ // See https://tools.ietf.org/html/rfc4226
+ // We can add an optional modifier
+ var timestepAsBytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((long) timestepNumber));
+ var hash = hashAlgorithm.ComputeHash(ApplyModifier(timestepAsBytes, modifier));
+
+ // Generate DT string
+ var offset = hash[hash.Length - 1] & 0xf;
+ Debug.Assert(offset + 4 < hash.Length);
+ var binaryCode = (hash[offset] & 0x7f) << 24
+ | (hash[offset + 1] & 0xff) << 16
+ | (hash[offset + 2] & 0xff) << 8
+ | (hash[offset + 3] & 0xff);
+
+ return binaryCode%mod;
+ }
+
+ private static byte[] ApplyModifier(byte[] input, string modifier)
+ {
+ if (String.IsNullOrEmpty(modifier))
+ {
+ return input;
+ }
+
+ var modifierBytes = _encoding.GetBytes(modifier);
+ var combined = new byte[checked(input.Length + modifierBytes.Length)];
+ Buffer.BlockCopy(input, 0, combined, 0, input.Length);
+ Buffer.BlockCopy(modifierBytes, 0, combined, input.Length, modifierBytes.Length);
+ return combined;
+ }
+
+ // More info: https://tools.ietf.org/html/rfc6238#section-4
+ private static ulong GetCurrentTimeStepNumber()
+ {
+ var delta = DateTime.UtcNow - _unixEpoch;
+ return (ulong) (delta.Ticks/_timestep.Ticks);
+ }
+
+ public static int GenerateCode(SecurityToken securityToken, string modifier = null)
+ {
+ if (securityToken == null)
+ {
+ throw new ArgumentNullException("securityToken");
+ }
+
+ // Allow a variance of no greater than 90 seconds in either direction
+ var currentTimeStep = GetCurrentTimeStepNumber();
+ using (var hashAlgorithm = new HMACSHA1(securityToken.GetDataNoClone()))
+ {
+ return ComputeTotp(hashAlgorithm, currentTimeStep, modifier);
+ }
+ }
+
+ public static bool ValidateCode(SecurityToken securityToken, int code, string modifier = null)
+ {
+ if (securityToken == null)
+ {
+ throw new ArgumentNullException("securityToken");
+ }
+
+ // Allow a variance of no greater than 90 seconds in either direction
+ var currentTimeStep = GetCurrentTimeStepNumber();
+ using (var hashAlgorithm = new HMACSHA1(securityToken.GetDataNoClone()))
+ {
+ for (var i = -2; i <= 2; i++)
+ {
+ var computedTotp = ComputeTotp(hashAlgorithm, (ulong) ((long) currentTimeStep + i), modifier);
+ if (computedTotp == code)
+ {
+ return true;
+ }
+ }
+ }
+
+ // No match
+ return false;
+ }
+ }
+}
+#endif
diff --git a/src/Microsoft.AspNet.Identity/UserLoginInfo.cs b/src/Microsoft.AspNet.Identity/UserLoginInfo.cs
new file mode 100644
index 0000000000..79a6f7f46a
--- /dev/null
+++ b/src/Microsoft.AspNet.Identity/UserLoginInfo.cs
@@ -0,0 +1,29 @@
+namespace Microsoft.AspNet.Identity
+{
+ ///
+ /// Represents a linked login for a user (i.e. a local username/password or a facebook/google account
+ ///
+ public sealed class UserLoginInfo
+ {
+ ///
+ /// Constructor
+ ///
+ ///
+ ///
+ public UserLoginInfo(string loginProvider, string providerKey)
+ {
+ LoginProvider = loginProvider;
+ ProviderKey = providerKey;
+ }
+
+ ///
+ /// Provider for the linked login, i.e. Local, Facebook, Google, etc.
+ ///
+ public string LoginProvider { get; set; }
+
+ ///
+ /// Key for the linked login at the provider
+ ///
+ public string ProviderKey { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/UserManager.cs b/src/Microsoft.AspNet.Identity/UserManager.cs
new file mode 100644
index 0000000000..6238936636
--- /dev/null
+++ b/src/Microsoft.AspNet.Identity/UserManager.cs
@@ -0,0 +1,1711 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+#if NET45
+using System.Security.Claims;
+#endif
+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;
+ private IIdentityValidator _passwordValidator;
+ private IIdentityValidator _userValidator;
+
+ ///
+ /// 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);
+ //PasswordValidator = new MinimumLengthValidator(6);
+ //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 IIdentityValidator UserValidator
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return _userValidator;
+ }
+ set
+ {
+ ThrowIfDisposed();
+ if (value == null)
+ {
+ throw new ArgumentNullException("value");
+ }
+ _userValidator = value;
+ }
+ }
+
+ ///
+ /// Used to validate passwords before persisting changes
+ ///
+ public IIdentityValidator PasswordValidator
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return _passwordValidator;
+ }
+ set
+ {
+ ThrowIfDisposed();
+ if (value == null)
+ {
+ throw new ArgumentNullException("value");
+ }
+ _passwordValidator = value;
+ }
+ }
+
+ ///
+ /// 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);
+ }
+
+#if NET45
+ ///
+ /// 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);
+ }
+#endif
+
+ ///
+ /// Create a user with no password
+ ///
+ ///
+ ///
+ public virtual async Task Create(TUser user)
+ {
+ ThrowIfDisposed();
+ await UpdateSecurityStampInternal(user).ConfigureAwait(false);
+ var result = await UserValidator.Validate(user).ConfigureAwait(false);
+ 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 UserValidator.Validate(user).ConfigureAwait(false);
+ 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)
+ {
+ 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);
+ }
+
+#if NET45
+ // 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);
+ }
+#endif
+
+ 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;
+ }
+ }
+ }
+}
\ No newline at end of file