diff --git a/Identity.sln b/Identity.sln index c64740edda..2b85d66d72 100644 --- a/Identity.sln +++ b/Identity.sln @@ -33,6 +33,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.Identity.S EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.Identity.Security.Test.net45", "test\Microsoft.AspNet.Identity.Security.Test\Microsoft.AspNet.Identity.Security.Test.net45.csproj", "{30A2C4BB-86AB-4971-BC63-4761E8CE7D0F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.Identity.Entity.Test.net45", "test\Microsoft.AspNet.Identity.Entity.Test\Microsoft.AspNet.Identity.Entity.Test.net45.csproj", "{515A8ACE-9359-474D-B4AE-E7F148CDDB39}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -83,6 +85,10 @@ Global {30A2C4BB-86AB-4971-BC63-4761E8CE7D0F}.Debug|Any CPU.Build.0 = Debug|Any CPU {30A2C4BB-86AB-4971-BC63-4761E8CE7D0F}.Release|Any CPU.ActiveCfg = Release|Any CPU {30A2C4BB-86AB-4971-BC63-4761E8CE7D0F}.Release|Any CPU.Build.0 = Release|Any CPU + {515A8ACE-9359-474D-B4AE-E7F148CDDB39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {515A8ACE-9359-474D-B4AE-E7F148CDDB39}.Debug|Any CPU.Build.0 = Debug|Any CPU + {515A8ACE-9359-474D-B4AE-E7F148CDDB39}.Release|Any CPU.ActiveCfg = Release|Any CPU + {515A8ACE-9359-474D-B4AE-E7F148CDDB39}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -93,6 +99,7 @@ Global {E00E23B0-79B8-41E1-9998-57FECA1F2535} = {52D59F18-62D2-4D17-8CF2-BE192445AF8E} {9102E676-B509-4A78-AD66-A479C50FD1C3} = {52D59F18-62D2-4D17-8CF2-BE192445AF8E} {30A2C4BB-86AB-4971-BC63-4761E8CE7D0F} = {52D59F18-62D2-4D17-8CF2-BE192445AF8E} + {515A8ACE-9359-474D-B4AE-E7F148CDDB39} = {52D59F18-62D2-4D17-8CF2-BE192445AF8E} {B72401D7-47F6-4A98-89D5-CCBFEFC5B2B8} = {F6B0C0E9-C346-49D0-B583-95B6CE04BB1B} {E52361C9-1F0B-4229-86A0-E5C7C12A5429} = {F6B0C0E9-C346-49D0-B583-95B6CE04BB1B} {054B3FFA-7196-466F-9A8A-593FFE037A69} = {F6B0C0E9-C346-49D0-B583-95B6CE04BB1B} diff --git a/src/Microsoft.AspNet.Identity.Entity/IdentityContext.cs b/src/Microsoft.AspNet.Identity.Entity/IdentityContext.cs new file mode 100644 index 0000000000..b6130ff910 --- /dev/null +++ b/src/Microsoft.AspNet.Identity.Entity/IdentityContext.cs @@ -0,0 +1,69 @@ +using System; +using Microsoft.Data.Entity; +using Microsoft.Data.InMemory; +using Microsoft.Data.SqlServer; +using Microsoft.Data.Entity.Metadata; + +namespace Microsoft.AspNet.Identity.Entity +{ + public class IdentityContext : + IdentityContext + { + public IdentityContext(EntityConfiguration config) : base(config) { } + public IdentityContext() { } + } + + public class IdentityContext : EntityContext + where TUser : IdentityUser + where TRole : IdentityRole /*, TUserRole*/ + where TUserLogin : IdentityUserLogin + where TUserRole : IdentityUserRole + where TUserClaim : IdentityUserClaim + where TKey : IEquatable + { + + public EntitySet Users { get; set; } + public EntitySet Roles { get; set; } + + public IdentityContext() { } + public IdentityContext(EntityConfiguration config) : base(config) { } + + protected override void OnConfiguring(EntityConfigurationBuilder builder) + { +//#if NET45 +// builder.UseSqlServer(@"Server=(localdb)\v11.0;Database=IdentityDb;Trusted_Connection=True;"); +//#else + builder.UseDataStore(new InMemoryDataStore()); +//#endif + } + + protected override void OnModelCreating(ModelBuilder builder) + { + builder.Entity() + .Key(u => u.Id) + .Properties(ps => ps.Property(u => u.UserName)); + //.ToTable("AspNetUsers"); + builder.Entity() + .Key(r => r.Id); + //.ToTable("AspNetRoles"); + + builder.Entity() + .Key(r => new {r.UserId, r.RoleId}) + .ForeignKeys(fk => fk.ForeignKey(f => f.UserId)) + .ForeignKeys(fk => fk.ForeignKey(f => f.RoleId)); + //.ToTable("AspNetUserRoles"); + + builder.Entity() + .Key(l => new {l.LoginProvider, l.ProviderKey, l.UserId}) + .ForeignKeys(fk => fk.ForeignKey(f => f.UserId)); + //.ToTable("AspNetUserLogins"); + + builder.Entity() + .Key(c => c.Id) + .ForeignKeys(fk => fk.ForeignKey(f => f.UserId)); + //.ToTable("AspNetUserClaims"); + + } + + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity.Entity/IdentityRole.cs b/src/Microsoft.AspNet.Identity.Entity/IdentityRole.cs new file mode 100644 index 0000000000..b4dcdf61aa --- /dev/null +++ b/src/Microsoft.AspNet.Identity.Entity/IdentityRole.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; + +namespace Microsoft.AspNet.Identity.Entity +{ + /// + /// Represents a Role entity + /// + public class IdentityRole : IdentityRole + { + /// + /// Constructor + /// + public IdentityRole() + { + Id = Guid.NewGuid().ToString(); + } + + /// + /// Constructor + /// + /// + public IdentityRole(string roleName) + : this() + { + Name = roleName; + } + } + + /// + /// Represents a Role entity + /// + /// + /// + public class IdentityRole : IRole + where TUserRole : IdentityUserRole + where TKey : IEquatable + { + /// + /// Constructor + /// + public IdentityRole() + { + Users = new List(); + } + + /// + /// Navigation property for users in the role + /// + public virtual ICollection Users { get; private set; } + + /// + /// Role id + /// + public virtual TKey Id { get; set; } + + /// + /// Role name + /// + public virtual string Name { get; set; } + } +} diff --git a/src/Microsoft.AspNet.Identity.Entity/IdentityUser.cs b/src/Microsoft.AspNet.Identity.Entity/IdentityUser.cs new file mode 100644 index 0000000000..a718931db6 --- /dev/null +++ b/src/Microsoft.AspNet.Identity.Entity/IdentityUser.cs @@ -0,0 +1,104 @@ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace Microsoft.AspNet.Identity.Entity +{ + public class IdentityUser : IdentityUser + { + public IdentityUser() + { + Id = Guid.NewGuid().ToString(); + } + + public IdentityUser(string userName) : this() + { + UserName = userName; + } + } + + public class IdentityUser : IUser + where TLogin : IdentityUserLogin + where TRole : IdentityUserRole + where TClaim : IdentityUserClaim + where TKey : IEquatable + { + public IdentityUser() + { + Claims = new List(); + Roles = new List(); + Logins = new List(); + + } + + public virtual TKey Id { get; set; } + public virtual string UserName { get; set; } + + /// + /// Email + /// + public virtual string Email { get; set; } + + /// + /// True if the email is confirmed, default is false + /// + public virtual bool EmailConfirmed { get; set; } + + /// + /// The salted/hashed form of the user password + /// + public virtual string PasswordHash { get; set; } + + /// + /// A random value that should change whenever a users credentials have changed (password changed, login removed) + /// + public virtual string SecurityStamp { get; set; } + + /// + /// PhoneNumber for the user + /// + public virtual string PhoneNumber { get; set; } + + /// + /// True if the phone number is confirmed, default is false + /// + public virtual bool PhoneNumberConfirmed { get; set; } + + /// + /// Is two factor enabled for the user + /// + public virtual bool TwoFactorEnabled { get; set; } + + /// + /// DateTime in UTC when lockout ends, any time in the past is considered not locked out. + /// + public virtual DateTime? LockoutEndDateUtc { get; set; } + + /// + /// Is lockout enabled for this user + /// + public virtual bool LockoutEnabled { get; set; } + + /// + /// Used to record failures for the purposes of lockout + /// + public virtual int AccessFailedCount { get; set; } + + /// + /// Navigation property for user roles + /// + public virtual ICollection Roles { get; private set; } + + /// + /// Navigation property for user claims + /// + public virtual ICollection Claims { get; private set; } + + /// + /// Navigation property for user logins + /// + public virtual ICollection Logins { get; private set; } + + } +} diff --git a/src/Microsoft.AspNet.Identity.Entity/IdentityUserClaim.cs b/src/Microsoft.AspNet.Identity.Entity/IdentityUserClaim.cs new file mode 100644 index 0000000000..6f79fe9dbf --- /dev/null +++ b/src/Microsoft.AspNet.Identity.Entity/IdentityUserClaim.cs @@ -0,0 +1,33 @@ +using System; + +namespace Microsoft.AspNet.Identity.Entity +{ + public class IdentityUserClaim : IdentityUserClaim { } + + /// + /// EntityType that represents one specific user claim + /// + /// + public class IdentityUserClaim where TKey : IEquatable + { + /// + /// Primary key + /// + public virtual int Id { get; set; } + + /// + /// User Id for the user who owns this login + /// + public virtual TKey UserId { get; set; } + + /// + /// Claim type + /// + public virtual string ClaimType { get; set; } + + /// + /// Claim value + /// + public virtual string ClaimValue { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity.Entity/IdentityUserLogin.cs b/src/Microsoft.AspNet.Identity.Entity/IdentityUserLogin.cs new file mode 100644 index 0000000000..27b46988ad --- /dev/null +++ b/src/Microsoft.AspNet.Identity.Entity/IdentityUserLogin.cs @@ -0,0 +1,28 @@ +using System; + +namespace Microsoft.AspNet.Identity.Entity +{ + public class IdentityUserLogin : IdentityUserLogin { } + + /// + /// Entity type for a user's login (i.e. facebook, google) + /// + /// + public class IdentityUserLogin where TKey : IEquatable + { + /// + /// The login provider for the login (i.e. facebook, google) + /// + public virtual string LoginProvider { get; set; } + + /// + /// Key representing the login for the provider + /// + public virtual string ProviderKey { get; set; } + + /// + /// User Id for the user who owns this login + /// + public virtual TKey UserId { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity.Entity/IdentityUserRole.cs b/src/Microsoft.AspNet.Identity.Entity/IdentityUserRole.cs new file mode 100644 index 0000000000..c7f5da5d15 --- /dev/null +++ b/src/Microsoft.AspNet.Identity.Entity/IdentityUserRole.cs @@ -0,0 +1,23 @@ +using System; + +namespace Microsoft.AspNet.Identity.Entity +{ + public class IdentityUserRole : IdentityUserRole { } + + /// + /// EntityType that represents a user belonging to a role + /// + /// + public class IdentityUserRole where TKey : IEquatable + { + /// + /// UserId for the user that is in the role + /// + public virtual TKey UserId { get; set; } + + /// + /// RoleId for the role + /// + public virtual TKey RoleId { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity.Entity/RoleStore.cs b/src/Microsoft.AspNet.Identity.Entity/RoleStore.cs new file mode 100644 index 0000000000..bee18805b6 --- /dev/null +++ b/src/Microsoft.AspNet.Identity.Entity/RoleStore.cs @@ -0,0 +1,132 @@ +using System; +using System.Linq; +using System.Linq.Expressions; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Data.Entity; + +namespace Microsoft.AspNet.Identity.Entity +{ + public class RoleStore : + IQueryableRoleStore + where TRole : class,IRole + where TKey : IEquatable + { + private bool _disposed; + + public RoleStore(EntityContext context) + { + if (context == null) + { + throw new ArgumentNullException("context"); + } + Context = context; + AutoSaveChanges = true; + } + + public EntityContext Context { get; private set; } + + /// + /// If true will call SaveChanges after Create/Update/Delete + /// + public bool AutoSaveChanges { get; set; } + + private async Task SaveChanges(CancellationToken cancellationToken) + { + if (AutoSaveChanges) + { + await Context.SaveChangesAsync(cancellationToken); + } + } + + public virtual Task GetRoleAggregate(Expression> filter, CancellationToken cancellationToken = default(CancellationToken)) + { + return Roles.SingleOrDefaultAsync(filter, cancellationToken); + } + + public async virtual Task Create(TRole role, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (role == null) + { + throw new ArgumentNullException("role"); + } + await Context.AddAsync(role, cancellationToken); + await SaveChanges(cancellationToken); + } + + public async virtual Task Update(TRole role, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (role == null) + { + throw new ArgumentNullException("role"); + } + await Context.UpdateAsync(role, cancellationToken); + await SaveChanges(cancellationToken); + } + + public async virtual Task Delete(TRole role, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (role == null) + { + throw new ArgumentNullException("role"); + } + Context.Delete(role); + await SaveChanges(cancellationToken); + } + + /// + /// Find a role by id + /// + /// + /// + /// + public virtual Task FindById(TKey id, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + return Roles.SingleOrDefaultAsync(r => r.Id.Equals(id), cancellationToken); + //return GetRoleAggregate(u => u.Id.Equals(id)); + } + + /// + /// Find a role by name + /// + /// + /// + /// + public virtual Task FindByName(string name, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + return Roles.SingleOrDefaultAsync(r => r.Name.ToUpper() == name.ToUpper(), cancellationToken); + //return GetRoleAggregate(u => u.Name.ToUpper() == name.ToUpper()); + } + + private void ThrowIfDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(GetType().Name); + } + } + + /// + /// Dispose the store + /// + public void Dispose() + { + _disposed = true; + } + + public IQueryable Roles + { + get { return Context.Set(); } + } + } +} diff --git a/src/Microsoft.AspNet.Identity.Entity/UserStore.cs b/src/Microsoft.AspNet.Identity.Entity/UserStore.cs new file mode 100644 index 0000000000..e87c4bdf08 --- /dev/null +++ b/src/Microsoft.AspNet.Identity.Entity/UserStore.cs @@ -0,0 +1,797 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Data.Entity; + +namespace Microsoft.AspNet.Identity.Entity +{ + public class UserStore : + UserStore + { + public UserStore(EntityContext context) : base(context) { } + } + + public class UserStore : + IUserLoginStore, + IUserClaimStore, + IUserRoleStore, + IUserPasswordStore, + IUserSecurityStampStore, + IQueryableUserStore, + IUserEmailStore, + IUserPhoneNumberStore, + IUserTwoFactorStore, + IUserLockoutStore + where TKey : IEquatable + where TUser : IdentityUser + where TRole : IdentityRole + where TUserLogin : IdentityUserLogin, new() + where TUserRole : IdentityUserRole, new() + where TUserClaim : IdentityUserClaim, new() + { + private bool _disposed; + + public UserStore(EntityContext context) + { + if (context == null) + { + throw new ArgumentNullException("context"); + } + Context = context; + AutoSaveChanges = true; + } + + public EntityContext Context { get; private set; } + + /// + /// If true will call SaveChanges after Create/Update/Delete + /// + public bool AutoSaveChanges { get; set; } + + private Task SaveChanges(CancellationToken cancellationToken) + { + return AutoSaveChanges ? Context.SaveChangesAsync(cancellationToken) : Task.FromResult(0); + } + + protected virtual Task GetUserAggregate(Expression> filter, CancellationToken cancellationToken = default(CancellationToken)) + { + return Users.SingleOrDefaultAsync(filter, cancellationToken); + //Include(u => u.Roles) + //.Include(u => u.Claims) + //.Include(u => u.Logins) + } + + public async virtual Task Create(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException("user"); + } + await Context.AddAsync(user, cancellationToken); + await SaveChanges(cancellationToken); + } + + public async virtual Task Update(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException("user"); + } + await Context.UpdateAsync(user, cancellationToken); + await SaveChanges(cancellationToken); + } + + public async virtual Task Delete(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException("user"); + } + Context.Delete(user); + await SaveChanges(cancellationToken); + } + + /// + /// Find a user by id + /// + /// + /// + /// + public virtual Task FindById(TKey userId, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + return Users.SingleOrDefaultAsync(u => u.Id.Equals(userId), cancellationToken); + // TODO: return GetUserAggregate(u => u.Id.Equals(userId), cancellationToken); + } + + /// + /// Find a user by name + /// + /// + /// + /// + public virtual Task FindByName(string userName, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + return Users.SingleOrDefaultAsync(u => u.UserName.ToUpper() == userName.ToUpper(), cancellationToken); + // TODO: return GetUserAggregate(u => u.UserName.ToUpper() == userName.ToUpper(), cancellationToken); + } + + public IQueryable Users + { + get { return Context.Set(); } + } + + public async virtual Task AddLogin(TUser user, UserLoginInfo login, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException("user"); + } + if (login == null) + { + throw new ArgumentNullException("login"); + } + var l = new TUserLogin + { + UserId = user.Id, + ProviderKey = login.ProviderKey, + LoginProvider = login.LoginProvider + }; + await Context.Set().AddAsync(l, cancellationToken); + user.Logins.Add(l); + } + + public virtual Task RemoveLogin(TUser user, UserLoginInfo login, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException("user"); + } + throw new NotImplementedException(); + } + + public virtual Task> GetLogins(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException("user"); + } + IList result = + user.Logins.Select(l => new UserLoginInfo(l.LoginProvider, l.ProviderKey)).ToList(); + return Task.FromResult(result); + } + + public async virtual Task Find(UserLoginInfo login, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (login == null) + { + throw new ArgumentNullException("login"); + } + var provider = login.LoginProvider; + var key = login.ProviderKey; + // TODO: use FirstOrDefaultAsync + var userLogin = + Context.Set().FirstOrDefault(l => l.LoginProvider == provider && l.ProviderKey == key); + if (userLogin != null) + { + return await GetUserAggregate(u => u.Id.Equals(userLogin.UserId), cancellationToken); + } + return null; + } + + /// + /// Set the password hash for a user + /// + /// + /// + /// + /// + public virtual Task SetPasswordHash(TUser user, string passwordHash, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException("user"); + } + user.PasswordHash = passwordHash; + return Task.FromResult(0); + } + + /// + /// Get the password hash for a user + /// + /// + /// + /// + public virtual Task GetPasswordHash(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException("user"); + } + return Task.FromResult(user.PasswordHash); + } + + /// + /// Returns true if the user has a password set + /// + /// + /// + /// + public virtual Task HasPassword(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.FromResult(user.PasswordHash != null); + } + + /// + /// Return the claims for a user + /// + /// + /// + /// + public virtual Task> GetClaims(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException("user"); + } + IList result = user.Claims.Select(c => new Claim(c.ClaimType, c.ClaimValue)).ToList(); + return Task.FromResult(result); + } + + /// + /// Add a claim to a user + /// + /// + /// + /// + /// + public virtual Task AddClaim(TUser user, Claim claim, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException("user"); + } + if (claim == null) + { + throw new ArgumentNullException("claim"); + } + user.Claims.Add(new TUserClaim { UserId = user.Id, ClaimType = claim.Type, ClaimValue = claim.Value }); + return Task.FromResult(0); + } + + /// + /// Remove a claim from a user + /// + /// + /// + /// + /// + public virtual Task RemoveClaim(TUser user, Claim claim, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException("user"); + } + if (claim == null) + { + throw new ArgumentNullException("claim"); + } + var claims = + user.Claims.Where(uc => uc.ClaimValue == claim.Value && uc.ClaimType == claim.Type).ToList(); + foreach (var c in claims) + { + user.Claims.Remove(c); + } + // TODO:these claims might not exist in the dbset + //var query = + // _userClaims.Where( + // uc => uc.UserId.Equals(user.Id) && uc.ClaimValue == claim.Value && uc.ClaimType == claim.Type); + //foreach (var c in query) + //{ + // _userClaims.Remove(c); + //} + return Task.FromResult(0); + } + + /// + /// Returns whether the user email is confirmed + /// + /// + /// + /// + public virtual Task GetEmailConfirmed(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException("user"); + } + return Task.FromResult(user.EmailConfirmed); + } + + /// + /// Set IsConfirmed on the user + /// + /// + /// + /// + /// + public virtual Task SetEmailConfirmed(TUser user, bool confirmed, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException("user"); + } + user.EmailConfirmed = confirmed; + return Task.FromResult(0); + } + + /// + /// Set the user email + /// + /// + /// + /// + /// + public virtual Task SetEmail(TUser user, string email, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException("user"); + } + user.Email = email; + return Task.FromResult(0); + } + + /// + /// Get the user's email + /// + /// + /// + /// + public virtual Task GetEmail(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException("user"); + } + return Task.FromResult(user.Email); + } + + /// + /// Find a user by email + /// + /// + /// + /// + public virtual Task FindByEmail(string email, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + return Task.FromResult(Users.SingleOrDefault(u => u.Email.ToUpper() == email.ToUpper())); + //return GetUserAggregate(u => u.Email.ToUpper() == email.ToUpper(), cancellationToken); + } + + /// + /// Returns the DateTimeOffset that represents the end of a user's lockout, any time in the past should be considered + /// not locked out. + /// + /// + /// + /// + public virtual Task GetLockoutEndDate(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException("user"); + } + return + Task.FromResult(user.LockoutEndDateUtc.HasValue + ? new DateTimeOffset(DateTime.SpecifyKind(user.LockoutEndDateUtc.Value, DateTimeKind.Utc)) + : new DateTimeOffset()); + } + + /// + /// Locks a user out until the specified end date (set to a past date, to unlock a user) + /// + /// + /// + /// + /// + public virtual Task SetLockoutEndDate(TUser user, DateTimeOffset lockoutEnd, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException("user"); + } + user.LockoutEndDateUtc = lockoutEnd == DateTimeOffset.MinValue ? (DateTime?)null : lockoutEnd.UtcDateTime; + return Task.FromResult(0); + } + + /// + /// Used to record when an attempt to access the user has failed + /// + /// + /// + /// + public virtual Task IncrementAccessFailedCount(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException("user"); + } + user.AccessFailedCount++; + return Task.FromResult(user.AccessFailedCount); + } + + /// + /// Used to reset the account access count, typically after the account is successfully accessed + /// + /// + /// + /// + public virtual Task ResetAccessFailedCount(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException("user"); + } + user.AccessFailedCount = 0; + return Task.FromResult(0); + } + + /// + /// 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. + /// + /// + /// + /// + public virtual Task GetAccessFailedCount(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException("user"); + } + return Task.FromResult(user.AccessFailedCount); + } + + /// + /// Returns whether the user can be locked out. + /// + /// + /// + /// + public virtual Task GetLockoutEnabled(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException("user"); + } + return Task.FromResult(user.LockoutEnabled); + } + + /// + /// Sets whether the user can be locked out. + /// + /// + /// + /// + /// + public virtual Task SetLockoutEnabled(TUser user, bool enabled, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException("user"); + } + user.LockoutEnabled = enabled; + return Task.FromResult(0); + } + + /// + /// Set the user's phone number + /// + /// + /// + /// + /// + public virtual Task SetPhoneNumber(TUser user, string phoneNumber, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException("user"); + } + user.PhoneNumber = phoneNumber; + return Task.FromResult(0); + } + + /// + /// Get a user's phone number + /// + /// + /// + /// + public virtual Task GetPhoneNumber(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException("user"); + } + return Task.FromResult(user.PhoneNumber); + } + + /// + /// Returns whether the user phoneNumber is confirmed + /// + /// + /// + /// + public virtual Task GetPhoneNumberConfirmed(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException("user"); + } + return Task.FromResult(user.PhoneNumberConfirmed); + } + + /// + /// Set PhoneNumberConfirmed on the user + /// + /// + /// + /// + /// + public virtual Task SetPhoneNumberConfirmed(TUser user, bool confirmed, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException("user"); + } + user.PhoneNumberConfirmed = confirmed; + return Task.FromResult(0); + } + + /// + /// Add a user to a role + /// + /// + /// + /// + /// + public virtual Task AddToRole(TUser user, string roleName, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException("user"); + } + // TODO: + //if (String.IsNullOrWhiteSpace(roleName)) + //{ + // throw new ArgumentException(IdentityResources.ValueCannotBeNullOrEmpty, "roleName"); + //} + var roleEntity = Context.Set().SingleOrDefault(r => r.Name.ToUpper() == roleName.ToUpper()); + if (roleEntity == null) + { + throw new InvalidOperationException("Role Not Found"); + //TODO: String.Format(CultureInfo.CurrentCulture, IdentityResources.RoleNotFound, roleName)); + } + var ur = new TUserRole { UserId = user.Id, RoleId = roleEntity.Id }; + user.Roles.Add(ur); + roleEntity.Users.Add(ur); + return Task.FromResult(0); + } + + /// + /// Remove a user from a role + /// + /// + /// + /// + /// + public virtual Task RemoveFromRole(TUser user, string roleName, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException("user"); + } + //if (String.IsNullOrWhiteSpace(roleName)) + //{ + // throw new ArgumentException(IdentityResources.ValueCannotBeNullOrEmpty, "roleName"); + //} + throw new NotImplementedException(); + } + + /// + /// Get the names of the roles a user is a member of + /// + /// + /// + /// + public virtual Task> GetRoles(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException("user"); + } + throw new NotImplementedException(); + } + + /// + /// Returns true if the user is in the named role + /// + /// + /// + /// + /// + public virtual Task IsInRole(TUser user, string roleName, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException("user"); + } + //if (String.IsNullOrWhiteSpace(roleName)) + //{ + // throw new ArgumentException(IdentityResources.ValueCannotBeNullOrEmpty, "roleName"); + //} + throw new NotImplementedException(); + } + + /// + /// Set the security stamp for the user + /// + /// + /// + /// + /// + public virtual Task SetSecurityStamp(TUser user, string stamp, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException("user"); + } + user.SecurityStamp = stamp; + return Task.FromResult(0); + } + + /// + /// Get the security stamp for a user + /// + /// + /// + /// + public virtual Task GetSecurityStamp(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException("user"); + } + return Task.FromResult(user.SecurityStamp); + } + + /// + /// Set whether two factor authentication is enabled for the user + /// + /// + /// + /// + /// + public virtual Task SetTwoFactorEnabled(TUser user, bool enabled, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException("user"); + } + user.TwoFactorEnabled = enabled; + return Task.FromResult(0); + } + + /// + /// Gets whether two factor authentication is enabled for the user + /// + /// + /// + /// + public virtual Task GetTwoFactorEnabled(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException("user"); + } + return Task.FromResult(user.TwoFactorEnabled); + } + + private void ThrowIfDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(GetType().Name); + } + } + + /// + /// Dispose the store + /// + public void Dispose() + { + _disposed = true; + } + } +} diff --git a/src/Microsoft.AspNet.Identity.Entity/project.json b/src/Microsoft.AspNet.Identity.Entity/project.json index 20d8a02a58..709a3031c5 100644 --- a/src/Microsoft.AspNet.Identity.Entity/project.json +++ b/src/Microsoft.AspNet.Identity.Entity/project.json @@ -1,13 +1,40 @@ { "version": "0.1-alpha-*", "dependencies": { - "Microsoft.AspNet.Identity": "0.1-alpha-*" + "Microsoft.AspNet.ConfigurationModel": "0.1-alpha-*", + "Microsoft.AspNet.DependencyInjection": "0.1-alpha-*", + "Microsoft.AspNet.Identity": "0.1-alpha-*", + "Microsoft.AspNet.Logging": "0.1-alpha-*", + "Microsoft.Data.Entity": "0.1-alpha-*", + "Microsoft.Data.Relational": "0.1-alpha-*", + "Microsoft.Data.SqlServer": "0.1-pre-*", + "Microsoft.Data.InMemory": "0.1-alpha-*", + "System.Security.Claims" : "0.1-alpha-*" }, "configurations": { - "net45": {}, + "net45": { + "dependencies": { + "System.Runtime": "", + "System.Collections": "" + } + }, "k10": { "dependencies": { - "System.Runtime": "4.0.20.0" + "System.Collections": "4.0.0.0", + "System.ComponentModel": "4.0.0.0", + "System.Diagnostics.Debug": "4.0.10.0", + "System.Diagnostics.Tools": "4.0.0.0", + "System.Globalization": "4.0.10.0", + "System.Linq": "4.0.0.0", + "System.Linq.Expressions": "4.0.0.0", + "System.Linq.Queryable": "4.0.0.0", + "System.Reflection": "4.0.10.0", + "System.Resources.ResourceManager": "4.0.0.0", + "System.Runtime": "4.0.20.0", + "System.Runtime.Extensions": "4.0.10.0", + "System.Security.Principal": "4.0.0.0", + "System.Text.Encoding": "4.0.20.0", + "System.Threading.Tasks": "4.0.10.0" } } } diff --git a/src/Microsoft.AspNet.Identity.InMemory/InMemoryRoleStore.cs b/src/Microsoft.AspNet.Identity.InMemory/InMemoryRoleStore.cs index ac8c06d279..665d2a4b09 100644 --- a/src/Microsoft.AspNet.Identity.InMemory/InMemoryRoleStore.cs +++ b/src/Microsoft.AspNet.Identity.InMemory/InMemoryRoleStore.cs @@ -6,17 +6,18 @@ using System.Threading.Tasks; namespace Microsoft.AspNet.Identity.InMemory { - public class InMemoryRoleStore : IQueryableRoleStore - { - private readonly Dictionary _roles = new Dictionary(); + public class InMemoryRoleStore : IQueryableRoleStore where TRole : class,IRole - public Task Create(InMemoryRole role, CancellationToken cancellationToken = default(CancellationToken)) + { + private readonly Dictionary _roles = new Dictionary(); + + public Task Create(TRole role, CancellationToken cancellationToken = default(CancellationToken)) { _roles[role.Id] = role; return Task.FromResult(0); } - public Task Delete(InMemoryRole role, CancellationToken cancellationToken = default(CancellationToken)) + public Task Delete(TRole role, CancellationToken cancellationToken = default(CancellationToken)) { if (role == null || !_roles.ContainsKey(role.Id)) { @@ -26,22 +27,22 @@ namespace Microsoft.AspNet.Identity.InMemory return Task.FromResult(0); } - public Task Update(InMemoryRole role, CancellationToken cancellationToken = default(CancellationToken)) + public Task Update(TRole role, CancellationToken cancellationToken = default(CancellationToken)) { _roles[role.Id] = role; return Task.FromResult(0); } - public Task FindById(string roleId, CancellationToken cancellationToken = default(CancellationToken)) + public Task FindById(string roleId, CancellationToken cancellationToken = default(CancellationToken)) { if (_roles.ContainsKey(roleId)) { return Task.FromResult(_roles[roleId]); } - return Task.FromResult(null); + return Task.FromResult(null); } - public Task FindByName(string roleName, CancellationToken cancellationToken = default(CancellationToken)) + public Task FindByName(string roleName, CancellationToken cancellationToken = default(CancellationToken)) { return Task.FromResult( @@ -52,7 +53,7 @@ namespace Microsoft.AspNet.Identity.InMemory { } - public IQueryable Roles + public IQueryable Roles { get { return _roles.Values.AsQueryable(); } } diff --git a/src/Microsoft.AspNet.Identity/project.json b/src/Microsoft.AspNet.Identity/project.json index fe3b27e4c9..e7215434ee 100644 --- a/src/Microsoft.AspNet.Identity/project.json +++ b/src/Microsoft.AspNet.Identity/project.json @@ -17,6 +17,7 @@ "System.Globalization": "4.0.10.0", "System.Linq": "4.0.0.0", "System.Linq.Expressions": "4.0.0.0", + "System.Linq.Queryable": "4.0.0.0", "System.Reflection": "4.0.10.0", "System.Resources.ResourceManager": "4.0.0.0", "System.Runtime": "4.0.20.0", diff --git a/test/Microsoft.AspNet.Identity.Entity.Test/IdentityResultAssert.cs b/test/Microsoft.AspNet.Identity.Entity.Test/IdentityResultAssert.cs new file mode 100644 index 0000000000..ea8d35026d --- /dev/null +++ b/test/Microsoft.AspNet.Identity.Entity.Test/IdentityResultAssert.cs @@ -0,0 +1,27 @@ +using System.Linq; +using Xunit; + +namespace Microsoft.AspNet.Identity.Entity.Test +{ + public static class IdentityResultAssert + { + public static void IsSuccess(IdentityResult result) + { + Assert.NotNull(result); + Assert.True(result.Succeeded); + } + + public static void IsFailure(IdentityResult result) + { + Assert.NotNull(result); + Assert.False(result.Succeeded); + } + + public static void IsFailure(IdentityResult result, string error) + { + Assert.NotNull(result); + Assert.False(result.Succeeded); + Assert.Equal(error, result.Errors.First()); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.Entity.Test/UserStoreTest.cs b/test/Microsoft.AspNet.Identity.Entity.Test/UserStoreTest.cs new file mode 100644 index 0000000000..0216d30601 --- /dev/null +++ b/test/Microsoft.AspNet.Identity.Entity.Test/UserStoreTest.cs @@ -0,0 +1,1624 @@ +using Microsoft.AspNet.DependencyInjection.Fallback; +using Microsoft.AspNet.Testing; +using Microsoft.Data.Entity; +using Microsoft.Data.Entity.Metadata; +using Microsoft.Data.Entity.Storage; +using Microsoft.Data.InMemory; +using System; +using System.Linq; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.AspNet.Identity.Entity.Test +{ + public class UserStoreTest + { + [Fact] + public async Task Can_share_instance_between_contexts_with_sugar_experience2() + { + using (var db = new IdentityContext()) + { + db.Users.Add(new IdentityUser { UserName = "John Doe" }); + await db.SaveChangesAsync(); + } + + using (var db = new IdentityContext()) + { + var data = db.Users.ToList(); + Assert.Equal(1, data.Count); + Assert.Equal("John Doe", data[0].UserName); + } + } + + [Fact] + public async Task Can_share_instance_between_contexts_with_sugar_experience() + { + using (var db = new SimpleContext()) + { + db.Artists.Add(new SimpleContext.Artist { Name = "John Doe" }); + await db.SaveChangesAsync(); + } + + using (var db = new SimpleContext()) + { + var data = db.Artists.ToList(); + Assert.Equal(1, data.Count); + Assert.Equal("John Doe", data[0].Name); + } + } + + [Fact] + public async Task Can_create_two_artists() + { + using (var db = new SimpleContext()) + { + db.Artists.Add(new SimpleContext.Artist { Name = "John Doe", ArtistId = Guid.NewGuid().ToString() }); + await db.SaveChangesAsync(); + db.Artists.Add(new SimpleContext.Artist { Name = "Second guy", ArtistId = Guid.NewGuid().ToString() }); + await db.SaveChangesAsync(); + } + } + + private class SimpleContext : EntityContext + { + public EntitySet Artists { get; set; } + + protected override void OnConfiguring(EntityConfigurationBuilder builder) + { + builder.UseDataStore(new InMemoryDataStore()); + } + + protected override void OnModelCreating(ModelBuilder builder) + { + builder.Entity().Key(a => a.ArtistId); + } + + public class Artist// : ArtistBase + { + public string ArtistId { get; set; } + public string Name { get; set; } + } + + public class ArtistBase + { + public TKey ArtistId { get; set; } + public string Name { get; set; } + } + } + + [Fact] + public async Task Foo() + { + using (var db = new IdentityContext()) + { + db.Users.Add(new IdentityUser("A")); + await db.SaveChangesAsync(); + } + + using (var db = new IdentityContext()) + { + var data = db.Users.ToList(); + Assert.Equal(1, data.Count); + Assert.Equal("A", data[0].UserName); + } + } + + [Fact] + public async Task CanDeleteUser() + { + var manager = CreateManager(); + var user = new IdentityUser("Delete"); + IdentityResultAssert.IsSuccess(await manager.Create(user)); + IdentityResultAssert.IsSuccess(await manager.Delete(user)); + Assert.Null(await manager.FindById(user.Id)); + } + + //[Fact] + //public async Task CanUpdateUserName() + //{ + // var manager = CreateManager(); + // var user = new IdentityUser("Update"); + // IdentityResultAssert.IsSuccess(await manager.Create(user)); + // Assert.Null(await manager.FindByName("New")); + // user.UserName = "New"; + // IdentityResultAssert.IsSuccess(await manager.Update(user)); + // Assert.NotNull(await manager.FindByName("New")); + // Assert.Null(await manager.FindByName("Update")); + //} + + [Fact] + public async Task UserValidatorCanBlockCreate() + { + var manager = CreateManager(); + var user = new IdentityUser("CreateBlocked"); + manager.UserValidator = new AlwaysBadValidator(); + IdentityResultAssert.IsFailure(await manager.Create(user), AlwaysBadValidator.ErrorMessage); + } + + //[Fact] + //public async Task UserValidatorCanBlockUpdate() + //{ + // var manager = CreateManager(); + // var user = new IdentityUser("UpdateBlocked"); + // IdentityResultAssert.IsSuccess(await manager.Create(user)); + // manager.UserValidator = new AlwaysBadValidator(); + // IdentityResultAssert.IsFailure(await manager.Update(user), AlwaysBadValidator.ErrorMessage); + //} + + // [Theory] + // [InlineData("")] + // [InlineData(null)] + // public async Task UserValidatorBlocksShortEmailsWhenRequiresUniqueEmail(string email) + // { + // var manager = CreateManager(); + // var user = new IdentityUser("UpdateBlocked") {Email = email}; + // manager.UserValidator = new UserValidator {RequireUniqueEmail = true}; + // IdentityResultAssert.IsFailure(await manager.Create(user), "Email cannot be null or empty."); + // } + + //#if NET45 + // [Theory] + // [InlineData("@@afd")] + // [InlineData("bogus")] + // public async Task UserValidatorBlocksInvalidEmailsWhenRequiresUniqueEmail(string email) + // { + // var manager = CreateManager(); + // var user = new IdentityUser("UpdateBlocked") {Email = email}; + // manager.UserValidator = new UserValidator {RequireUniqueEmail = true}; + // IdentityResultAssert.IsFailure(await manager.Create(user), "Email '" + email + "' is invalid."); + // } + //#endif + + // [Fact] + // public async Task PasswordValidatorCanBlockAddPassword() + // { + // var manager = CreateManager(); + // var user = new IdentityUser("AddPasswordBlocked"); + // IdentityResultAssert.IsSuccess(await manager.Create(user)); + // manager.PasswordValidator = new AlwaysBadValidator(); + // IdentityResultAssert.IsFailure(await manager.AddPassword(user.Id, "password"), + // AlwaysBadValidator.ErrorMessage); + // } + + [Fact] + public async Task PasswordValidatorCanBlockChangePassword() + { + var manager = CreateManager(); + var user = new IdentityUser("ChangePasswordBlocked"); + IdentityResultAssert.IsSuccess(await manager.Create(user, "password")); + manager.PasswordValidator = new AlwaysBadValidator(); + IdentityResultAssert.IsFailure(await manager.ChangePassword(user.Id, "password", "new"), + AlwaysBadValidator.ErrorMessage); + } + + [Fact] + public async Task CanCreateUserNoPassword() + { + var manager = CreateManager(); + IdentityResultAssert.IsSuccess(await manager.Create(new IdentityUser("CreateUserTest"))); + var user = await manager.FindByName("CreateUserTest"); + Assert.NotNull(user); + Assert.Null(user.PasswordHash); + var logins = await manager.GetLogins(user.Id); + Assert.NotNull(logins); + Assert.Equal(0, logins.Count()); + } + + [Fact] + public async Task CanCreateUserAddLogin() + { + var manager = CreateManager(); + const string userName = "CreateExternalUserTest"; + const string provider = "ZzAuth"; + const string providerKey = "HaoKey"; + IdentityResultAssert.IsSuccess(await manager.Create(new IdentityUser(userName))); + var user = await manager.FindByName(userName); + Assert.NotNull(user); + var login = new UserLoginInfo(provider, providerKey); + IdentityResultAssert.IsSuccess(await manager.AddLogin(user.Id, login)); + var logins = await manager.GetLogins(user.Id); + Assert.NotNull(logins); + Assert.Equal(1, logins.Count()); + Assert.Equal(provider, logins.First().LoginProvider); + Assert.Equal(providerKey, logins.First().ProviderKey); + } + + [Fact] + public async Task CanCreateUserLoginAndAddPassword() + { + var manager = CreateManager(); + var login = new UserLoginInfo("Provider", "key"); + var user = new IdentityUser("CreateUserLoginAddPasswordTest"); + IdentityResultAssert.IsSuccess(await manager.Create(user)); + IdentityResultAssert.IsSuccess(await manager.AddLogin(user.Id, login)); + Assert.False(await manager.HasPassword(user.Id)); + IdentityResultAssert.IsSuccess(await manager.AddPassword(user.Id, "password")); + Assert.True(await manager.HasPassword(user.Id)); + var logins = await manager.GetLogins(user.Id); + Assert.NotNull(logins); + Assert.Equal(1, logins.Count()); + Assert.Equal(user, await manager.Find(login)); + Assert.Equal(user, await manager.Find(user.UserName, "password")); + } + + [Fact] + public async Task AddPasswordFailsIfAlreadyHave() + { + var manager = CreateManager(); + var user = new IdentityUser("CannotAddAnotherPassword"); + IdentityResultAssert.IsSuccess(await manager.Create(user, "Password")); + Assert.True(await manager.HasPassword(user.Id)); + IdentityResultAssert.IsFailure(await manager.AddPassword(user.Id, "password"), + "User already has a password set."); + } + + [Fact] + public async Task CanCreateUserAddRemoveLogin() + { + var manager = CreateManager(); + var user = new IdentityUser("CreateUserAddRemoveLoginTest"); + var login = new UserLoginInfo("Provider", "key"); + var result = await manager.Create(user); + Assert.NotNull(user); + IdentityResultAssert.IsSuccess(result); + IdentityResultAssert.IsSuccess(await manager.AddLogin(user.Id, login)); + Assert.Equal(user, await manager.Find(login)); + var logins = await manager.GetLogins(user.Id); + Assert.NotNull(logins); + Assert.Equal(1, logins.Count()); + Assert.Equal(login.LoginProvider, logins.Last().LoginProvider); + Assert.Equal(login.ProviderKey, logins.Last().ProviderKey); + var stamp = user.SecurityStamp; + IdentityResultAssert.IsSuccess(await manager.RemoveLogin(user.Id, login)); + Assert.Null(await manager.Find(login)); + logins = await manager.GetLogins(user.Id); + Assert.NotNull(logins); + Assert.Equal(0, logins.Count()); + Assert.NotEqual(stamp, user.SecurityStamp); + } + + [Fact] + public async Task CanRemovePassword() + { + var manager = CreateManager(); + var user = new IdentityUser("RemovePasswordTest"); + const string password = "password"; + IdentityResultAssert.IsSuccess(await manager.Create(user, password)); + var stamp = user.SecurityStamp; + IdentityResultAssert.IsSuccess(await manager.RemovePassword(user.Id)); + var u = await manager.FindByName(user.UserName); + Assert.NotNull(u); + Assert.Null(u.PasswordHash); + Assert.NotEqual(stamp, user.SecurityStamp); + } + + [Fact] + public async Task CanChangePassword() + { + var manager = CreateManager(); + var user = new IdentityUser("ChangePasswordTest"); + const string password = "password"; + const string newPassword = "newpassword"; + IdentityResultAssert.IsSuccess(await manager.Create(user, password)); + Assert.Equal(manager.Users.Count(), 1); + var stamp = user.SecurityStamp; + Assert.NotNull(stamp); + IdentityResultAssert.IsSuccess(await manager.ChangePassword(user.Id, password, newPassword)); + Assert.Null(await manager.Find(user.UserName, password)); + Assert.Equal(user, await manager.Find(user.UserName, newPassword)); + Assert.NotEqual(stamp, user.SecurityStamp); + } + + [Fact] + public async Task CanAddRemoveUserClaim() + { + var manager = CreateManager(); + var user = new IdentityUser("ClaimsAddRemove"); + IdentityResultAssert.IsSuccess(await manager.Create(user)); + Claim[] claims = { new Claim("c", "v"), new Claim("c2", "v2"), new Claim("c2", "v3") }; + foreach (var c in claims) + { + IdentityResultAssert.IsSuccess(await manager.AddClaim(user.Id, c)); + } + var userClaims = await manager.GetClaims(user.Id); + Assert.Equal(3, userClaims.Count); + IdentityResultAssert.IsSuccess(await manager.RemoveClaim(user.Id, claims[0])); + userClaims = await manager.GetClaims(user.Id); + Assert.Equal(2, userClaims.Count); + IdentityResultAssert.IsSuccess(await manager.RemoveClaim(user.Id, claims[1])); + userClaims = await manager.GetClaims(user.Id); + Assert.Equal(1, userClaims.Count); + IdentityResultAssert.IsSuccess(await manager.RemoveClaim(user.Id, claims[2])); + userClaims = await manager.GetClaims(user.Id); + Assert.Equal(0, userClaims.Count); + } + + [Fact] + public async Task ChangePasswordFallsIfPasswordWrong() + { + var manager = CreateManager(); + var user = new IdentityUser("user"); + IdentityResultAssert.IsSuccess(await manager.Create(user, "password")); + var result = await manager.ChangePassword(user.Id, "bogus", "newpassword"); + IdentityResultAssert.IsFailure(result, "Incorrect password."); + } + + [Fact] + public async Task AddDupeUserNameFails() + { + var manager = CreateManager(); + var user = new IdentityUser("dupe"); + var user2 = new IdentityUser("dupe"); + IdentityResultAssert.IsSuccess(await manager.Create(user)); + IdentityResultAssert.IsFailure(await manager.Create(user2), "Name dupe is already taken."); + } + + [Fact] + public async Task AddDupeEmailAllowedByDefault() + { + var manager = CreateManager(); + var user = new IdentityUser("dupe") { Email = "yup@yup.com" }; + var user2 = new IdentityUser("dupeEmail") { Email = "yup@yup.com" }; + IdentityResultAssert.IsSuccess(await manager.Create(user)); + IdentityResultAssert.IsSuccess(await manager.Create(user2)); + } + + [Fact] + public async Task AddDupeEmailFallsWhenUniqueEmailRequired() + { + var manager = CreateManager(); + manager.UserValidator = new UserValidator { RequireUniqueEmail = true }; + var user = new IdentityUser("dupe") { Email = "yup@yup.com" }; + var user2 = new IdentityUser("dupeEmail") { Email = "yup@yup.com" }; + IdentityResultAssert.IsSuccess(await manager.Create(user)); + IdentityResultAssert.IsFailure(await manager.Create(user2), "Email 'yup@yup.com' is already taken."); + } + + [Fact] + public async Task UpdateSecurityStampActuallyChanges() + { + var manager = CreateManager(); + var user = new IdentityUser("stampMe"); + Assert.Null(user.SecurityStamp); + IdentityResultAssert.IsSuccess(await manager.Create(user)); + var stamp = user.SecurityStamp; + Assert.NotNull(stamp); + IdentityResultAssert.IsSuccess(await manager.UpdateSecurityStamp(user.Id)); + Assert.NotEqual(stamp, user.SecurityStamp); + } + + [Fact] + public async Task AddDupeLoginFails() + { + var manager = CreateManager(); + var user = new IdentityUser("DupeLogin"); + var login = new UserLoginInfo("provder", "key"); + IdentityResultAssert.IsSuccess(await manager.Create(user)); + IdentityResultAssert.IsSuccess(await manager.AddLogin(user.Id, login)); + var result = await manager.AddLogin(user.Id, login); + IdentityResultAssert.IsFailure(result, "A user with that external login already exists."); + } + + // Email tests + [Fact] + public async Task CanFindByEmail() + { + var manager = CreateManager(); + const string userName = "EmailTest"; + const string email = "email@test.com"; + var user = new IdentityUser(userName) { Email = email }; + IdentityResultAssert.IsSuccess(await manager.Create(user)); + var fetch = await manager.FindByEmail(email); + Assert.Equal(user, fetch); + } + + [Fact] + public async Task CanFindUsersViaUserQuerable() + { + var mgr = CreateManager(); + var users = new[] + { + new IdentityUser("user1"), + new IdentityUser("user2"), + new IdentityUser("user3") + }; + foreach (var u in users) + { + IdentityResultAssert.IsSuccess(await mgr.Create(u)); + } + var usersQ = mgr.Users; + Assert.Equal(3, usersQ.Count()); + Assert.NotNull(usersQ.FirstOrDefault(u => u.UserName == "user1")); + Assert.NotNull(usersQ.FirstOrDefault(u => u.UserName == "user2")); + Assert.NotNull(usersQ.FirstOrDefault(u => u.UserName == "user3")); + Assert.Null(usersQ.FirstOrDefault(u => u.UserName == "bogus")); + } + + [Fact] + public async Task ClaimsIdentityCreatesExpectedClaims() + { + var context = CreateContext(); + var manager = CreateManager(context); + var role = CreateRoleManager(context); + var user = new IdentityUser("Hao"); + IdentityResultAssert.IsSuccess(await manager.Create(user)); + IdentityResultAssert.IsSuccess(await role.Create(new IdentityRole("Admin"))); + IdentityResultAssert.IsSuccess(await role.Create(new IdentityRole("Local"))); + IdentityResultAssert.IsSuccess(await manager.AddToRole(user.Id, "Admin")); + IdentityResultAssert.IsSuccess(await manager.AddToRole(user.Id, "Local")); + Claim[] userClaims = + { + new Claim("Whatever", "Value"), + new Claim("Whatever2", "Value2") + }; + foreach (var c in userClaims) + { + IdentityResultAssert.IsSuccess(await manager.AddClaim(user.Id, c)); + } + + var identity = await manager.CreateIdentity(user, "test"); + var claimsFactory = manager.ClaimsIdentityFactory as ClaimsIdentityFactory; + Assert.NotNull(claimsFactory); + var claims = identity.Claims; + Assert.NotNull(claims); + Assert.True( + claims.Any(c => c.Type == claimsFactory.UserNameClaimType && c.Value == user.UserName)); + Assert.True(claims.Any(c => c.Type == claimsFactory.UserIdClaimType && c.Value == user.Id)); + Assert.True(claims.Any(c => c.Type == claimsFactory.RoleClaimType && c.Value == "Admin")); + Assert.True(claims.Any(c => c.Type == claimsFactory.RoleClaimType && c.Value == "Local")); + foreach (var cl in userClaims) + { + Assert.True(claims.Any(c => c.Type == cl.Type && c.Value == cl.Value)); + } + } + + [Fact] + public async Task ConfirmEmailFalseByDefaultTest() + { + var manager = CreateManager(); + var user = new IdentityUser("test"); + IdentityResultAssert.IsSuccess(await manager.Create(user)); + Assert.False(await manager.IsEmailConfirmed(user.Id)); + } + + // TODO: No token provider implementations yet + private class StaticTokenProvider : IUserTokenProvider + { + public Task Generate(string purpose, UserManager manager, + IdentityUser user, CancellationToken token) + { + return Task.FromResult(MakeToken(purpose, user)); + } + + public Task Validate(string purpose, string token, UserManager manager, + IdentityUser user, CancellationToken cancellationToken) + { + return Task.FromResult(token == MakeToken(purpose, user)); + } + + public Task Notify(string token, UserManager manager, IdentityUser user, CancellationToken cancellationToken) + { + return Task.FromResult(0); + } + + public Task IsValidProviderForUser(UserManager manager, IdentityUser user, CancellationToken token) + { + return Task.FromResult(true); + } + + private static string MakeToken(string purpose, IUser user) + { + return string.Join(":", user.Id, purpose, "ImmaToken"); + } + } + + [Fact] + public async Task CanResetPasswordWithStaticTokenProvider() + { + var manager = CreateManager(); + manager.UserTokenProvider = new StaticTokenProvider(); + var user = new IdentityUser("ResetPasswordTest"); + const string password = "password"; + const string newPassword = "newpassword"; + IdentityResultAssert.IsSuccess(await manager.Create(user, password)); + var stamp = user.SecurityStamp; + Assert.NotNull(stamp); + var token = await manager.GeneratePasswordResetToken(user.Id); + Assert.NotNull(token); + IdentityResultAssert.IsSuccess(await manager.ResetPassword(user.Id, token, newPassword)); + Assert.Null(await manager.Find(user.UserName, password)); + Assert.Equal(user, await manager.Find(user.UserName, newPassword)); + Assert.NotEqual(stamp, user.SecurityStamp); + } + + [Fact] + public async Task PasswordValidatorCanBlockResetPasswordWithStaticTokenProvider() + { + var manager = CreateManager(); + manager.UserTokenProvider = new StaticTokenProvider(); + var user = new IdentityUser("ResetPasswordTest"); + const string password = "password"; + const string newPassword = "newpassword"; + IdentityResultAssert.IsSuccess(await manager.Create(user, password)); + var stamp = user.SecurityStamp; + Assert.NotNull(stamp); + var token = await manager.GeneratePasswordResetToken(user.Id); + Assert.NotNull(token); + manager.PasswordValidator = new AlwaysBadValidator(); + IdentityResultAssert.IsFailure(await manager.ResetPassword(user.Id, token, newPassword), + AlwaysBadValidator.ErrorMessage); + Assert.NotNull(await manager.Find(user.UserName, password)); + Assert.Equal(user, await manager.Find(user.UserName, password)); + Assert.Equal(stamp, user.SecurityStamp); + } + + [Fact] + public async Task ResetPasswordWithStaticTokenProviderFailsWithWrongToken() + { + var manager = CreateManager(); + manager.UserTokenProvider = new StaticTokenProvider(); + var user = new IdentityUser("ResetPasswordTest"); + const string password = "password"; + const string newPassword = "newpassword"; + IdentityResultAssert.IsSuccess(await manager.Create(user, password)); + var stamp = user.SecurityStamp; + Assert.NotNull(stamp); + IdentityResultAssert.IsFailure(await manager.ResetPassword(user.Id, "bogus", newPassword), "Invalid token."); + Assert.NotNull(await manager.Find(user.UserName, password)); + Assert.Equal(user, await manager.Find(user.UserName, password)); + Assert.Equal(stamp, user.SecurityStamp); + } + + [Fact] + public async Task CanGenerateAndVerifyUserTokenWithStaticTokenProvider() + { + var manager = CreateManager(); + manager.UserTokenProvider = new StaticTokenProvider(); + var user = new IdentityUser("UserTokenTest"); + var user2 = new IdentityUser("UserTokenTest2"); + IdentityResultAssert.IsSuccess(await manager.Create(user)); + IdentityResultAssert.IsSuccess(await manager.Create(user2)); + var token = await manager.GenerateUserToken("test", user.Id); + Assert.True(await manager.VerifyUserToken(user.Id, "test", token)); + Assert.False(await manager.VerifyUserToken(user.Id, "test2", token)); + Assert.False(await manager.VerifyUserToken(user.Id, "test", token + "a")); + Assert.False(await manager.VerifyUserToken(user2.Id, "test", token)); + } + + [Fact] + public async Task CanConfirmEmailWithStaticToken() + { + var manager = CreateManager(); + manager.UserTokenProvider = new StaticTokenProvider(); + var user = new IdentityUser("test"); + Assert.False(user.EmailConfirmed); + IdentityResultAssert.IsSuccess(await manager.Create(user)); + var token = await manager.GenerateEmailConfirmationToken(user.Id); + Assert.NotNull(token); + IdentityResultAssert.IsSuccess(await manager.ConfirmEmail(user.Id, token)); + Assert.True(await manager.IsEmailConfirmed(user.Id)); + IdentityResultAssert.IsSuccess(await manager.SetEmail(user.Id, null)); + Assert.False(await manager.IsEmailConfirmed(user.Id)); + } + + [Fact] + public async Task ConfirmEmailWithStaticTokenFailsWithWrongToken() + { + var manager = CreateManager(); + manager.UserTokenProvider = new StaticTokenProvider(); + var user = new IdentityUser("test"); + Assert.False(user.EmailConfirmed); + IdentityResultAssert.IsSuccess(await manager.Create(user)); + IdentityResultAssert.IsFailure(await manager.ConfirmEmail(user.Id, "bogus"), "Invalid token."); + Assert.False(await manager.IsEmailConfirmed(user.Id)); + } + + // TODO: Can't reenable til we have a SecurityStamp linked token provider + //[Fact] + //public async Task ConfirmTokenFailsAfterPasswordChange() + //{ + // var manager = CreateManager(); + // var user = new IdentityUser("test"); + // Assert.False(user.EmailConfirmed); + // IdentityResultAssert.IsSuccess(await manager.Create(user, "password")); + // var token = await manager.GenerateEmailConfirmationToken(user.Id); + // Assert.NotNull(token); + // IdentityResultAssert.IsSuccess(await manager.ChangePassword(user.Id, "password", "newpassword")); + // IdentityResultAssert.IsFailure(await manager.ConfirmEmail(user.Id, token), "Invalid token."); + // Assert.False(await manager.IsEmailConfirmed(user.Id)); + //} + + // Lockout tests + + [Fact] + public async Task SingleFailureLockout() + { + var mgr = CreateManager(); + mgr.DefaultAccountLockoutTimeSpan = TimeSpan.FromHours(1); + mgr.UserLockoutEnabledByDefault = true; + var user = new IdentityUser("fastLockout"); + IdentityResultAssert.IsSuccess(await mgr.Create(user)); + Assert.True(await mgr.GetLockoutEnabled(user.Id)); + Assert.True(user.LockoutEnabled); + Assert.False(await mgr.IsLockedOut(user.Id)); + IdentityResultAssert.IsSuccess(await mgr.AccessFailed(user.Id)); + Assert.True(await mgr.IsLockedOut(user.Id)); + Assert.True(await mgr.GetLockoutEndDate(user.Id) > DateTimeOffset.UtcNow.AddMinutes(55)); + Assert.Equal(0, await mgr.GetAccessFailedCount(user.Id)); + } + + [Fact] + public async Task TwoFailureLockout() + { + var mgr = CreateManager(); + mgr.DefaultAccountLockoutTimeSpan = TimeSpan.FromHours(1); + mgr.UserLockoutEnabledByDefault = true; + mgr.MaxFailedAccessAttemptsBeforeLockout = 2; + var user = new IdentityUser("twoFailureLockout"); + IdentityResultAssert.IsSuccess(await mgr.Create(user)); + Assert.True(await mgr.GetLockoutEnabled(user.Id)); + Assert.True(user.LockoutEnabled); + Assert.False(await mgr.IsLockedOut(user.Id)); + IdentityResultAssert.IsSuccess(await mgr.AccessFailed(user.Id)); + Assert.False(await mgr.IsLockedOut(user.Id)); + Assert.False(await mgr.GetLockoutEndDate(user.Id) > DateTimeOffset.UtcNow.AddMinutes(55)); + Assert.Equal(1, await mgr.GetAccessFailedCount(user.Id)); + IdentityResultAssert.IsSuccess(await mgr.AccessFailed(user.Id)); + Assert.True(await mgr.IsLockedOut(user.Id)); + Assert.True(await mgr.GetLockoutEndDate(user.Id) > DateTimeOffset.UtcNow.AddMinutes(55)); + Assert.Equal(0, await mgr.GetAccessFailedCount(user.Id)); + } + + [Fact] + public async Task ResetAccessCountPreventsLockout() + { + var mgr = CreateManager(); + mgr.DefaultAccountLockoutTimeSpan = TimeSpan.FromHours(1); + mgr.UserLockoutEnabledByDefault = true; + mgr.MaxFailedAccessAttemptsBeforeLockout = 2; + var user = new IdentityUser("resetLockout"); + IdentityResultAssert.IsSuccess(await mgr.Create(user)); + Assert.True(await mgr.GetLockoutEnabled(user.Id)); + Assert.True(user.LockoutEnabled); + Assert.False(await mgr.IsLockedOut(user.Id)); + IdentityResultAssert.IsSuccess(await mgr.AccessFailed(user.Id)); + Assert.False(await mgr.IsLockedOut(user.Id)); + Assert.False(await mgr.GetLockoutEndDate(user.Id) > DateTimeOffset.UtcNow.AddMinutes(55)); + Assert.Equal(1, await mgr.GetAccessFailedCount(user.Id)); + IdentityResultAssert.IsSuccess(await mgr.ResetAccessFailedCount(user.Id)); + Assert.Equal(0, await mgr.GetAccessFailedCount(user.Id)); + Assert.False(await mgr.IsLockedOut(user.Id)); + Assert.False(await mgr.GetLockoutEndDate(user.Id) > DateTimeOffset.UtcNow.AddMinutes(55)); + IdentityResultAssert.IsSuccess(await mgr.AccessFailed(user.Id)); + Assert.False(await mgr.IsLockedOut(user.Id)); + Assert.False(await mgr.GetLockoutEndDate(user.Id) > DateTimeOffset.UtcNow.AddMinutes(55)); + Assert.Equal(1, await mgr.GetAccessFailedCount(user.Id)); + } + + [Fact] + public async Task CanEnableLockoutManuallyAndLockout() + { + var mgr = CreateManager(); + mgr.DefaultAccountLockoutTimeSpan = TimeSpan.FromHours(1); + mgr.MaxFailedAccessAttemptsBeforeLockout = 2; + var user = new IdentityUser("manualLockout"); + IdentityResultAssert.IsSuccess(await mgr.Create(user)); + Assert.False(await mgr.GetLockoutEnabled(user.Id)); + Assert.False(user.LockoutEnabled); + IdentityResultAssert.IsSuccess(await mgr.SetLockoutEnabled(user.Id, true)); + Assert.True(await mgr.GetLockoutEnabled(user.Id)); + Assert.True(user.LockoutEnabled); + Assert.False(await mgr.IsLockedOut(user.Id)); + IdentityResultAssert.IsSuccess(await mgr.AccessFailed(user.Id)); + Assert.False(await mgr.IsLockedOut(user.Id)); + Assert.False(await mgr.GetLockoutEndDate(user.Id) > DateTimeOffset.UtcNow.AddMinutes(55)); + Assert.Equal(1, await mgr.GetAccessFailedCount(user.Id)); + IdentityResultAssert.IsSuccess(await mgr.AccessFailed(user.Id)); + Assert.True(await mgr.IsLockedOut(user.Id)); + Assert.True(await mgr.GetLockoutEndDate(user.Id) > DateTimeOffset.UtcNow.AddMinutes(55)); + Assert.Equal(0, await mgr.GetAccessFailedCount(user.Id)); + } + + [Fact] + public async Task UserNotLockedOutWithNullDateTimeAndIsSetToNullDate() + { + var mgr = CreateManager(); + mgr.UserLockoutEnabledByDefault = true; + var user = new IdentityUser("LockoutTest"); + IdentityResultAssert.IsSuccess(await mgr.Create(user)); + Assert.True(await mgr.GetLockoutEnabled(user.Id)); + Assert.True(user.LockoutEnabled); + IdentityResultAssert.IsSuccess(await mgr.SetLockoutEndDate(user.Id, new DateTimeOffset())); + Assert.False(await mgr.IsLockedOut(user.Id)); + Assert.Equal(new DateTimeOffset(), await mgr.GetLockoutEndDate(user.Id)); + Assert.Null(user.LockoutEndDateUtc); + } + + [Fact] + public async Task LockoutFailsIfNotEnabled() + { + var mgr = CreateManager(); + var user = new IdentityUser("LockoutNotEnabledTest"); + IdentityResultAssert.IsSuccess(await mgr.Create(user)); + Assert.False(await mgr.GetLockoutEnabled(user.Id)); + Assert.False(user.LockoutEnabled); + IdentityResultAssert.IsFailure(await mgr.SetLockoutEndDate(user.Id, new DateTimeOffset()), + "Lockout is not enabled for this user."); + Assert.False(await mgr.IsLockedOut(user.Id)); + } + + [Fact] + public async Task LockoutEndToUtcNowMinus1SecInUserShouldNotBeLockedOut() + { + var mgr = CreateManager(); + mgr.UserLockoutEnabledByDefault = true; + var user = new IdentityUser("LockoutUtcNowTest") { LockoutEndDateUtc = DateTime.UtcNow.AddSeconds(-1) }; + IdentityResultAssert.IsSuccess(await mgr.Create(user)); + Assert.True(await mgr.GetLockoutEnabled(user.Id)); + Assert.True(user.LockoutEnabled); + Assert.False(await mgr.IsLockedOut(user.Id)); + } + + [Fact] + public async Task LockoutEndToUtcNowSubOneSecondWithManagerShouldNotBeLockedOut() + { + var mgr = CreateManager(); + mgr.UserLockoutEnabledByDefault = true; + var user = new IdentityUser("LockoutUtcNowTest"); + IdentityResultAssert.IsSuccess(await mgr.Create(user)); + Assert.True(await mgr.GetLockoutEnabled(user.Id)); + Assert.True(user.LockoutEnabled); + IdentityResultAssert.IsSuccess(await mgr.SetLockoutEndDate(user.Id, DateTimeOffset.UtcNow.AddSeconds(-1))); + Assert.False(await mgr.IsLockedOut(user.Id)); + } + + [Fact] + public async Task LockoutEndToUtcNowPlus5ShouldBeLockedOut() + { + var mgr = CreateManager(); + mgr.UserLockoutEnabledByDefault = true; + var user = new IdentityUser("LockoutUtcNowTest") { LockoutEndDateUtc = DateTime.UtcNow.AddMinutes(5) }; + IdentityResultAssert.IsSuccess(await mgr.Create(user)); + Assert.True(await mgr.GetLockoutEnabled(user.Id)); + Assert.True(user.LockoutEnabled); + Assert.True(await mgr.IsLockedOut(user.Id)); + } + + [Fact] + public async Task UserLockedOutWithDateTimeLocalKindNowPlus30() + { + var mgr = CreateManager(); + mgr.UserLockoutEnabledByDefault = true; + var user = new IdentityUser("LockoutTest"); + IdentityResultAssert.IsSuccess(await mgr.Create(user)); + Assert.True(await mgr.GetLockoutEnabled(user.Id)); + Assert.True(user.LockoutEnabled); + var lockoutEnd = new DateTimeOffset(DateTime.Now.AddMinutes(30).ToLocalTime()); + IdentityResultAssert.IsSuccess(await mgr.SetLockoutEndDate(user.Id, lockoutEnd)); + Assert.True(await mgr.IsLockedOut(user.Id)); + var end = await mgr.GetLockoutEndDate(user.Id); + Assert.Equal(lockoutEnd, end); + } + + // Role Tests + [Fact] + public async Task CanCreateRoleTest() + { + var manager = CreateRoleManager(); + var role = new IdentityRole("create"); + Assert.False(await manager.RoleExists(role.Name)); + IdentityResultAssert.IsSuccess(await manager.Create(role)); + Assert.True(await manager.RoleExists(role.Name)); + } + + private class AlwaysBadValidator : IUserValidator, IRoleValidator, + IPasswordValidator + { + public const string ErrorMessage = "I'm Bad."; + + public Task Validate(string password, CancellationToken token) + { + return Task.FromResult(IdentityResult.Failed(ErrorMessage)); + } + + public Task Validate(RoleManager manager, IdentityRole role, CancellationToken token) + { + return Task.FromResult(IdentityResult.Failed(ErrorMessage)); + } + + public Task Validate(UserManager manager, IdentityUser user, CancellationToken token) + { + return Task.FromResult(IdentityResult.Failed(ErrorMessage)); + } + } + + [Fact] + public async Task BadValidatorBlocksCreateRole() + { + var manager = CreateRoleManager(); + manager.RoleValidator = new AlwaysBadValidator(); + IdentityResultAssert.IsFailure(await manager.Create(new IdentityRole("blocked")), + AlwaysBadValidator.ErrorMessage); + } + + [Fact] + public async Task BadValidatorBlocksRoleUpdate() + { + var manager = CreateRoleManager(); + var role = new IdentityRole("poorguy"); + IdentityResultAssert.IsSuccess(await manager.Create(role)); + var error = AlwaysBadValidator.ErrorMessage; + manager.RoleValidator = new AlwaysBadValidator(); + IdentityResultAssert.IsFailure(await manager.Update(role), error); + } + + [Fact] + public async Task CanDeleteRoleTest() + { + var manager = CreateRoleManager(); + var role = new IdentityRole("delete"); + Assert.False(await manager.RoleExists(role.Name)); + IdentityResultAssert.IsSuccess(await manager.Create(role)); + IdentityResultAssert.IsSuccess(await manager.Delete(role)); + Assert.False(await manager.RoleExists(role.Name)); + } + + [Fact] + public async Task CanRoleFindByIdTest() + { + var manager = CreateRoleManager(); + var role = new IdentityRole("FindById"); + Assert.Null(await manager.FindById(role.Id)); + IdentityResultAssert.IsSuccess(await manager.Create(role)); + Assert.Equal(role, await manager.FindById(role.Id)); + } + + [Fact] + public async Task CanRoleFindByName() + { + var manager = CreateRoleManager(); + var role = new IdentityRole("FindByName"); + Assert.Null(await manager.FindByName(role.Name)); + Assert.False(await manager.RoleExists(role.Name)); + IdentityResultAssert.IsSuccess(await manager.Create(role)); + Assert.Equal(role, await manager.FindByName(role.Name)); + } + + [Fact] + public async Task CanUpdateRoleNameTest() + { + var manager = CreateRoleManager(); + var role = new IdentityRole("update"); + Assert.False(await manager.RoleExists(role.Name)); + IdentityResultAssert.IsSuccess(await manager.Create(role)); + Assert.True(await manager.RoleExists(role.Name)); + role.Name = "Changed"; + IdentityResultAssert.IsSuccess(await manager.Update(role)); + Assert.False(await manager.RoleExists("update")); + Assert.Equal(role, await manager.FindByName(role.Name)); + } + + [Fact] + public async Task CanQuerableRolesTest() + { + var manager = CreateRoleManager(); + IdentityRole[] roles = + { + new IdentityRole("r1"), new IdentityRole("r2"), new IdentityRole("r3"), + new IdentityRole("r4") + }; + foreach (var r in roles) + { + IdentityResultAssert.IsSuccess(await manager.Create(r)); + } + Assert.Equal(roles.Length, manager.Roles.Count()); + var r1 = manager.Roles.FirstOrDefault(r => r.Name == "r1"); + Assert.Equal(roles[0], r1); + } + + //[Fact] + //public async Task DeleteRoleNonEmptySucceedsTest() + //{ + // // Need fail if not empty? + // var userMgr = CreateManager(); + // var roleMgr = CreateRoleManager(); + // var role = new IdentityRole("deleteNonEmpty"); + // Assert.False(await roleMgr.RoleExists(role.Name)); + // IdentityResultAssert.IsSuccess(await roleMgr.Create(role)); + // var user = new IdentityUser("t"); + // IdentityResultAssert.IsSuccess(await userMgr.Create(user)); + // IdentityResultAssert.IsSuccess(await userMgr.AddToRole(user.Id, role.Name)); + // IdentityResultAssert.IsSuccess(await roleMgr.Delete(role)); + // Assert.Null(await roleMgr.FindByName(role.Name)); + // Assert.False(await roleMgr.RoleExists(role.Name)); + // // REVIEW: We should throw if deleteing a non empty role? + // var roles = await userMgr.GetRoles(user.Id); + + // // In memory this doesn't work since there's no concept of cascading deletes + // //Assert.Equal(0, roles.Count()); + //} + + ////[Fact] + ////public async Task DeleteUserRemovesFromRoleTest() + ////{ + //// // Need fail if not empty? + //// var userMgr = CreateManager(); + //// var roleMgr = CreateRoleManager(); + //// var role = new IdentityRole("deleteNonEmpty"); + //// Assert.False(await roleMgr.RoleExists(role.Name)); + //// IdentityResultAssert.IsSuccess(await roleMgr.Create(role)); + //// var user = new IdentityUser("t"); + //// IdentityResultAssert.IsSuccess(await userMgr.Create(user)); + //// IdentityResultAssert.IsSuccess(await userMgr.AddToRole(user.Id, role.Name)); + //// IdentityResultAssert.IsSuccess(await userMgr.Delete(user)); + //// role = roleMgr.FindById(role.Id); + ////} + + [Fact] + public async Task CreateRoleFailsIfExists() + { + var manager = CreateRoleManager(); + var role = new IdentityRole("dupeRole"); + Assert.False(await manager.RoleExists(role.Name)); + IdentityResultAssert.IsSuccess(await manager.Create(role)); + Assert.True(await manager.RoleExists(role.Name)); + var role2 = new IdentityRole("dupeRole"); + IdentityResultAssert.IsFailure(await manager.Create(role2)); + } + + [Fact] + public async Task CanAddUsersToRole() + { + var context = CreateContext(); + var manager = CreateManager(context); + var roleManager = CreateRoleManager(context); + var role = new IdentityRole("addUserTest"); + IdentityResultAssert.IsSuccess(await roleManager.Create(role)); + IdentityUser[] users = + { + new IdentityUser("1"), new IdentityUser("2"), new IdentityUser("3"), + new IdentityUser("4") + }; + foreach (var u in users) + { + IdentityResultAssert.IsSuccess(await manager.Create(u)); + IdentityResultAssert.IsSuccess(await manager.AddToRole(u.Id, role.Name)); + Assert.True(await manager.IsInRole(u.Id, role.Name)); + } + } + + [Fact] + public async Task CanGetRolesForUser() + { + var context = CreateContext(); + var userManager = CreateManager(context); + var roleManager = CreateRoleManager(context); + IdentityUser[] users = + { + new IdentityUser("u1"), new IdentityUser("u2"), new IdentityUser("u3"), + new IdentityUser("u4") + }; + IdentityRole[] roles = + { + new IdentityRole("r1"), new IdentityRole("r2"), new IdentityRole("r3"), + new IdentityRole("r4") + }; + foreach (var u in users) + { + IdentityResultAssert.IsSuccess(await userManager.Create(u)); + } + foreach (var r in roles) + { + IdentityResultAssert.IsSuccess(await roleManager.Create(r)); + foreach (var u in users) + { + IdentityResultAssert.IsSuccess(await userManager.AddToRole(u.Id, r.Name)); + Assert.True(await userManager.IsInRole(u.Id, r.Name)); + } + } + + foreach (var u in users) + { + var rs = await userManager.GetRoles(u.Id); + Assert.Equal(roles.Length, rs.Count); + foreach (var r in roles) + { + Assert.True(rs.Any(role => role == r.Name)); + } + } + } + + + [Fact] + public async Task RemoveUserFromRoleWithMultipleRoles() + { + var context = CreateContext(); + var userManager = CreateManager(context); + var roleManager = CreateRoleManager(context); + var user = new IdentityUser("MultiRoleUser"); + IdentityResultAssert.IsSuccess(await userManager.Create(user)); + IdentityRole[] roles = + { + new IdentityRole("r1"), new IdentityRole("r2"), new IdentityRole("r3"), + new IdentityRole("r4") + }; + foreach (var r in roles) + { + IdentityResultAssert.IsSuccess(await roleManager.Create(r)); + IdentityResultAssert.IsSuccess(await userManager.AddToRole(user.Id, r.Name)); + Assert.True(await userManager.IsInRole(user.Id, r.Name)); + } + IdentityResultAssert.IsSuccess(await userManager.RemoveFromRole(user.Id, roles[2].Name)); + Assert.False(await userManager.IsInRole(user.Id, roles[2].Name)); + } + + [Fact] + public async Task CanRemoveUsersFromRole() + { + var context = CreateContext(); + var userManager = CreateManager(context); + var roleManager = CreateRoleManager(context); + IdentityUser[] users = + { + new IdentityUser("1"), new IdentityUser("2"), new IdentityUser("3"), + new IdentityUser("4") + }; + foreach (var u in users) + { + IdentityResultAssert.IsSuccess(await userManager.Create(u)); + } + var r = new IdentityRole("r1"); + IdentityResultAssert.IsSuccess(await roleManager.Create(r)); + foreach (var u in users) + { + IdentityResultAssert.IsSuccess(await userManager.AddToRole(u.Id, r.Name)); + Assert.True(await userManager.IsInRole(u.Id, r.Name)); + } + foreach (var u in users) + { + IdentityResultAssert.IsSuccess(await userManager.RemoveFromRole(u.Id, r.Name)); + Assert.False(await userManager.IsInRole(u.Id, r.Name)); + } + } + + [Fact] + public async Task RemoveUserNotInRoleFails() + { + var context = CreateContext(); + var userMgr = CreateManager(context); + var roleMgr = CreateRoleManager(context); + var role = new IdentityRole("addUserDupeTest"); + var user = new IdentityUser("user1"); + IdentityResultAssert.IsSuccess(await userMgr.Create(user)); + IdentityResultAssert.IsSuccess(await roleMgr.Create(role)); + var result = await userMgr.RemoveFromRole(user.Id, role.Name); + IdentityResultAssert.IsFailure(result, "User is not in role."); + } + + [Fact] + public async Task AddUserToRoleFailsIfAlreadyInRole() + { + var context = CreateContext(); + var userMgr = CreateManager(context); + var roleMgr = CreateRoleManager(context); + var role = new IdentityRole("addUserDupeTest"); + var user = new IdentityUser("user1"); + IdentityResultAssert.IsSuccess(await userMgr.Create(user)); + IdentityResultAssert.IsSuccess(await roleMgr.Create(role)); + IdentityResultAssert.IsSuccess(await userMgr.AddToRole(user.Id, role.Name)); + Assert.True(await userMgr.IsInRole(user.Id, role.Name)); + IdentityResultAssert.IsFailure(await userMgr.AddToRole(user.Id, role.Name), "User already in role."); + } + + [Fact] + public async Task CanFindRoleByNameWithManager() + { + var roleMgr = CreateRoleManager(); + var role = new IdentityRole("findRoleByNameTest"); + IdentityResultAssert.IsSuccess(await roleMgr.Create(role)); + Assert.Equal(role.Id, (await roleMgr.FindByName(role.Name)).Id); + } + + [Fact] + public async Task CanFindRoleWithManager() + { + var roleMgr = CreateRoleManager(); + var role = new IdentityRole("findRoleTest"); + IdentityResultAssert.IsSuccess(await roleMgr.Create(role)); + Assert.Equal(role, await roleMgr.FindById(role.Id)); + } + + [Fact] + public async Task SetPhoneNumberTest() + { + var manager = CreateManager(); + var userName = "PhoneTest"; + var user = new IdentityUser(userName); + user.PhoneNumber = "123-456-7890"; + IdentityResultAssert.IsSuccess(await manager.Create(user)); + var stamp = await manager.GetSecurityStamp(user.Id); + Assert.Equal(await manager.GetPhoneNumber(user.Id), "123-456-7890"); + IdentityResultAssert.IsSuccess(await manager.SetPhoneNumber(user.Id, "111-111-1111")); + Assert.Equal(await manager.GetPhoneNumber(user.Id), "111-111-1111"); + Assert.NotEqual(stamp, user.SecurityStamp); + } + + [Fact] + public async Task CanChangePhoneNumber() + { + var manager = CreateManager(); + const string userName = "PhoneTest"; + var user = new IdentityUser(userName) { PhoneNumber = "123-456-7890" }; + IdentityResultAssert.IsSuccess(await manager.Create(user)); + Assert.False(await manager.IsPhoneNumberConfirmed(user.Id)); + var stamp = await manager.GetSecurityStamp(user.Id); + var token1 = await manager.GenerateChangePhoneNumberToken(user.Id, "111-111-1111"); + IdentityResultAssert.IsSuccess(await manager.ChangePhoneNumber(user.Id, "111-111-1111", token1)); + Assert.True(await manager.IsPhoneNumberConfirmed(user.Id)); + Assert.Equal(await manager.GetPhoneNumber(user.Id), "111-111-1111"); + Assert.NotEqual(stamp, user.SecurityStamp); + } + + [Fact] + public async Task ChangePhoneNumberFailsWithWrongToken() + { + var manager = CreateManager(); + const string userName = "PhoneTest"; + var user = new IdentityUser(userName) { PhoneNumber = "123-456-7890" }; + IdentityResultAssert.IsSuccess(await manager.Create(user)); + Assert.False(await manager.IsPhoneNumberConfirmed(user.Id)); + var stamp = await manager.GetSecurityStamp(user.Id); + IdentityResultAssert.IsFailure(await manager.ChangePhoneNumber(user.Id, "111-111-1111", "bogus"), + "Invalid token."); + Assert.False(await manager.IsPhoneNumberConfirmed(user.Id)); + Assert.Equal(await manager.GetPhoneNumber(user.Id), "123-456-7890"); + Assert.Equal(stamp, user.SecurityStamp); + } + + [Fact] + public async Task CanVerifyPhoneNumber() + { + var manager = CreateManager(); + const string userName = "VerifyPhoneTest"; + var user = new IdentityUser(userName); + IdentityResultAssert.IsSuccess(await manager.Create(user)); + const string num1 = "111-123-4567"; + const string num2 = "111-111-1111"; + var token1 = await manager.GenerateChangePhoneNumberToken(user.Id, num1); + var token2 = await manager.GenerateChangePhoneNumberToken(user.Id, num2); + Assert.NotEqual(token1, token2); + Assert.True(await manager.VerifyChangePhoneNumberToken(user.Id, token1, num1)); + Assert.True(await manager.VerifyChangePhoneNumberToken(user.Id, token2, num2)); + Assert.False(await manager.VerifyChangePhoneNumberToken(user.Id, token2, num1)); + Assert.False(await manager.VerifyChangePhoneNumberToken(user.Id, token1, num2)); + } + + private class EmailTokenProvider : IUserTokenProvider + { + public Task Generate(string purpose, UserManager manager, IdentityUser user, CancellationToken token) + { + return Task.FromResult(MakeToken(purpose)); + } + + public Task Validate(string purpose, string token, UserManager manager, + IdentityUser user, CancellationToken cancellationToken) + { + return Task.FromResult(token == MakeToken(purpose)); + } + + public Task Notify(string token, UserManager manager, IdentityUser user, CancellationToken cancellationToken) + { + return manager.SendEmail(user.Id, token, token); + } + + public async Task IsValidProviderForUser(UserManager manager, IdentityUser user, CancellationToken token) + { + return !string.IsNullOrEmpty(await manager.GetEmail(user.Id)); + } + + private static string MakeToken(string purpose) + { + return "email:" + purpose; + } + } + + private class SmsTokenProvider : IUserTokenProvider + { + public Task Generate(string purpose, UserManager manager, IdentityUser user, CancellationToken token) + { + return Task.FromResult(MakeToken(purpose)); + } + + public Task Validate(string purpose, string token, UserManager manager, + IdentityUser user, CancellationToken cancellationToken) + { + return Task.FromResult(token == MakeToken(purpose)); + } + + public Task Notify(string token, UserManager manager, IdentityUser user, CancellationToken cancellationToken) + { + return manager.SendSms(user.Id, token); + } + + public async Task IsValidProviderForUser(UserManager manager, IdentityUser user, CancellationToken token) + { + return !string.IsNullOrEmpty(await manager.GetPhoneNumber(user.Id)); + } + + private static string MakeToken(string purpose) + { + return "sms:" + purpose; + } + } + + [Fact] + public async Task CanEmailTwoFactorToken() + { + var manager = CreateManager(); + var messageService = new TestMessageService(); + manager.EmailService = messageService; + const string factorId = "EmailCode"; + manager.RegisterTwoFactorProvider(factorId, new EmailTokenProvider()); + var user = new IdentityUser("EmailCodeTest") { Email = "foo@foo.com" }; + const string password = "password"; + IdentityResultAssert.IsSuccess(await manager.Create(user, password)); + var stamp = user.SecurityStamp; + Assert.NotNull(stamp); + var token = await manager.GenerateTwoFactorToken(user.Id, factorId); + Assert.NotNull(token); + Assert.Null(messageService.Message); + IdentityResultAssert.IsSuccess(await manager.NotifyTwoFactorToken(user.Id, factorId, token)); + Assert.NotNull(messageService.Message); + Assert.Equal(token, messageService.Message.Subject); + Assert.Equal(token, messageService.Message.Body); + Assert.True(await manager.VerifyTwoFactorToken(user.Id, factorId, token)); + } + + [Fact] + public async Task NotifyWithUnknownProviderFails() + { + var manager = CreateManager(); + var user = new IdentityUser("NotifyFail"); + IdentityResultAssert.IsSuccess(await manager.Create(user)); + await + ExceptionAssert.ThrowsAsync( + async () => await manager.NotifyTwoFactorToken(user.Id, "Bogus", "token"), + "No IUserTwoFactorProvider for 'Bogus' is registered."); + } + + + //[Fact] + //public async Task EmailTokenFactorWithFormatTest() + //{ + // var manager = CreateManager(); + // var messageService = new TestMessageService(); + // manager.EmailService = messageService; + // const string factorId = "EmailCode"; + // manager.RegisterTwoFactorProvider(factorId, new EmailTokenProvider + // { + // Subject = "Security Code", + // BodyFormat = "Your code is: {0}" + // }); + // var user = new IdentityUser("EmailCodeTest") { Email = "foo@foo.com" }; + // const string password = "password"; + // IdentityResultAssert.IsSuccess(await manager.Create(user, password)); + // var stamp = user.SecurityStamp; + // Assert.NotNull(stamp); + // var token = await manager.GenerateTwoFactorToken(user.Id, factorId); + // Assert.NotNull(token); + // Assert.Null(messageService.Message); + // IdentityResultAssert.IsSuccess(await manager.NotifyTwoFactorToken(user.Id, factorId, token)); + // Assert.NotNull(messageService.Message); + // Assert.Equal("Security Code", messageService.Message.Subject); + // Assert.Equal("Your code is: " + token, messageService.Message.Body); + // Assert.True(await manager.VerifyTwoFactorToken(user.Id, factorId, token)); + //} + + //[Fact] + //public async Task EmailFactorFailsAfterSecurityStampChangeTest() + //{ + // var manager = CreateManager(); + // const string factorId = "EmailCode"; + // manager.RegisterTwoFactorProvider(factorId, new EmailTokenProvider()); + // var user = new IdentityUser("EmailCodeTest") { Email = "foo@foo.com" }; + // IdentityResultAssert.IsSuccess(await manager.Create(user)); + // var stamp = user.SecurityStamp; + // Assert.NotNull(stamp); + // var token = await manager.GenerateTwoFactorToken(user.Id, factorId); + // Assert.NotNull(token); + // IdentityResultAssert.IsSuccess(await manager.UpdateSecurityStamp(user.Id)); + // Assert.False(await manager.VerifyTwoFactorToken(user.Id, factorId, token)); + //} + + [Fact] + public async Task EnableTwoFactorChangesSecurityStamp() + { + var manager = CreateManager(); + var user = new IdentityUser("TwoFactorEnabledTest"); + IdentityResultAssert.IsSuccess(await manager.Create(user)); + var stamp = user.SecurityStamp; + Assert.NotNull(stamp); + IdentityResultAssert.IsSuccess(await manager.SetTwoFactorEnabled(user.Id, true)); + Assert.NotEqual(stamp, await manager.GetSecurityStamp(user.Id)); + Assert.True(await manager.GetTwoFactorEnabled(user.Id)); + } + + [Fact] + public async Task CanSendSms() + { + var manager = CreateManager(); + var messageService = new TestMessageService(); + manager.SmsService = messageService; + var user = new IdentityUser("SmsTest") { PhoneNumber = "4251234567" }; + IdentityResultAssert.IsSuccess(await manager.Create(user)); + await manager.SendSms(user.Id, "Hi"); + Assert.NotNull(messageService.Message); + Assert.Equal("Hi", messageService.Message.Body); + } + + [Fact] + public async Task CanSendEmail() + { + var manager = CreateManager(); + var messageService = new TestMessageService(); + manager.EmailService = messageService; + var user = new IdentityUser("EmailTest") { Email = "foo@foo.com" }; + IdentityResultAssert.IsSuccess(await manager.Create(user)); + await manager.SendEmail(user.Id, "Hi", "Body"); + Assert.NotNull(messageService.Message); + Assert.Equal("Hi", messageService.Message.Subject); + Assert.Equal("Body", messageService.Message.Body); + } + + [Fact] + public async Task CanSmsTwoFactorToken() + { + var manager = CreateManager(); + var messageService = new TestMessageService(); + manager.SmsService = messageService; + const string factorId = "PhoneCode"; + manager.RegisterTwoFactorProvider(factorId, new SmsTokenProvider()); + var user = new IdentityUser("PhoneCodeTest") { PhoneNumber = "4251234567" }; + IdentityResultAssert.IsSuccess(await manager.Create(user)); + var stamp = user.SecurityStamp; + Assert.NotNull(stamp); + var token = await manager.GenerateTwoFactorToken(user.Id, factorId); + Assert.NotNull(token); + Assert.Null(messageService.Message); + IdentityResultAssert.IsSuccess(await manager.NotifyTwoFactorToken(user.Id, factorId, token)); + Assert.NotNull(messageService.Message); + Assert.Equal(token, messageService.Message.Body); + Assert.True(await manager.VerifyTwoFactorToken(user.Id, factorId, token)); + } + + //[Fact] + //public async Task PhoneTokenFactorFormatTest() + //{ + // var manager = CreateManager(); + // var messageService = new TestMessageService(); + // manager.SmsService = messageService; + // const string factorId = "PhoneCode"; + // manager.RegisterTwoFactorProvider(factorId, new PhoneNumberTokenProvider + // { + // MessageFormat = "Your code is: {0}" + // }); + // var user = new IdentityUser("PhoneCodeTest") { PhoneNumber = "4251234567" }; + // IdentityResultAssert.IsSuccess(await manager.Create(user)); + // var stamp = user.SecurityStamp; + // Assert.NotNull(stamp); + // var token = await manager.GenerateTwoFactorToken(user.Id, factorId); + // Assert.NotNull(token); + // Assert.Null(messageService.Message); + // IdentityResultAssert.IsSuccess(await manager.NotifyTwoFactorToken(user.Id, factorId, token)); + // Assert.NotNull(messageService.Message); + // Assert.Equal("Your code is: " + token, messageService.Message.Body); + // Assert.True(await manager.VerifyTwoFactorToken(user.Id, factorId, token)); + //} + + [Fact] + public async Task GenerateTwoFactorWithUnknownFactorProviderWillThrow() + { + var manager = CreateManager(); + var user = new IdentityUser("PhoneCodeTest"); + IdentityResultAssert.IsSuccess(await manager.Create(user)); + const string error = "No IUserTwoFactorProvider for 'bogus' is registered."; + await + ExceptionAssert.ThrowsAsync( + () => manager.GenerateTwoFactorToken(user.Id, "bogus"), error); + await ExceptionAssert.ThrowsAsync( + () => manager.VerifyTwoFactorToken(user.Id, "bogus", "bogus"), error); + } + + [Fact] + public async Task GetValidTwoFactorTestEmptyWithNoProviders() + { + var manager = CreateManager(); + var user = new IdentityUser("test"); + IdentityResultAssert.IsSuccess(await manager.Create(user)); + var factors = await manager.GetValidTwoFactorProviders(user.Id); + Assert.NotNull(factors); + Assert.True(!factors.Any()); + } + + [Fact] + public async Task GetValidTwoFactorTest() + { + var manager = CreateManager(); + manager.RegisterTwoFactorProvider("phone", new SmsTokenProvider()); + manager.RegisterTwoFactorProvider("email", new EmailTokenProvider()); + var user = new IdentityUser("test"); + IdentityResultAssert.IsSuccess(await manager.Create(user)); + var factors = await manager.GetValidTwoFactorProviders(user.Id); + Assert.NotNull(factors); + Assert.True(!factors.Any()); + IdentityResultAssert.IsSuccess(await manager.SetPhoneNumber(user.Id, "111-111-1111")); + factors = await manager.GetValidTwoFactorProviders(user.Id); + Assert.NotNull(factors); + Assert.True(factors.Count() == 1); + Assert.Equal("phone", factors[0]); + IdentityResultAssert.IsSuccess(await manager.SetEmail(user.Id, "test@test.com")); + factors = await manager.GetValidTwoFactorProviders(user.Id); + Assert.NotNull(factors); + Assert.True(factors.Count() == 2); + IdentityResultAssert.IsSuccess(await manager.SetEmail(user.Id, null)); + factors = await manager.GetValidTwoFactorProviders(user.Id); + Assert.NotNull(factors); + Assert.True(factors.Count() == 1); + Assert.Equal("phone", factors[0]); + } + + //[Fact] + //public async Task PhoneFactorFailsAfterSecurityStampChangeTest() + //{ + // var manager = CreateManager(); + // var factorId = "PhoneCode"; + // manager.RegisterTwoFactorProvider(factorId, new PhoneNumberTokenProvider()); + // var user = new IdentityUser("PhoneCodeTest"); + // user.PhoneNumber = "4251234567"; + // IdentityResultAssert.IsSuccess(await manager.Create(user)); + // var stamp = user.SecurityStamp; + // Assert.NotNull(stamp); + // var token = await manager.GenerateTwoFactorToken(user.Id, factorId); + // Assert.NotNull(token); + // IdentityResultAssert.IsSuccess(await manager.UpdateSecurityStamp(user.Id)); + // Assert.False(await manager.VerifyTwoFactorToken(user.Id, factorId, token)); + //} + + [Fact] + public async Task VerifyTokenFromWrongTokenProviderFails() + { + var manager = CreateManager(); + manager.RegisterTwoFactorProvider("PhoneCode", new SmsTokenProvider()); + manager.RegisterTwoFactorProvider("EmailCode", new EmailTokenProvider()); + var user = new IdentityUser("WrongTokenProviderTest") { PhoneNumber = "4251234567" }; + IdentityResultAssert.IsSuccess(await manager.Create(user)); + var token = await manager.GenerateTwoFactorToken(user.Id, "PhoneCode"); + Assert.NotNull(token); + Assert.False(await manager.VerifyTwoFactorToken(user.Id, "EmailCode", token)); + } + + [Fact] + public async Task VerifyWithWrongSmsTokenFails() + { + var manager = CreateManager(); + const string factorId = "PhoneCode"; + manager.RegisterTwoFactorProvider(factorId, new SmsTokenProvider()); + var user = new IdentityUser("PhoneCodeTest") { PhoneNumber = "4251234567" }; + IdentityResultAssert.IsSuccess(await manager.Create(user)); + Assert.False(await manager.VerifyTwoFactorToken(user.Id, factorId, "bogus")); + } + + private class DataStoreConfig : ContextConfiguration + { + private readonly DataStore _store; + + public DataStoreConfig(DataStore store) + { + _store = store; + } + + public override DataStore DataStore + { + get { return _store; } + } + + } + + private static EntityContext CreateContext() + { + var configuration = new EntityConfigurationBuilder() + //.UseModel(model) + .UseDataStore(new InMemoryDataStore()) + .BuildConfiguration(); + + var db = new IdentityContext(configuration); + // var sql = db.Configuration.DataStore as SqlServerDataStore; + // if (sql != null) + // { + //#if NET45 + // var builder = new DbConnectionStringBuilder {ConnectionString = sql.ConnectionString}; + // var targetDatabase = builder["Database"].ToString(); + + // // Connect to master, check if database exists, and create if not + // builder.Add("Database", "master"); + // using (var masterConnection = new SqlConnection(builder.ConnectionString)) + // { + // masterConnection.Open(); + + // var masterCommand = masterConnection.CreateCommand(); + // masterCommand.CommandText = "SELECT COUNT(*) FROM sys.databases WHERE [name]=N'" + targetDatabase + + // "'"; + // if ((int?) masterCommand.ExecuteScalar() < 1) + // { + // masterCommand.CommandText = "CREATE DATABASE [" + targetDatabase + "]"; + // masterCommand.ExecuteNonQuery(); + + // using (var conn = new SqlConnection(sql.ConnectionString)) + // { + // conn.Open(); + // var command = conn.CreateCommand(); + // command.CommandText = @" + //CREATE TABLE [dbo].[AspNetUsers] ( + //[Id] NVARCHAR (128) NOT NULL, + //[Email] NVARCHAR (256) NULL, + //[EmailConfirmed] BIT NOT NULL, + //[PasswordHash] NVARCHAR (MAX) NULL, + //[SecurityStamp] NVARCHAR (MAX) NULL, + //[PhoneNumber] NVARCHAR (MAX) NULL, + //[PhoneNumberConfirmed] BIT NOT NULL, + //[TwoFactorEnabled] BIT NOT NULL, + //[LockoutEndDateUtc] DATETIME NULL, + //[LockoutEnabled] BIT NOT NULL, + //[AccessFailedCount] INT NOT NULL, + //[UserName] NVARCHAR (256) NOT NULL + //) "; + // //CONSTRAINT [PK_dbo.AspNetUsers] PRIMARY KEY CLUSTERED ([Id] ASC) + // command.ExecuteNonQuery(); + // } + // } + // } + //#else + // throw new NotSupportedException("SQL Server is not yet supported when running against K10."); + //#endif + //} + + + // TODO: Create DB? + return db; + } + + + private static UserManager CreateManager(EntityContext context) + { + return new UserManager(new UserStore(context)); + } + + private static UserManager CreateManager() + { + return CreateManager(CreateContext()); + } + + private static RoleManager CreateRoleManager(EntityContext context) + { + return new RoleManager(new RoleStore(context)); + } + + private static RoleManager CreateRoleManager() + { + return CreateRoleManager(CreateContext()); + } + + public class TestMessageService : IIdentityMessageService + { + public IdentityMessage Message { get; set; } + + public Task Send(IdentityMessage message, CancellationToken token) + { + Message = message; + return Task.FromResult(0); + } + } + } +} diff --git a/test/Microsoft.AspNet.Identity.Entity.Test/project.json b/test/Microsoft.AspNet.Identity.Entity.Test/project.json new file mode 100644 index 0000000000..4ac280645e --- /dev/null +++ b/test/Microsoft.AspNet.Identity.Entity.Test/project.json @@ -0,0 +1,54 @@ +{ + "version": "0.1-alpha-*", + "dependencies": { + "Microsoft.Bcl.Immutable": "1.1.18-beta-*", + "Microsoft.AspNet.DependencyInjection": "0.1-alpha-*", + "Microsoft.AspNet.ConfigurationModel": "0.1-alpha-*", + "Microsoft.AspNet.Identity": "0.1-alpha-*", + "Microsoft.AspNet.Identity.Entity": "0.1-alpha-*", + "Microsoft.AspNet.DependencyInjection": "0.1-alpha-*", + "Microsoft.AspNet.Logging": "0.1-alpha-*", + "Microsoft.AspNet.Security.DataProtection": "0.1-alpha-*", + "Microsoft.AspNet.Testing": "0.1-alpha-*", + "Microsoft.Data.Entity": "0.1-alpha-*", + "Microsoft.Data.InMemory": "0.1-alpha-*", + "Microsoft.Data.Relational": "0.1-alpha-*", + "Microsoft.Data.SqlServer": "0.1-pre-*", + "System.Security.Claims": "0.1-alpha-*", + "Xunit.KRunner": "0.1-alpha-*", + "xunit.abstractions": "2.0.0-aspnet-*", + "xunit.assert": "2.0.0-aspnet-*", + "xunit.core": "2.0.0-aspnet-*", + "xunit.execution": "2.0.0-aspnet-*" + }, + "configurations": { + "net45": { + "dependencies": { + "System.Runtime": "", + "System.Collections": "", + "System.Data": "" + } + }, + "k10": { + "dependencies": { + "System.Collections": "4.0.0.0", + "System.ComponentModel": "4.0.0.0", + "System.Diagnostics.Debug": "4.0.10.0", + "System.Diagnostics.Tools": "4.0.0.0", + "System.Globalization": "4.0.10.0", + "System.Linq": "4.0.0.0", + "System.Linq.Expressions": "4.0.0.0", + "System.Linq.Queryable": "4.0.0.0", + "System.Reflection": "4.0.10.0", + "System.Resources.ResourceManager": "4.0.0.0", + "System.Runtime": "4.0.20.0", + "System.Runtime.Extensions": "4.0.10.0", + "System.Security.Principal": "4.0.0.0", + "System.Text.Encoding": "4.0.20.0", + "System.Threading.Tasks": "4.0.10.0" + } + } + }, + "commands": { + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryStoreTest.cs b/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryStoreTest.cs index 95dac2ba94..871a3ac419 100644 --- a/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryStoreTest.cs +++ b/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryStoreTest.cs @@ -1413,7 +1413,7 @@ namespace Microsoft.AspNet.Identity.InMemory.Test private static RoleManager CreateRoleManager() { - return new RoleManager(new InMemoryRoleStore()); + return new RoleManager(new InMemoryRoleStore()); } public class TestMessageService : IIdentityMessageService diff --git a/test/Microsoft.AspNet.Identity.Test/TestServices.cs b/test/Microsoft.AspNet.Identity.Test/TestServices.cs index 19caa4ee0f..65706213cf 100644 --- a/test/Microsoft.AspNet.Identity.Test/TestServices.cs +++ b/test/Microsoft.AspNet.Identity.Test/TestServices.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using Microsoft.AspNet.DependencyInjection; using Microsoft.AspNet.DependencyInjection.Fallback; @@ -7,51 +8,17 @@ namespace Microsoft.AspNet.Identity.Test { public static class TestServices { - public static IServiceProvider DefaultServiceProvider() - where TUser : class, IUser - where TKey : IEquatable - { - var serviceCollection = new ServiceCollection {DefaultServices()}; - return serviceCollection.BuildServiceProvider(); - } - public static IEnumerable DefaultServices() where TUser : class, IUser where TKey : IEquatable { - return new IServiceDescriptor[] - { - new ServiceDescriptor(), - new ServiceDescriptor, UserValidator>(), - new ServiceDescriptor(), - new ServiceDescriptor, ClaimsIdentityFactory>(), - new ServiceDescriptor, NoopUserStore>() - }; + var describer = new ServiceDescriber(); + yield return describer.Transient(); + yield return describer.Transient, UserValidator>(); + yield return describer.Transient(); + yield return describer.Transient, ClaimsIdentityFactory>(); + yield return describer.Transient, NoopUserStore>(); } - public class ServiceDescriptor : IServiceDescriptor - { - public ServiceDescriptor(LifecycleKind lifecycle = LifecycleKind.Transient) - { - Lifecycle = lifecycle; - } - - public LifecycleKind Lifecycle { get; private set; } - - public Type ServiceType - { - get { return typeof (TService); } - } - - public Type ImplementationType - { - get { return typeof (TImplementation); } - } - - public object ImplementationInstance - { - get { return null; } - } - } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs b/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs index 42f0dc546d..ea9788c953 100644 --- a/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Security.Claims; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNet.DependencyInjection.Fallback; using Microsoft.AspNet.Testing; using Moq; using Xunit; @@ -22,7 +23,7 @@ namespace Microsoft.AspNet.Identity.Test [Fact] public void ServiceProviderWireupTest() { - var manager = new TestManager(TestServices.DefaultServiceProvider()); + var manager = new TestManager(TestServices.DefaultServices().BuildServiceProvider()); Assert.NotNull(manager.PasswordHasher); Assert.NotNull(manager.PasswordValidator); Assert.NotNull(manager.UserValidator);