From 31f76b8d58a865db2bff7a0f1d4367ce0a3a286e Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Fri, 2 May 2014 17:09:24 -0700 Subject: [PATCH] Initial DI changes from app building Incorporate feedback and make identity DI friendly everywhere Also start using the new OptionsSetup --- .../IdentityBuilderExtensions.cs | 19 + .../IdentityContext.cs | 5 +- .../Microsoft.AspNet.Identity.Entity.kproj | 12 +- .../RoleStore.cs | 5 + .../project.json | 2 +- .../IdentityBuilderExtensions.cs | 19 + .../Microsoft.AspNet.Identity.InMemory.kproj | 3 + .../project.json | 3 - .../DefaultAuthenticationTypes.cs | 2 +- .../Microsoft.AspNet.Identity.Security.kproj | 5 + .../SignInManager.cs | 49 +- .../ClaimTypeOptions.cs | 60 + .../ClaimsIdentityFactory.cs | 47 +- .../IPasswordValidator.cs | 4 +- .../IdentityBuilder.cs | 86 +- .../IdentityOptions.cs | 89 + .../IdentityServiceCollectionExtensions.cs | 6 +- .../IdentityServices.cs | 22 +- .../LockoutOptions.cs | 42 + .../Microsoft.AspNet.Identity.kproj | 13 + .../PasswordOptions.cs | 59 + .../PasswordValidator.cs | 55 +- src/Microsoft.AspNet.Identity/RoleManager.cs | 34 +- src/Microsoft.AspNet.Identity/UserManager.cs | 91 +- src/Microsoft.AspNet.Identity/UserOptions.cs | 38 + .../UserValidator.cs | 22 +- src/Microsoft.AspNet.Identity/project.json | 5 +- ...icrosoft.AspNet.Identity.Entity.Test.kproj | 3 + .../RoleStoreTest.cs | 52 + .../StartupTest.cs | 121 ++ .../TestIdentityFactory.cs | 105 +- .../UserStoreTest.cs | 131 +- .../project.json | 9 +- .../InMemoryStoreTest.cs | 1495 +---------------- ...rosoft.AspNet.Identity.InMemory.Test.kproj | 4 + .../StartupTest.cs | 80 +- .../project.json | 4 +- ...rosoft.AspNet.Identity.Security.Test.kproj | 4 + .../SignInManagerTest.cs | 259 ++- .../project.json | 15 +- .../ClaimsIdentityFactoryTest.cs | 16 +- .../IdentityBuilderTest.cs | 44 +- .../IdentityOptionsTest.cs | 125 ++ .../Microsoft.AspNet.Identity.Test.kproj | 5 +- .../PasswordValidatorTest.cs | 57 +- .../RoleManagerTest.cs | 22 +- .../RoleValidatorTest.cs | 6 +- .../UserManagerTest.cs | 111 +- .../UserValidatorTest.cs | 13 +- .../project.json | 1 + test/Shared/IdentityConfig.cs | 19 + .../IdentityResultAssert.cs | 0 test/Shared/MockHelpers.cs | 33 + test/Shared/TestMessageService.cs | 17 + .../TestRole.cs | 0 .../TestUser.cs | 2 +- test/Shared/UserManagerTestBase.cs | 1462 ++++++++++++++++ 57 files changed, 2907 insertions(+), 2105 deletions(-) create mode 100644 src/Microsoft.AspNet.Identity.Entity/IdentityBuilderExtensions.cs create mode 100644 src/Microsoft.AspNet.Identity.InMemory/IdentityBuilderExtensions.cs create mode 100644 src/Microsoft.AspNet.Identity/ClaimTypeOptions.cs create mode 100644 src/Microsoft.AspNet.Identity/IdentityOptions.cs create mode 100644 src/Microsoft.AspNet.Identity/LockoutOptions.cs create mode 100644 src/Microsoft.AspNet.Identity/PasswordOptions.cs create mode 100644 src/Microsoft.AspNet.Identity/UserOptions.cs create mode 100644 test/Microsoft.AspNet.Identity.Entity.Test/StartupTest.cs create mode 100644 test/Microsoft.AspNet.Identity.Test/IdentityOptionsTest.cs create mode 100644 test/Shared/IdentityConfig.cs rename test/{Microsoft.AspNet.Identity.Test => Shared}/IdentityResultAssert.cs (100%) create mode 100644 test/Shared/MockHelpers.cs create mode 100644 test/Shared/TestMessageService.cs rename test/{Microsoft.AspNet.Identity.Test => Shared}/TestRole.cs (100%) rename test/{Microsoft.AspNet.Identity.Test => Shared}/TestUser.cs (95%) create mode 100644 test/Shared/UserManagerTestBase.cs diff --git a/src/Microsoft.AspNet.Identity.Entity/IdentityBuilderExtensions.cs b/src/Microsoft.AspNet.Identity.Entity/IdentityBuilderExtensions.cs new file mode 100644 index 0000000000..93411053e4 --- /dev/null +++ b/src/Microsoft.AspNet.Identity.Entity/IdentityBuilderExtensions.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNet.Identity.Entity; +using Microsoft.AspNet.DependencyInjection; + +namespace Microsoft.AspNet.Identity +{ + public static class IdentityBuilderExtensions + { + public static IdentityBuilder AddEntity(this IdentityBuilder builder) + where TUser : EntityUser + where TRole : EntityRole + { + builder.Services.AddScoped, UserStore>(); + builder.Services.AddScoped, UserManager>(); + builder.Services.AddScoped, RoleStore>(); + builder.Services.AddScoped, RoleManager>(); + return builder; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity.Entity/IdentityContext.cs b/src/Microsoft.AspNet.Identity.Entity/IdentityContext.cs index bc89b22e91..2bce131c80 100644 --- a/src/Microsoft.AspNet.Identity.Entity/IdentityContext.cs +++ b/src/Microsoft.AspNet.Identity.Entity/IdentityContext.cs @@ -19,6 +19,7 @@ using System; using Microsoft.AspNet.DependencyInjection; using Microsoft.AspNet.DependencyInjection.Fallback; using Microsoft.Data.Entity; +using Microsoft.Data.SqlServer; using Microsoft.Data.InMemory; using Microsoft.Data.Entity.Metadata; @@ -33,7 +34,7 @@ namespace Microsoft.AspNet.Identity.Entity public class IdentityContext : DbContext where TUser : EntityUser - where TRole : EntityRole /*, TUserRole*/ + where TRole : EntityRole where TUserLogin : IdentityUserLogin where TUserRole : IdentityUserRole where TUserClaim : IdentityUserClaim @@ -51,7 +52,7 @@ namespace Microsoft.AspNet.Identity.Entity protected override void OnConfiguring(EntityConfigurationBuilder builder) { //#if NET45 -// builder.SqlServerConnectionString(@"Server=(localdb)\v11.0;Database=IdentityDb;Trusted_Connection=True;"); +// builder.SqlServerConnectionString(@"Server=(localdb)\v11.0;Database=IdentityDb3;Trusted_Connection=True;"); //#else builder.UseInMemoryStore(); //#endif diff --git a/src/Microsoft.AspNet.Identity.Entity/Microsoft.AspNet.Identity.Entity.kproj b/src/Microsoft.AspNet.Identity.Entity/Microsoft.AspNet.Identity.Entity.kproj index 6910b8ed44..1a4cd762eb 100644 --- a/src/Microsoft.AspNet.Identity.Entity/Microsoft.AspNet.Identity.Entity.kproj +++ b/src/Microsoft.AspNet.Identity.Entity/Microsoft.AspNet.Identity.Entity.kproj @@ -20,20 +20,14 @@ -<<<<<<< HEAD - - - - - - -======= ->>>>>>> POCO + + + \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity.Entity/RoleStore.cs b/src/Microsoft.AspNet.Identity.Entity/RoleStore.cs index 821b0637a9..6f27c08ef8 100644 --- a/src/Microsoft.AspNet.Identity.Entity/RoleStore.cs +++ b/src/Microsoft.AspNet.Identity.Entity/RoleStore.cs @@ -24,6 +24,11 @@ using Microsoft.Data.Entity; namespace Microsoft.AspNet.Identity.Entity { + public class RoleStore : RoleStore where TRole : EntityRole + { + public RoleStore(DbContext context) : base(context) { } + } + public class RoleStore : IQueryableRoleStore where TRole : EntityRole diff --git a/src/Microsoft.AspNet.Identity.Entity/project.json b/src/Microsoft.AspNet.Identity.Entity/project.json index ee99bd301e..21f07f02b4 100644 --- a/src/Microsoft.AspNet.Identity.Entity/project.json +++ b/src/Microsoft.AspNet.Identity.Entity/project.json @@ -4,9 +4,9 @@ "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-alpha-*", "Microsoft.Data.InMemory": "0.1-alpha-*", "System.Security.Claims" : "0.1-alpha-*" }, diff --git a/src/Microsoft.AspNet.Identity.InMemory/IdentityBuilderExtensions.cs b/src/Microsoft.AspNet.Identity.InMemory/IdentityBuilderExtensions.cs new file mode 100644 index 0000000000..7949445f41 --- /dev/null +++ b/src/Microsoft.AspNet.Identity.InMemory/IdentityBuilderExtensions.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNet.Identity.InMemory; +using Microsoft.AspNet.DependencyInjection; + +namespace Microsoft.AspNet.Identity +{ + public static class IdentityBuilderExtensions + { + public static IdentityBuilder AddInMemory(this IdentityBuilder builder) + where TUser : IdentityUser + where TRole : IdentityRole + { + builder.Services.AddScoped, InMemoryUserStore>(); + builder.Services.AddScoped, UserManager>(); + builder.Services.AddScoped, InMemoryRoleStore>(); + builder.Services.AddScoped, RoleManager>(); + return builder; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity.InMemory/Microsoft.AspNet.Identity.InMemory.kproj b/src/Microsoft.AspNet.Identity.InMemory/Microsoft.AspNet.Identity.InMemory.kproj index f4f1b108f4..1792939c5f 100644 --- a/src/Microsoft.AspNet.Identity.InMemory/Microsoft.AspNet.Identity.InMemory.kproj +++ b/src/Microsoft.AspNet.Identity.InMemory/Microsoft.AspNet.Identity.InMemory.kproj @@ -23,5 +23,8 @@ + + + \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity.InMemory/project.json b/src/Microsoft.AspNet.Identity.InMemory/project.json index 6edbc05b35..0f7de96e9f 100644 --- a/src/Microsoft.AspNet.Identity.InMemory/project.json +++ b/src/Microsoft.AspNet.Identity.InMemory/project.json @@ -2,10 +2,7 @@ "version": "0.1-alpha-*", "dependencies": { "Microsoft.AspNet.Identity": "0.1-alpha-*", - "Microsoft.AspNet.Abstractions" : "0.1-alpha-*", - "Microsoft.AspNet.ConfigurationModel" : "0.1-alpha-*", "Microsoft.AspNet.DependencyInjection" : "0.1-alpha-*", - "Microsoft.AspNet.RequestContainer" : "0.1-alpha-*", "System.Security.Claims": "0.1-alpha-*" }, "configurations": { diff --git a/src/Microsoft.AspNet.Identity.Security/DefaultAuthenticationTypes.cs b/src/Microsoft.AspNet.Identity.Security/DefaultAuthenticationTypes.cs index e537057f2f..3778a7b268 100644 --- a/src/Microsoft.AspNet.Identity.Security/DefaultAuthenticationTypes.cs +++ b/src/Microsoft.AspNet.Identity.Security/DefaultAuthenticationTypes.cs @@ -19,6 +19,6 @@ namespace Microsoft.AspNet.Identity.Security { public static class DefaultAuthenticationTypes { - public const string ApplicationCookie = "Microsoft.Aspnet.Identity.Security.Application"; + public static readonly string ApplicationCookie = typeof(DefaultAuthenticationTypes).Namespace+".Application"; } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity.Security/Microsoft.AspNet.Identity.Security.kproj b/src/Microsoft.AspNet.Identity.Security/Microsoft.AspNet.Identity.Security.kproj index 846a185477..44e592c920 100644 --- a/src/Microsoft.AspNet.Identity.Security/Microsoft.AspNet.Identity.Security.kproj +++ b/src/Microsoft.AspNet.Identity.Security/Microsoft.AspNet.Identity.Security.kproj @@ -20,8 +20,13 @@ + + + + + \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity.Security/SignInManager.cs b/src/Microsoft.AspNet.Identity.Security/SignInManager.cs index 9905028f12..9fbe478ffa 100644 --- a/src/Microsoft.AspNet.Identity.Security/SignInManager.cs +++ b/src/Microsoft.AspNet.Identity.Security/SignInManager.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Open Technologies, Inc. +// Copyright (c) Microsoft Open Technologies, Inc. // All Rights Reserved // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,13 +17,37 @@ using Microsoft.AspNet.Abstractions; using Microsoft.AspNet.Abstractions.Security; +using Microsoft.AspNet.DependencyInjection; +using System; using System.Security.Claims; using System.Threading.Tasks; namespace Microsoft.AspNet.Identity.Security { - public class SignInManager where TUser : class + public class SignInManager : SignInManager, TUser> where TUser : class { + public SignInManager(UserManager userManager, IContextAccessor contextAccessor) + : base(userManager, contextAccessor) { } + } + + + public class SignInManager where TManager : UserManager where TUser : class + { + public SignInManager(TManager userManager, IContextAccessor contextAccessor) + { + if (userManager == null) + { + throw new ArgumentNullException("userManager"); + } + if (contextAccessor == null || contextAccessor.Value == null) + { + throw new ArgumentNullException("contextAccessor"); + } + UserManager = userManager; + Context = contextAccessor.Value; + } + + // TODO: this should go into some kind of Options/setup private string _authType; public string AuthenticationType { @@ -31,25 +55,16 @@ namespace Microsoft.AspNet.Identity.Security set { _authType = value; } } - public UserManager UserManager { get; set; } - public HttpContext Context { get; set; } + public TManager UserManager { get; private set; } + public HttpContext Context { get; private set; } public virtual async Task CreateUserIdentityAsync(TUser user) { - if (UserManager == null) - { - return null; - } return await UserManager.CreateIdentityAsync(user, AuthenticationType); } public virtual async Task SignInAsync(TUser user, bool isPersistent, bool rememberBrowser) { - if (Context == null) - { - return; - } - // TODO: all the two factor logic/external/rememberBrowser var userIdentity = await CreateUserIdentityAsync(user); Context.Response.SignIn(userIdentity, new AuthenticationProperties { IsPersistent = isPersistent }); @@ -57,10 +72,6 @@ namespace Microsoft.AspNet.Identity.Security public virtual void SignOut() { - if (Context == null) - { - return; - } Context.Response.SignOut(AuthenticationType); } @@ -151,10 +162,6 @@ namespace Microsoft.AspNet.Identity.Security public virtual async Task PasswordSignInAsync(string userName, string password, bool isPersistent, bool shouldLockout) { - if (UserManager == null) - { - return SignInStatus.Failure; - } var user = await UserManager.FindByNameAsync(userName); if (user == null) { diff --git a/src/Microsoft.AspNet.Identity/ClaimTypeOptions.cs b/src/Microsoft.AspNet.Identity/ClaimTypeOptions.cs new file mode 100644 index 0000000000..3b4a5579ae --- /dev/null +++ b/src/Microsoft.AspNet.Identity/ClaimTypeOptions.cs @@ -0,0 +1,60 @@ +using System; +using System.ComponentModel; +using System.Security.Claims; +using Microsoft.AspNet.ConfigurationModel; + +namespace Microsoft.AspNet.Identity +{ + public class ClaimTypeOptions + { + /// + /// ClaimType used for the security stamp by default + /// + public static readonly string DefaultSecurityStampClaimType = "AspNet.Identity.SecurityStamp"; + + public ClaimTypeOptions() + { + Role = ClaimTypes.Role; + SecurityStamp = DefaultSecurityStampClaimType; + UserId = ClaimTypes.NameIdentifier; + UserName = ClaimTypes.Name; + } + + public ClaimTypeOptions(IConfiguration config) : this() + { + IdentityOptions.Read(this, config); + } + + /// + /// Claim type used for role claims + /// + public string Role { get; set; } + + /// + /// Claim type used for the user name + /// + public string UserName { get; set; } + + /// + /// Claim type used for the user id + /// + public string UserId { get; set; } + + /// + /// Claim type used for the user security stamp + /// + public string SecurityStamp { get; set; } + + public virtual void Copy(ClaimTypeOptions options) + { + if (options == null) + { + return; + } + Role = options.Role; + SecurityStamp = options.SecurityStamp; + UserId = options.UserId; + UserName = options.UserName; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/ClaimsIdentityFactory.cs b/src/Microsoft.AspNet.Identity/ClaimsIdentityFactory.cs index 0e744b60c9..52fc686dc5 100644 --- a/src/Microsoft.AspNet.Identity/ClaimsIdentityFactory.cs +++ b/src/Microsoft.AspNet.Identity/ClaimsIdentityFactory.cs @@ -19,6 +19,7 @@ using System; using System.Security.Claims; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNet.DependencyInjection; namespace Microsoft.AspNet.Identity { @@ -29,42 +30,6 @@ namespace Microsoft.AspNet.Identity public class ClaimsIdentityFactory : IClaimsIdentityFactory where TUser : class { - /// - /// ClaimType used for the security stamp by default - /// - public const string DefaultSecurityStampClaimType = "AspNet.Identity.SecurityStamp"; - - /// - /// Constructor - /// - public ClaimsIdentityFactory() - { - RoleClaimType = ClaimsIdentity.DefaultRoleClaimType; - UserIdClaimType = ClaimTypes.NameIdentifier; - UserNameClaimType = ClaimsIdentity.DefaultNameClaimType; - SecurityStampClaimType = DefaultSecurityStampClaimType; - } - - /// - /// Claim type used for role claims - /// - public string RoleClaimType { get; set; } - - /// - /// Claim type used for the user name - /// - public string UserNameClaimType { get; set; } - - /// - /// Claim type used for the user id - /// - public string UserIdClaimType { get; set; } - - /// - /// Claim type used for the user security stamp - /// - public string SecurityStampClaimType { get; set; } - /// /// CreateAsync a ClaimsIdentity from a user /// @@ -86,19 +51,19 @@ namespace Microsoft.AspNet.Identity } var userId = await manager.GetUserIdAsync(user, cancellationToken); var userName = await manager.GetUserNameAsync(user, cancellationToken); - var id = new ClaimsIdentity(authenticationType, UserNameClaimType, RoleClaimType); - id.AddClaim(new Claim(UserIdClaimType, userId)); - id.AddClaim(new Claim(UserNameClaimType, userName, ClaimValueTypes.String)); + var id = new ClaimsIdentity(authenticationType, manager.Options.ClaimType.UserName, manager.Options.ClaimType.Role); + id.AddClaim(new Claim(manager.Options.ClaimType.UserId, userId)); + id.AddClaim(new Claim(manager.Options.ClaimType.UserName, userName, ClaimValueTypes.String)); if (manager.SupportsUserSecurityStamp) { - id.AddClaim(new Claim(SecurityStampClaimType, await manager.GetSecurityStampAsync(user, cancellationToken))); + id.AddClaim(new Claim(manager.Options.ClaimType.SecurityStamp, await manager.GetSecurityStampAsync(user, cancellationToken))); } if (manager.SupportsUserRole) { var roles = await manager.GetRolesAsync(user, cancellationToken); foreach (var roleName in roles) { - id.AddClaim(new Claim(RoleClaimType, roleName, ClaimValueTypes.String)); + id.AddClaim(new Claim(manager.Options.ClaimType.Role, roleName, ClaimValueTypes.String)); } } if (manager.SupportsUserClaim) diff --git a/src/Microsoft.AspNet.Identity/IPasswordValidator.cs b/src/Microsoft.AspNet.Identity/IPasswordValidator.cs index db46d75cfb..a06fb6f4fb 100644 --- a/src/Microsoft.AspNet.Identity/IPasswordValidator.cs +++ b/src/Microsoft.AspNet.Identity/IPasswordValidator.cs @@ -23,12 +23,12 @@ namespace Microsoft.AspNet.Identity /// /// Used to validate passwords /// - public interface IPasswordValidator + public interface IPasswordValidator where TUser : class { /// /// Validate the item /// /// - Task ValidateAsync(string password, CancellationToken cancellationToken = default(CancellationToken)); + Task ValidateAsync(string password, UserManager manager, CancellationToken cancellationToken = default(CancellationToken)); } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/IdentityBuilder.cs b/src/Microsoft.AspNet.Identity/IdentityBuilder.cs index 37c6b82775..61d539a0ee 100644 --- a/src/Microsoft.AspNet.Identity/IdentityBuilder.cs +++ b/src/Microsoft.AspNet.Identity/IdentityBuilder.cs @@ -22,55 +22,82 @@ namespace Microsoft.AspNet.Identity { public class IdentityBuilder where TUser : class where TRole : class { - private ServiceCollection Services { get; set; } + public ServiceCollection Services { get; private set; } public IdentityBuilder(ServiceCollection services) { Services = services; } - public IdentityBuilder Use(Func func) + // Rename to Add + + public IdentityBuilder AddInstance(Func func) { - Services.AddInstance(func()); + Services.AddInstance(func()); return this; } - public IdentityBuilder UseIdentity() + public IdentityBuilder AddUserStore(Func> func) { - Services.Add(IdentityServices.GetDefaultUserServices()); - Services.Add(IdentityServices.GetDefaultRoleServices()); + return AddInstance(func); + } + + public IdentityBuilder AddRoleStore(Func> func) + { + return AddInstance(func); + } + + public IdentityBuilder AddPasswordValidator(Func> func) + { + return AddInstance(func); + } + + public IdentityBuilder AddUserValidator(Func> func) + { + return AddInstance(func); + } + + public class OptionsSetup : IOptionsSetup + { + public Action SetupAction { get; private set; } + + public OptionsSetup(Action setupAction) + { + if (setupAction == null) + { + throw new ArgumentNullException("setupAction"); + } + SetupAction = setupAction; + } + + public void Setup(TOptions options) + { + SetupAction(options); + } + + public int Order { get; set; } + } + + public IdentityBuilder SetupOptions(Action action, int order) + { + Services.AddSetup(new OptionsSetup(action) {Order = order}); return this; } - public IdentityBuilder UseUserStore(Func> func) + public IdentityBuilder SetupOptions(Action action) { - return Use(func); + return SetupOptions(action, 0); } - public IdentityBuilder UseRoleStore(Func> func) + public IdentityBuilder AddUserManager() where TManager : UserManager { - return Use(func); - } - - public IdentityBuilder UsePasswordValidator(Func func) - { - return Use(func); - } - - public IdentityBuilder UseUserValidator(Func> func) - { - return Use(func); - } - - public IdentityBuilder UseUserManager() where TManager : UserManager - { - Services.AddScoped(); + Services.AddScoped(); return this; } - public IdentityBuilder UseRoleManager() where TManager : RoleManager + public IdentityBuilder AddRoleManager() where TManager : RoleManager { - Services.AddScoped(); + Services.AddScoped(); return this; } @@ -79,10 +106,5 @@ namespace Microsoft.AspNet.Identity // return Use(func); //} - public IdentityBuilder UseLockoutPolicy(Func func) - { - return Use(func); - } - } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/IdentityOptions.cs b/src/Microsoft.AspNet.Identity/IdentityOptions.cs new file mode 100644 index 0000000000..e0d536a960 --- /dev/null +++ b/src/Microsoft.AspNet.Identity/IdentityOptions.cs @@ -0,0 +1,89 @@ +using System.Reflection; +using Microsoft.AspNet.ConfigurationModel; + +namespace Microsoft.AspNet.Identity +{ + /// + /// Configuration for lockout + /// + public class IdentityOptions + { + + public IdentityOptions() + { + // TODO: Split into sub options + ClaimType = new ClaimTypeOptions(); + User = new UserOptions(); + Password = new PasswordOptions(); + Lockout = new LockoutOptions(); + } + + public IdentityOptions(IConfiguration config) + { + ClaimType = new ClaimTypeOptions(config.GetSubKey("identity:claimtype")); + User = new UserOptions(config.GetSubKey("identity:user")); + Password = new PasswordOptions(config.GetSubKey("identity:password")); + //Lockout = new LockoutOptions(config.GetSubKey("identity:lockout")); + } + + public static void Read(object obj, IConfiguration config) + { + var type = obj.GetType(); + var props = type.GetTypeInfo().DeclaredProperties; + foreach (var prop in props) + { + // TODO: handle non string types? + if (!prop.CanWrite) + { + continue; + } + var configValue = config.Get(prop.Name); + if (configValue == null) + { + continue; + } + if (prop.PropertyType == typeof(string)) + { + prop.SetValue(obj, configValue); + } + else if (prop.PropertyType == typeof(int)) + { + // todo: TryParse/ errors? + prop.SetValue(obj, int.Parse(configValue)); + } + else if (prop.PropertyType == typeof(bool)) + { + // todo: TryParse/ errors? + prop.SetValue(obj, bool.Parse(configValue)); + } + //else if (prop.PropertyType == typeof(TimeSpan)) + //{ + // // todo: TryParse/ errors? + // prop.SetValue(obj, TimeSpan.Parse(configValue)); + //} + } + } + + + public ClaimTypeOptions ClaimType { get; set; } + + public UserOptions User { get; set; } + + public PasswordOptions Password { get; set; } + + public LockoutOptions Lockout { get; set; } + + // TODO: maybe make this internal as its only for tests + public void Copy(IdentityOptions options) + { + if (options == null) + { + return; + } + User.Copy(options.User); + Password.Copy(options.Password); + Lockout.Copy(options.Lockout); + ClaimType.Copy(options.ClaimType); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/IdentityServiceCollectionExtensions.cs b/src/Microsoft.AspNet.Identity/IdentityServiceCollectionExtensions.cs index 19774d2864..ac565f8b1c 100644 --- a/src/Microsoft.AspNet.Identity/IdentityServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNet.Identity/IdentityServiceCollectionExtensions.cs @@ -28,6 +28,7 @@ namespace Microsoft.AspNet.DependencyInjection { services.Add(IdentityServices.GetDefaultUserServices()); services.Add(IdentityServices.GetDefaultRoleServices()); + services.AddSingleton, OptionsAccessor>(); actionBuilder(new IdentityBuilder(services)); return services; } @@ -35,10 +36,7 @@ namespace Microsoft.AspNet.DependencyInjection public static ServiceCollection AddIdentity(this ServiceCollection services, Action> actionBuilder) where TUser : class { - services.Add(IdentityServices.GetDefaultUserServices()); - services.Add(IdentityServices.GetDefaultRoleServices()); - actionBuilder(new IdentityBuilder(services)); - return services; + return services.AddIdentity(actionBuilder); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/IdentityServices.cs b/src/Microsoft.AspNet.Identity/IdentityServices.cs index 07fa8cae7d..e6e946b62d 100644 --- a/src/Microsoft.AspNet.Identity/IdentityServices.cs +++ b/src/Microsoft.AspNet.Identity/IdentityServices.cs @@ -38,26 +38,12 @@ namespace Microsoft.AspNet.Identity var describe = new ServiceDescriber(configuration); // TODO: review defaults for validators should get picked up from config? - yield return describe.Instance>(new UserValidator()); - yield return describe.Instance(new PasswordValidator() - { - RequiredLength = 6, - RequireDigit = true, - RequireLowercase = true, - RequireNonLetterOrDigit = true, - RequireUppercase = true - }); - yield return describe.Instance(new PasswordHasher()); - yield return describe.Instance>(new ClaimsIdentityFactory()); - yield return describe.Instance(new LockoutPolicy - { - UserLockoutEnabledByDefault = false, - DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5), - MaxFailedAccessAttemptsBeforeLockout = 5 - }); + yield return describe.Transient, UserValidator>(); + yield return describe.Transient, PasswordValidator>(); + yield return describe.Transient(); + yield return describe.Transient, ClaimsIdentityFactory>(); // TODO: rationalize email/sms/usertoken services - // TODO: configure lockout from config? } public static IEnumerable GetDefaultRoleServices() where TRole : class diff --git a/src/Microsoft.AspNet.Identity/LockoutOptions.cs b/src/Microsoft.AspNet.Identity/LockoutOptions.cs new file mode 100644 index 0000000000..7dcca2ebfd --- /dev/null +++ b/src/Microsoft.AspNet.Identity/LockoutOptions.cs @@ -0,0 +1,42 @@ +using System; + +namespace Microsoft.AspNet.Identity +{ + public class LockoutOptions + { + public LockoutOptions() + { + DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5); + EnabledByDefault = false; + MaxFailedAccessAttempts = 5; + } + public void Copy(LockoutOptions options) + { + if (options == null) + { + return; + } + + DefaultLockoutTimeSpan = options.DefaultLockoutTimeSpan; + EnabledByDefault = options.EnabledByDefault; + MaxFailedAccessAttempts = options.MaxFailedAccessAttempts; + } + + /// + /// If true, will enable user lockout when users are created + /// + public bool EnabledByDefault { get; set; } + + + /// + /// Number of access attempts allowed for a user before lockout (if enabled) + /// + public int MaxFailedAccessAttempts { get; set; } + + + /// + /// Default amount of time an user is locked out for after MaxFailedAccessAttempsBeforeLockout is reached + /// + public TimeSpan DefaultLockoutTimeSpan { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/Microsoft.AspNet.Identity.kproj b/src/Microsoft.AspNet.Identity/Microsoft.AspNet.Identity.kproj index 109a27ebc3..a161751b0f 100644 --- a/src/Microsoft.AspNet.Identity/Microsoft.AspNet.Identity.kproj +++ b/src/Microsoft.AspNet.Identity/Microsoft.AspNet.Identity.kproj @@ -24,8 +24,18 @@ + + + + + + + + + + @@ -56,5 +66,8 @@ + + + \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/PasswordOptions.cs b/src/Microsoft.AspNet.Identity/PasswordOptions.cs new file mode 100644 index 0000000000..945e058298 --- /dev/null +++ b/src/Microsoft.AspNet.Identity/PasswordOptions.cs @@ -0,0 +1,59 @@ +using Microsoft.AspNet.ConfigurationModel; + +namespace Microsoft.AspNet.Identity +{ + public class PasswordOptions + { + public PasswordOptions() + { + RequireDigit = true; + RequireLowercase = true; + RequireNonLetterOrDigit = true; + RequireUppercase = true; + RequiredLength = 6; + } + + public PasswordOptions(IConfiguration config) : this() + { + IdentityOptions.Read(this, config); + } + + /// + /// Minimum required length + /// + public int RequiredLength { get; set; } + + /// + /// Require a non letter or digit character + /// + public bool RequireNonLetterOrDigit { get; set; } + + /// + /// Require a lower case letter ('a' - 'z') + /// + public bool RequireLowercase { get; set; } + + /// + /// Require an upper case letter ('A' - 'Z') + /// + public bool RequireUppercase { get; set; } + + /// + /// Require a digit ('0' - '9') + /// + public bool RequireDigit { get; set; } + + public virtual void Copy(PasswordOptions options) + { + if (options == null) + { + return; + } + RequireDigit = options.RequireDigit; + RequireLowercase = options.RequireLowercase; + RequireNonLetterOrDigit = options.RequireNonLetterOrDigit; + RequireUppercase = options.RequireUppercase; + RequiredLength = options.RequiredLength; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/PasswordValidator.cs b/src/Microsoft.AspNet.Identity/PasswordValidator.cs index ca6966229e..55a329fd30 100644 --- a/src/Microsoft.AspNet.Identity/PasswordValidator.cs +++ b/src/Microsoft.AspNet.Identity/PasswordValidator.cs @@ -27,63 +27,44 @@ namespace Microsoft.AspNet.Identity /// /// Used to validate some basic password policy like length and number of non alphanumerics /// - public class PasswordValidator : IPasswordValidator + public class PasswordValidator : IPasswordValidator where TUser : class { /// - /// Minimum required length + /// Ensures that the password is of the required length and meets the configured requirements /// - public int RequiredLength { get; set; } - - /// - /// Require a non letter or digit character - /// - public bool RequireNonLetterOrDigit { get; set; } - - /// - /// Require a lower case letter ('a' - 'z') - /// - public bool RequireLowercase { get; set; } - - /// - /// Require an upper case letter ('A' - 'Z') - /// - public bool RequireUppercase { get; set; } - - /// - /// Require a digit ('0' - '9') - /// - public bool RequireDigit { get; set; } - - /// - /// Ensures that the string is of the required length and meets the configured requirements - /// - /// + /// + /// /// /// - public virtual Task ValidateAsync(string item, CancellationToken cancellationToken = default(CancellationToken)) + public virtual Task ValidateAsync(string password, UserManager manager, CancellationToken cancellationToken = default(CancellationToken)) { - if (item == null) + if (password == null) { - throw new ArgumentNullException("item"); + throw new ArgumentNullException("password"); + } + if (manager == null) + { + throw new ArgumentNullException("manager"); } var errors = new List(); - if (string.IsNullOrWhiteSpace(item) || item.Length < RequiredLength) + var options = manager.Options.Password; + if (string.IsNullOrWhiteSpace(password) || password.Length < options.RequiredLength) { - errors.Add(String.Format(CultureInfo.CurrentCulture, Resources.PasswordTooShort, RequiredLength)); + errors.Add(String.Format(CultureInfo.CurrentCulture, Resources.PasswordTooShort, options.RequiredLength)); } - if (RequireNonLetterOrDigit && item.All(IsLetterOrDigit)) + if (options.RequireNonLetterOrDigit && password.All(IsLetterOrDigit)) { errors.Add(Resources.PasswordRequireNonLetterOrDigit); } - if (RequireDigit && !item.Any(IsDigit)) + if (options.RequireDigit && !password.Any(IsDigit)) { errors.Add(Resources.PasswordRequireDigit); } - if (RequireLowercase && !item.Any(IsLower)) + if (options.RequireLowercase && !password.Any(IsLower)) { errors.Add(Resources.PasswordRequireLower); } - if (RequireUppercase && !item.Any(IsUpper)) + if (options.RequireUppercase && !password.Any(IsUpper)) { errors.Add(Resources.PasswordRequireUpper); } diff --git a/src/Microsoft.AspNet.Identity/RoleManager.cs b/src/Microsoft.AspNet.Identity/RoleManager.cs index 53d204259c..a487edae04 100644 --- a/src/Microsoft.AspNet.Identity/RoleManager.cs +++ b/src/Microsoft.AspNet.Identity/RoleManager.cs @@ -32,24 +32,23 @@ namespace Microsoft.AspNet.Identity { private bool _disposed; - public RoleManager(IServiceProvider services) - { - Initialize(services); - } - /// - /// Constructor + /// Constructor /// + /// /// The IRoleStore is responsible for commiting changes via the UpdateAsync/CreateAsync methods - public RoleManager(IRoleStore store) + public RoleManager(IServiceProvider services, IRoleStore store) { if (store == null) { throw new ArgumentNullException("store"); } - var services = new ServiceCollection { IdentityServices.GetDefaultRoleServices() }; - services.AddInstance>(store); - Initialize(services.BuildServiceProvider()); + if (services == null) + { + throw new ArgumentNullException("services"); + } + RoleValidator = services.GetService>() ?? new RoleValidator(); + Store = store; } /// @@ -90,21 +89,6 @@ namespace Microsoft.AspNet.Identity } } - public void Initialize(IServiceProvider services) - { - if (services == null) - { - throw new ArgumentNullException("services"); - } - Store = services.GetService>(); - if (Store == null) - { - // TODO: what is the right way to enforce required services - throw new InvalidOperationException(); - } - RoleValidator = services.GetService>(); - } - /// /// Dispose this object /// diff --git a/src/Microsoft.AspNet.Identity/UserManager.cs b/src/Microsoft.AspNet.Identity/UserManager.cs index 89fc45b6cc..0603d2ec19 100644 --- a/src/Microsoft.AspNet.Identity/UserManager.cs +++ b/src/Microsoft.AspNet.Identity/UserManager.cs @@ -41,52 +41,34 @@ namespace Microsoft.AspNet.Identity private TimeSpan _defaultLockout = TimeSpan.Zero; private bool _disposed; private IPasswordHasher _passwordHasher; - private LockoutPolicy _lockoutPolicy; - - // Needed for mock unit tests - public UserManager() { } + private IdentityOptions _options; /// - /// Constructor which takes a service provider + /// Constructor which takes a service provider and user store /// /// - public UserManager(IServiceProvider serviceProvider) - { - Initialize(serviceProvider); - } - - /// - /// Constructor - /// - /// The IUserStore is responsible for commiting changes via the UpdateAsync/CreateAsync methods - public UserManager(IUserStore store) - { - if (store == null) - { - throw new ArgumentNullException("store"); - } - var services = new ServiceCollection { IdentityServices.GetDefaultUserServices() }; - services.AddInstance>(store); - Initialize(services.BuildServiceProvider()); - } - - public void Initialize(IServiceProvider serviceProvider) + /// + /// + public UserManager(IServiceProvider serviceProvider, IUserStore store, IOptionsAccessor optionsAccessor) { if (serviceProvider == null) { throw new ArgumentNullException("serviceProvider"); } - PasswordHasher = serviceProvider.GetService(); - UserValidator = serviceProvider.GetService>(); - PasswordValidator = serviceProvider.GetService(); - ClaimsIdentityFactory = serviceProvider.GetService>(); - LockoutPolicy = serviceProvider.GetService(); - Store = serviceProvider.GetService>(); - if (Store == null) + if (store == null) { - // TODO: what is the right way to enforce required services - throw new InvalidOperationException(); + throw new ArgumentNullException("store"); } + if (optionsAccessor == null || optionsAccessor.Options == null) + { + throw new ArgumentNullException("optionsAccessor"); + } + Store = store; + Options = optionsAccessor.Options; + PasswordHasher = serviceProvider.GetService() ?? new PasswordHasher(); + UserValidator = serviceProvider.GetService>() ?? new UserValidator(); + PasswordValidator = serviceProvider.GetService>() ?? new PasswordValidator(); + ClaimsIdentityFactory = serviceProvider.GetService>() ?? new ClaimsIdentityFactory(); // TODO: Email/Sms/Token services } @@ -124,7 +106,7 @@ namespace Microsoft.AspNet.Identity /// /// Used to validate passwords before persisting changes /// - public IPasswordValidator PasswordValidator { get; set; } + public IPasswordValidator PasswordValidator { get; set; } /// /// Used to create claims identities from users @@ -162,30 +144,23 @@ namespace Microsoft.AspNet.Identity /// public IUserTokenProvider UserTokenProvider { get; set; } - public LockoutPolicy LockoutPolicy { get; set; } - - private bool UserLockoutEnabledByDefault + public IdentityOptions Options { get { - return LockoutPolicy != null && LockoutPolicy.UserLockoutEnabledByDefault; + ThrowIfDisposed(); + return _options; } - } - - private int MaxFailedAccessAttemptsBeforeLockout - { - get + set { - return LockoutPolicy != null ? LockoutPolicy.MaxFailedAccessAttemptsBeforeLockout : 0; - } - } - - private TimeSpan DefaultAccountLockoutTimeSpan - { - get - { - return LockoutPolicy != null ? LockoutPolicy.DefaultAccountLockoutTimeSpan : TimeSpan.FromMinutes(5); + ThrowIfDisposed(); + if (value == null) + { + throw new ArgumentNullException("value"); + } + _options = value; } + } /// @@ -380,7 +355,7 @@ namespace Microsoft.AspNet.Identity { return result; } - if (UserLockoutEnabledByDefault && SupportsUserLockout) + if (Options.Lockout.EnabledByDefault && SupportsUserLockout) { await GetUserLockoutStore().SetLockoutEnabledAsync(user, true, cancellationToken); } @@ -673,7 +648,7 @@ namespace Microsoft.AspNet.Identity { if (PasswordValidator != null) { - var result = await PasswordValidator.ValidateAsync(newPassword, cancellationToken); + var result = await PasswordValidator.ValidateAsync(newPassword, this, cancellationToken); if (!result.Succeeded) { return result; @@ -1757,12 +1732,12 @@ namespace Microsoft.AspNet.Identity } // If this puts the user over the threshold for lockout, lock them out and reset the access failed count var count = await store.IncrementAccessFailedCountAsync(user, cancellationToken); - if (count < MaxFailedAccessAttemptsBeforeLockout) + if (count < Options.Lockout.MaxFailedAccessAttempts) { return await UpdateAsync(user, cancellationToken); } await - store.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow.Add(DefaultAccountLockoutTimeSpan), cancellationToken); + store.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow.Add(Options.Lockout.DefaultLockoutTimeSpan), cancellationToken); await store.ResetAccessFailedCountAsync(user, cancellationToken); return await UpdateAsync(user, cancellationToken); } diff --git a/src/Microsoft.AspNet.Identity/UserOptions.cs b/src/Microsoft.AspNet.Identity/UserOptions.cs new file mode 100644 index 0000000000..94349965d0 --- /dev/null +++ b/src/Microsoft.AspNet.Identity/UserOptions.cs @@ -0,0 +1,38 @@ +using Microsoft.AspNet.ConfigurationModel; + +namespace Microsoft.AspNet.Identity +{ + public class UserOptions + { + public UserOptions() + { + AllowOnlyAlphanumericNames = true; + //User.RequireUniqueEmail = true; // TODO: app decision? + } + + public UserOptions(IConfiguration config) : this() + { + IdentityOptions.Read(this, config); + } + + /// + /// Only allow [A-Za-z0-9@_] in UserNames + /// + public bool AllowOnlyAlphanumericNames { get; set; } + + /// + /// If set, enforces that emails are non empty, valid, and unique + /// + public bool RequireUniqueEmail { get; set; } + + public virtual void Copy(UserOptions options) + { + if (options == null) + { + return; + } + AllowOnlyAlphanumericNames = options.AllowOnlyAlphanumericNames; + RequireUniqueEmail = options.RequireUniqueEmail; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/UserValidator.cs b/src/Microsoft.AspNet.Identity/UserValidator.cs index 472cb4cc6b..ce885cb7a2 100644 --- a/src/Microsoft.AspNet.Identity/UserValidator.cs +++ b/src/Microsoft.AspNet.Identity/UserValidator.cs @@ -33,24 +33,6 @@ namespace Microsoft.AspNet.Identity /// public class UserValidator : IUserValidator where TUser : class { - /// - /// Constructor - /// - public UserValidator() - { - AllowOnlyAlphanumericUserNames = true; - } - - /// - /// Only allow [A-Za-z0-9@_] in UserNames - /// - public bool AllowOnlyAlphanumericUserNames { get; set; } - - /// - /// If set, enforces that emails are non empty, valid, and unique - /// - public bool RequireUniqueEmail { get; set; } - /// /// Validates a user before saving /// @@ -70,7 +52,7 @@ namespace Microsoft.AspNet.Identity } var errors = new List(); await ValidateUserName(manager, user, errors); - if (RequireUniqueEmail) + if (manager.Options.User.RequireUniqueEmail) { await ValidateEmail(manager, user, errors); } @@ -126,7 +108,7 @@ namespace Microsoft.AspNet.Identity { errors.Add(String.Format(CultureInfo.CurrentCulture, Resources.PropertyTooShort, "UserName")); } - else if (AllowOnlyAlphanumericUserNames && !userName.All(IsAlphaNumeric)) + else if (manager.Options.User.AllowOnlyAlphanumericNames && !userName.All(IsAlphaNumeric)) { // If any characters are not letters or digits, its an illegal user name errors.Add(String.Format(CultureInfo.CurrentCulture, Resources.InvalidUserName, userName)); diff --git a/src/Microsoft.AspNet.Identity/project.json b/src/Microsoft.AspNet.Identity/project.json index 3158a62019..8e49b41c9b 100644 --- a/src/Microsoft.AspNet.Identity/project.json +++ b/src/Microsoft.AspNet.Identity/project.json @@ -3,9 +3,9 @@ "dependencies": { "Microsoft.AspNet.ConfigurationModel": "0.1-alpha-*", "Microsoft.AspNet.DependencyInjection" : "0.1-alpha-*", - "System.Security.Claims" : "0.1-alpha-*", "Microsoft.AspNet.HttpFeature" : "0.1-alpha-*", - "Microsoft.AspNet.Security.DataProtection" : "0.1-alpha-*" + "Microsoft.AspNet.Security.DataProtection" : "0.1-alpha-*", + "System.Security.Claims" : "0.1-alpha-*" }, "configurations": { "net45": {}, @@ -20,6 +20,7 @@ "System.Linq.Expressions": "4.0.0.0", "System.Linq.Queryable": "4.0.0.0", "System.Reflection": "4.0.10.0", + "System.Reflection.Extensions": "4.0.0.0", "System.Resources.ResourceManager": "4.0.0.0", "System.Runtime": "4.0.20.0", "System.Runtime.Extensions": "4.0.10.0", diff --git a/test/Microsoft.AspNet.Identity.Entity.Test/Microsoft.AspNet.Identity.Entity.Test.kproj b/test/Microsoft.AspNet.Identity.Entity.Test/Microsoft.AspNet.Identity.Entity.Test.kproj index 70eaf7fdb2..17600b2a61 100644 --- a/test/Microsoft.AspNet.Identity.Entity.Test/Microsoft.AspNet.Identity.Entity.Test.kproj +++ b/test/Microsoft.AspNet.Identity.Entity.Test/Microsoft.AspNet.Identity.Entity.Test.kproj @@ -26,5 +26,8 @@ + + + \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.Entity.Test/RoleStoreTest.cs b/test/Microsoft.AspNet.Identity.Entity.Test/RoleStoreTest.cs index 35c751d663..8d4cd6cad2 100644 --- a/test/Microsoft.AspNet.Identity.Entity.Test/RoleStoreTest.cs +++ b/test/Microsoft.AspNet.Identity.Entity.Test/RoleStoreTest.cs @@ -17,12 +17,64 @@ using System; using System.Threading.Tasks; +using Microsoft.AspNet.DependencyInjection; +using Microsoft.AspNet.DependencyInjection.Fallback; +using Microsoft.AspNet.Identity.Test; +using Microsoft.Data.Entity; +using Microsoft.Data.InMemory; using Xunit; namespace Microsoft.AspNet.Identity.Entity.Test { public class RoleStoreTest { + class ApplicationRoleManager : RoleManager + { + public ApplicationRoleManager(IServiceProvider services, IRoleStore store) : base(services, store) { } + } + + [Fact] + public async Task CanCreateUsingAddRoleManager() + { + var services = new ServiceCollection(); +#if NET45 + // services.AddEntityFramework(s => s.AddSqlServer()); + //#else + services.AddEntityFramework(s => s.AddInMemoryStore()); +#endif + // TODO: this should construct a new instance of InMemoryStore + var store = new RoleStore(new IdentityContext()); + services.AddIdentity(s => + { + s.AddRoleStore(() => store); + s.AddRoleManager(); + }); + + var provider = services.BuildServiceProvider(); + var manager = provider.GetService(); + Assert.NotNull(manager); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(new EntityRole("arole"))); + } + [Fact] + public async Task CanCreateRoleWithSingletonManager() + { + var services = new ServiceCollection(); +#if NET45 +// services.AddEntityFramework(s => s.AddSqlServer()); +//#else + services.AddEntityFramework(s => s.AddInMemoryStore()); +#endif + services.AddTransient(); + services.AddTransient, RoleStore>(); + //todo: services.AddSingleton, RoleManager>(); + // TODO: How to configure SqlServer? + services.AddSingleton(); + var provider = services.BuildServiceProvider(); + var manager = provider.GetService(); + Assert.NotNull(manager); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(new EntityRole("someRole"))); + } + [Fact] public async Task RoleStoreMethodsThrowWhenDisposedTest() { diff --git a/test/Microsoft.AspNet.Identity.Entity.Test/StartupTest.cs b/test/Microsoft.AspNet.Identity.Entity.Test/StartupTest.cs new file mode 100644 index 0000000000..da0568e6e3 --- /dev/null +++ b/test/Microsoft.AspNet.Identity.Entity.Test/StartupTest.cs @@ -0,0 +1,121 @@ +using Microsoft.AspNet.Abstractions; +using Microsoft.AspNet.DependencyInjection; +using Microsoft.AspNet.DependencyInjection.Fallback; +using Microsoft.AspNet.Identity.Entity; +using Microsoft.AspNet.Identity.Test; +using Microsoft.AspNet.PipelineCore; +using System; +using System.Threading.Tasks; +using Microsoft.Data.Entity; +using Xunit; + +namespace Microsoft.AspNet.Identity.Entity.Test +{ + public class StartupTest + { + public class ApplicationUser : EntityUser { } + public class ApplicationUserManager : UserManager + { + public ApplicationUserManager(IServiceProvider services, IUserStore store, IOptionsAccessor options) : base(services, store, options) { } + } + public class ApplicationRoleManager : RoleManager + { + public ApplicationRoleManager(IServiceProvider services, IRoleStore store) : base(services, store) { } + } + + + public class PasswordsNegativeLengthSetup : IOptionsSetup + { + public int Order { get { return 0; } } + public void Setup(IdentityOptions options) + { + options.Password.RequiredLength = -1; + } + } + + [Fact] + public void CanCustomizeIdentityOptions() + { + IBuilder builder = new Builder(new ServiceCollection().BuildServiceProvider()); + builder.UseServices(services => { + services.AddIdentity(identityServices => { }); + services.AddSetup(); + }); + + var setup = builder.ApplicationServices.GetService>(); + Assert.IsType(typeof(PasswordsNegativeLengthSetup), setup); + var optionsGetter = builder.ApplicationServices.GetService>(); + Assert.NotNull(optionsGetter); + setup.Setup(optionsGetter.Options); + + var myOptions = optionsGetter.Options; + Assert.True(myOptions.Password.RequireLowercase); + Assert.True(myOptions.Password.RequireDigit); + Assert.True(myOptions.Password.RequireNonLetterOrDigit); + Assert.True(myOptions.Password.RequireUppercase); + Assert.Equal(-1, myOptions.Password.RequiredLength); + } + + [Fact] + public void CanSetupIdentityOptions() + { + IBuilder app = new Builder(new ServiceCollection().BuildServiceProvider()); + app.UseServices(services => services.AddIdentity(identityServices => identityServices.SetupOptions(options => options.User.RequireUniqueEmail = true))); + + var optionsGetter = app.ApplicationServices.GetService>(); + Assert.NotNull(optionsGetter); + + var myOptions = optionsGetter.Options; + Assert.True(myOptions.User.RequireUniqueEmail); + } + + [Fact] + public async Task EnsureStartupUsageWorks() + { + IBuilder builder = new Builder(new ServiceCollection().BuildServiceProvider()); + + //builder.UseServices(services => services.AddIdentity(s => + // s.AddEntity() + //{ + + builder.UseServices(services => + { + services.AddEntityFramework(); + services.AddTransient(); + services.AddIdentity(s => + { + s.AddEntity(); + s.AddUserManager(); + s.AddRoleManager(); + }); + }); + + var userStore = builder.ApplicationServices.GetService>(); + var roleStore = builder.ApplicationServices.GetService>(); + var userManager = builder.ApplicationServices.GetService(); + //TODO: var userManager = builder.ApplicationServices.GetService(); + var roleManager = builder.ApplicationServices.GetService(); + + Assert.NotNull(userStore); + Assert.NotNull(userManager); + Assert.NotNull(roleStore); + Assert.NotNull(roleManager); + + //await CreateAdminUser(builder.ApplicationServices); + } + + private static async Task CreateAdminUser(IServiceProvider serviceProvider) + { + const string userName = "admin"; + const string roleName = "Admins"; + const string password = "1qaz@WSX"; + var userManager = serviceProvider.GetService(); + var roleManager = serviceProvider.GetService(); + + var user = new ApplicationUser { UserName = userName }; + IdentityResultAssert.IsSuccess(await userManager.CreateAsync(user, password)); + IdentityResultAssert.IsSuccess(await roleManager.CreateAsync(new EntityRole { Name = roleName })); + IdentityResultAssert.IsSuccess(await userManager.AddToRoleAsync(user, roleName)); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.Entity.Test/TestIdentityFactory.cs b/test/Microsoft.AspNet.Identity.Entity.Test/TestIdentityFactory.cs index 1e5c7e661c..1043116c56 100644 --- a/test/Microsoft.AspNet.Identity.Entity.Test/TestIdentityFactory.cs +++ b/test/Microsoft.AspNet.Identity.Entity.Test/TestIdentityFactory.cs @@ -36,74 +36,63 @@ namespace Microsoft.AspNet.Identity.Entity.Test public static DbContext CreateContext() { var serviceProvider = new ServiceCollection() - .AddEntityFramework(s => s.AddInMemoryStore()) - .BuildServiceProvider(); +//#if NET45 +// .AddEntityFramework(s => s.AddSqlServer()) +//#else + .AddEntityFramework(s => s.AddInMemoryStore()) +//#endif + .BuildServiceProvider(); var db = new IdentityContext(serviceProvider); - // 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: Recreate DB, doesn't support String ID or Identity context yet + //if (!db.Database.Exists()) + //{ + // db.Database.Create(); //} - // TODO: CreateAsync DB? return db; } + public class TestSetup : IOptionsSetup + { + private readonly IdentityOptions _options; + + public TestSetup(IdentityOptions options) + { + _options = options; + } + + public int Order { get { return 0; } } + public void Setup(IdentityOptions options) + { + options.Copy(_options); + } + } + public static UserManager CreateManager(DbContext context) { - var manager = new UserManager(new UserStore(context)) + var services = new ServiceCollection(); + services.AddTransient, UserValidator>(); + services.AddTransient, PasswordValidator>(); + services.AddInstance>(new UserStore(context)); + services.AddSingleton, UserManager>(); + var options = new IdentityOptions { - UserValidator = new UserValidator(), - PasswordValidator = new PasswordValidator() + Password = new PasswordOptions + { + RequireDigit = false, + RequireLowercase = false, + RequireNonLetterOrDigit = false, + RequireUppercase = false + } }; - return manager; + var optionsAccessor = new OptionsAccessor(new[] { new TestSetup(options) }); + //services.AddInstance>(new OptionsAccessor(new[] { new TestSetup(options) })); + //return services.BuildServiceProvider().GetService>(); + return new UserManager(services.BuildServiceProvider(), new UserStore(context), optionsAccessor); } public static UserManager CreateManager() @@ -113,7 +102,11 @@ namespace Microsoft.AspNet.Identity.Entity.Test public static RoleManager CreateRoleManager(DbContext context) { - return new RoleManager(new RoleStore(context)); + var services = new ServiceCollection(); + services.AddTransient, RoleValidator>(); + services.AddInstance>(new RoleStore(context)); +// return services.BuildServiceProvider().GetService>(); + return new RoleManager(services.BuildServiceProvider(), new RoleStore(context)); } public static RoleManager CreateRoleManager() diff --git a/test/Microsoft.AspNet.Identity.Entity.Test/UserStoreTest.cs b/test/Microsoft.AspNet.Identity.Entity.Test/UserStoreTest.cs index c937f44780..04bbc9a792 100644 --- a/test/Microsoft.AspNet.Identity.Entity.Test/UserStoreTest.cs +++ b/test/Microsoft.AspNet.Identity.Entity.Test/UserStoreTest.cs @@ -17,11 +17,12 @@ using Microsoft.AspNet.DependencyInjection; using Microsoft.AspNet.DependencyInjection.Fallback; +using Microsoft.AspNet.Identity.Test; using Microsoft.AspNet.Testing; using Microsoft.Data.Entity; using Microsoft.Data.Entity.Metadata; -using Microsoft.Data.Entity.Storage; using Microsoft.Data.InMemory; +using Microsoft.Data.SqlServer; using System; using System.Linq; using System.Security.Claims; @@ -35,37 +36,49 @@ namespace Microsoft.AspNet.Identity.Entity.Test { class ApplicationUserManager : UserManager { - public ApplicationUserManager(IServiceProvider services) : base(services) { } + public ApplicationUserManager(IServiceProvider services, IUserStore store, IOptionsAccessor options) : base(services, store, options) { } } - // [Fact] Disabled - see issue #48 + [Fact] public async Task CanUseAddedManagerInstance() { var services = new ServiceCollection(); - var store = new UserStore(new IdentityContext()); - services.AddInstance>(new UserManager(store)); +#if NET45 + // services.AddEntityFramework(s => s.AddSqlServer()); + //#else + services.AddEntityFramework(s => s.AddInMemoryStore()); +#endif + services.AddSingleton, OptionsAccessor>(); + services.AddInstance(new IdentityContext()); + services.AddTransient, UserStore>(); + services.AddSingleton(); var provider = services.BuildServiceProvider(); - var manager = provider.GetService>(); + var manager = provider.GetService(); Assert.NotNull(manager); IdentityResultAssert.IsSuccess(await manager.CreateAsync(new EntityUser("hello"))); } [Fact] - public async Task CanUseSingletonManagerInstance() + public async Task CanCreateUsingAddUserManager() { var services = new ServiceCollection(); +#if NET45 + // services.AddEntityFramework(s => s.AddSqlServer()); + //#else + services.AddEntityFramework(s => s.AddInMemoryStore()); +#endif + // TODO: this needs to construct a new instance of InMemoryStore var store = new UserStore(new IdentityContext()); services.AddIdentity(s => { - s.UseUserStore(() => store); - s.UseUserManager(); + s.AddUserStore(() => store); + s.AddUserManager(); }); - //services.AddSingleton(); var provider = services.BuildServiceProvider(); var manager = provider.GetService(); Assert.NotNull(manager); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(new EntityUser("hello"))); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(new EntityUser("hello2"))); } //[Fact] @@ -146,30 +159,30 @@ namespace Microsoft.AspNet.Identity.Entity.Test Assert.ThrowsAsync("user", async () => await store.SetPasswordHashAsync(null, null)); await Assert.ThrowsAsync("user", async () => await store.GetSecurityStampAsync(null)); - await Assert.ThrowsAsync("user", + await Assert.ThrowsAsync("user", async () => await store.SetSecurityStampAsync(null, null)); - await Assert.ThrowsAsync("claim", + await Assert.ThrowsAsync("claim", async () => await store.AddClaimAsync(new EntityUser("fake"), null)); - await Assert.ThrowsAsync("claim", + await Assert.ThrowsAsync("claim", async () => await store.RemoveClaimAsync(new EntityUser("fake"), null)); - await Assert.ThrowsAsync("login", + await Assert.ThrowsAsync("login", async () => await store.AddLoginAsync(new EntityUser("fake"), null)); - await Assert.ThrowsAsync("login", + await Assert.ThrowsAsync("login", async () => await store.RemoveLoginAsync(new EntityUser("fake"), null)); await Assert.ThrowsAsync("login", async () => await store.FindByLoginAsync(null)); await Assert.ThrowsAsync("user", async () => await store.GetEmailConfirmedAsync(null)); - await Assert.ThrowsAsync("user", + await Assert.ThrowsAsync("user", async () => await store.SetEmailConfirmedAsync(null, true)); await Assert.ThrowsAsync("user", async () => await store.GetEmailAsync(null)); await Assert.ThrowsAsync("user", async () => await store.SetEmailAsync(null, null)); await Assert.ThrowsAsync("user", async () => await store.GetPhoneNumberAsync(null)); await Assert.ThrowsAsync("user", async () => await store.SetPhoneNumberAsync(null, null)); - await Assert.ThrowsAsync("user", + await Assert.ThrowsAsync("user", async () => await store.GetPhoneNumberConfirmedAsync(null)); - await Assert.ThrowsAsync("user", + await Assert.ThrowsAsync("user", async () => await store.SetPhoneNumberConfirmedAsync(null, true)); await Assert.ThrowsAsync("user", async () => await store.GetTwoFactorEnabledAsync(null)); - await Assert.ThrowsAsync("user", + await Assert.ThrowsAsync("user", async () => await store.SetTwoFactorEnabledAsync(null, true)); await Assert.ThrowsAsync("user", async () => await store.GetAccessFailedCountAsync(null)); await Assert.ThrowsAsync("user", async () => await store.GetLockoutEnabledAsync(null)); @@ -308,7 +321,7 @@ namespace Microsoft.AspNet.Identity.Entity.Test { var manager = TestIdentityFactory.CreateManager(); var user = new EntityUser("UpdateBlocked") { Email = email }; - manager.UserValidator = new UserValidator { RequireUniqueEmail = true }; + manager.Options.User.RequireUniqueEmail = true; IdentityResultAssert.IsFailure(await manager.CreateAsync(user), "Email cannot be null or empty."); } @@ -320,7 +333,7 @@ namespace Microsoft.AspNet.Identity.Entity.Test { var manager = TestIdentityFactory.CreateManager(); var user = new EntityUser("UpdateBlocked") { Email = email }; - manager.UserValidator = new UserValidator { RequireUniqueEmail = true }; + manager.Options.User.RequireUniqueEmail = true; IdentityResultAssert.IsFailure(await manager.CreateAsync(user), "Email '" + email + "' is invalid."); } #endif @@ -523,7 +536,7 @@ namespace Microsoft.AspNet.Identity.Entity.Test public async Task AddDupeEmailFallsWhenUniqueEmailRequired() { var manager = TestIdentityFactory.CreateManager(); - manager.UserValidator = new UserValidator { RequireUniqueEmail = true }; + manager.Options.User.RequireUniqueEmail = true; var user = new EntityUser("dupe") { Email = "yup@yup.com" }; var user2 = new EntityUser("dupeEmail") { Email = "yup@yup.com" }; IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); @@ -618,10 +631,10 @@ namespace Microsoft.AspNet.Identity.Entity.Test var claims = identity.Claims.ToList(); 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")); + claims.Any(c => c.Type == manager.Options.ClaimType.UserName && c.Value == user.UserName)); + Assert.True(claims.Any(c => c.Type == manager.Options.ClaimType.UserId && c.Value == user.Id.ToString())); + Assert.True(claims.Any(c => c.Type == manager.Options.ClaimType.Role && c.Value == "Admin")); + Assert.True(claims.Any(c => c.Type == manager.Options.ClaimType.Role && c.Value == "Local")); foreach (var cl in userClaims) { Assert.True(claims.Any(c => c.Type == cl.Type && c.Value == cl.Value)); @@ -790,11 +803,9 @@ namespace Microsoft.AspNet.Identity.Entity.Test public async Task SingleFailureLockout() { var mgr = TestIdentityFactory.CreateManager(); - mgr.LockoutPolicy = new LockoutPolicy - { - DefaultAccountLockoutTimeSpan = TimeSpan.FromHours(1), - UserLockoutEnabledByDefault = true, - }; + mgr.Options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromHours(1); + mgr.Options.Lockout.EnabledByDefault = true; + mgr.Options.Lockout.MaxFailedAccessAttempts = 0; var user = new EntityUser("fastLockout"); IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); Assert.True(await mgr.GetLockoutEnabledAsync(user)); @@ -810,12 +821,9 @@ namespace Microsoft.AspNet.Identity.Entity.Test public async Task TwoFailureLockout() { var mgr = TestIdentityFactory.CreateManager(); - mgr.LockoutPolicy = new LockoutPolicy - { - DefaultAccountLockoutTimeSpan = TimeSpan.FromHours(1), - UserLockoutEnabledByDefault = true, - MaxFailedAccessAttemptsBeforeLockout = 2 - }; + mgr.Options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromHours(1); + mgr.Options.Lockout.EnabledByDefault = true; + mgr.Options.Lockout.MaxFailedAccessAttempts = 2; var user = new EntityUser("twoFailureLockout"); IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); Assert.True(await mgr.GetLockoutEnabledAsync(user)); @@ -835,12 +843,9 @@ namespace Microsoft.AspNet.Identity.Entity.Test public async Task ResetAccessCountPreventsLockout() { var mgr = TestIdentityFactory.CreateManager(); - mgr.LockoutPolicy = new LockoutPolicy - { - DefaultAccountLockoutTimeSpan = TimeSpan.FromHours(1), - UserLockoutEnabledByDefault = true, - MaxFailedAccessAttemptsBeforeLockout = 2 - }; + mgr.Options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromHours(1); + mgr.Options.Lockout.EnabledByDefault = true; + mgr.Options.Lockout.MaxFailedAccessAttempts = 2; var user = new EntityUser("resetLockout"); IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); Assert.True(await mgr.GetLockoutEnabledAsync(user)); @@ -864,11 +869,8 @@ namespace Microsoft.AspNet.Identity.Entity.Test public async Task CanEnableLockoutManuallyAndLockout() { var mgr = TestIdentityFactory.CreateManager(); - mgr.LockoutPolicy = new LockoutPolicy - { - DefaultAccountLockoutTimeSpan = TimeSpan.FromHours(1), - MaxFailedAccessAttemptsBeforeLockout = 2 - }; + mgr.Options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromHours(1); + mgr.Options.Lockout.MaxFailedAccessAttempts = 2; var user = new EntityUser("manualLockout"); IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); Assert.False(await mgr.GetLockoutEnabledAsync(user)); @@ -891,10 +893,7 @@ namespace Microsoft.AspNet.Identity.Entity.Test public async Task UserNotLockedOutWithNullDateTimeAndIsSetToNullDate() { var mgr = TestIdentityFactory.CreateManager(); - mgr.LockoutPolicy = new LockoutPolicy - { - UserLockoutEnabledByDefault = true, - }; + mgr.Options.Lockout.EnabledByDefault = true; var user = new EntityUser("LockoutTest"); IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); Assert.True(await mgr.GetLockoutEnabledAsync(user)); @@ -922,10 +921,7 @@ namespace Microsoft.AspNet.Identity.Entity.Test public async Task LockoutEndToUtcNowMinus1SecInUserShouldNotBeLockedOut() { var mgr = TestIdentityFactory.CreateManager(); - mgr.LockoutPolicy = new LockoutPolicy - { - UserLockoutEnabledByDefault = true, - }; + mgr.Options.Lockout.EnabledByDefault = true; var user = new EntityUser("LockoutUtcNowTest") { LockoutEnd = DateTime.UtcNow.AddSeconds(-1) }; IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); Assert.True(await mgr.GetLockoutEnabledAsync(user)); @@ -937,10 +933,7 @@ namespace Microsoft.AspNet.Identity.Entity.Test public async Task LockoutEndToUtcNowSubOneSecondWithManagerShouldNotBeLockedOut() { var mgr = TestIdentityFactory.CreateManager(); - mgr.LockoutPolicy = new LockoutPolicy - { - UserLockoutEnabledByDefault = true, - }; + mgr.Options.Lockout.EnabledByDefault = true; var user = new EntityUser("LockoutUtcNowTest"); IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); Assert.True(await mgr.GetLockoutEnabledAsync(user)); @@ -953,10 +946,7 @@ namespace Microsoft.AspNet.Identity.Entity.Test public async Task LockoutEndToUtcNowPlus5ShouldBeLockedOut() { var mgr = TestIdentityFactory.CreateManager(); - mgr.LockoutPolicy = new LockoutPolicy - { - UserLockoutEnabledByDefault = true, - }; + mgr.Options.Lockout.EnabledByDefault = true; var user = new EntityUser("LockoutUtcNowTest") { LockoutEnd = DateTime.UtcNow.AddMinutes(5) }; IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); Assert.True(await mgr.GetLockoutEnabledAsync(user)); @@ -968,10 +958,7 @@ namespace Microsoft.AspNet.Identity.Entity.Test public async Task UserLockedOutWithDateTimeLocalKindNowPlus30() { var mgr = TestIdentityFactory.CreateManager(); - mgr.LockoutPolicy = new LockoutPolicy - { - UserLockoutEnabledByDefault = true, - }; + mgr.Options.Lockout.EnabledByDefault = true; var user = new EntityUser("LockoutTest"); IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); Assert.True(await mgr.GetLockoutEnabledAsync(user)); @@ -995,11 +982,11 @@ namespace Microsoft.AspNet.Identity.Entity.Test } private class AlwaysBadValidator : IUserValidator, IRoleValidator, - IPasswordValidator + IPasswordValidator { public const string ErrorMessage = "I'm Bad."; - public Task ValidateAsync(string password, CancellationToken token) + public Task ValidateAsync(string password, UserManager manager,CancellationToken token) { return Task.FromResult(IdentityResult.Failed(ErrorMessage)); } @@ -1326,7 +1313,7 @@ namespace Microsoft.AspNet.Identity.Entity.Test { var manager = TestIdentityFactory.CreateManager(); const string userName = "PhoneTest"; - var user = new EntityUser(userName) {PhoneNumber = "123-456-7890"}; + var user = new EntityUser(userName) { PhoneNumber = "123-456-7890" }; IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); var stamp = await manager.GetSecurityStampAsync(user); Assert.Equal(await manager.GetPhoneNumberAsync(user), "123-456-7890"); diff --git a/test/Microsoft.AspNet.Identity.Entity.Test/project.json b/test/Microsoft.AspNet.Identity.Entity.Test/project.json index 4b556b0ff2..d67dd3a3cb 100644 --- a/test/Microsoft.AspNet.Identity.Entity.Test/project.json +++ b/test/Microsoft.AspNet.Identity.Entity.Test/project.json @@ -1,9 +1,9 @@ { "version": "0.1-alpha-*", "dependencies": { - "Microsoft.Bcl.Immutable": "1.1.18-beta-*", - "Microsoft.AspNet.DependencyInjection": "0.1-alpha-*", + "Microsoft.AspNet.Abstractions": "0.1-alpha-*", "Microsoft.AspNet.ConfigurationModel": "0.1-alpha-*", + "Microsoft.AspNet.DependencyInjection": "0.1-alpha-*", "Microsoft.AspNet.Identity": "", "Microsoft.AspNet.Identity.Entity": "", "Microsoft.AspNet.PipelineCore": "0.1-alpha-*", @@ -11,9 +11,12 @@ "Microsoft.AspNet.RequestContainer": "0.1-alpha-*", "Microsoft.AspNet.Security.DataProtection": "0.1-alpha-*", "Microsoft.AspNet.Testing": "0.1-alpha-*", + "Microsoft.Bcl.Immutable": "1.1.18-beta-*", "Microsoft.Data.Entity": "0.1-alpha-*", "Microsoft.Data.InMemory": "0.1-alpha-*", + "Microsoft.Data.Migrations": "0.1-alpha-*", "Microsoft.Data.Relational": "0.1-alpha-*", + "Microsoft.Data.SqlServer": "0.1-alpha-*", "Remotion.Linq": "1.15.13.0", "System.Security.Claims": "0.1-alpha-*", "Xunit.KRunner": "0.1-alpha-*", @@ -22,9 +25,11 @@ "xunit.core": "2.0.0-aspnet-*", "xunit.execution": "2.0.0-aspnet-*" }, + "code": "**\\*.cs;..\\Shared\\*.cs", "configurations": { "net45": { "dependencies": { + "Moq" : "4.2.1312.1622", "System.Runtime": "", "System.Collections": "", "System.Data": "" diff --git a/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryStoreTest.cs b/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryStoreTest.cs index f6f10ca506..b37c458722 100644 --- a/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryStoreTest.cs +++ b/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryStoreTest.cs @@ -20,1483 +20,46 @@ using System.Linq; using System.Security.Claims; using System.Threading; using System.Threading.Tasks; -using Microsoft.AspNet.Testing; -using Xunit; +using Microsoft.AspNet.DependencyInjection; +using Microsoft.AspNet.DependencyInjection.Fallback; +using Microsoft.AspNet.Identity.Test; namespace Microsoft.AspNet.Identity.InMemory.Test { - public class InMemoryStoreTest + public class InMemoryStoreTest : UserManagerTestBase { - [Fact] - public async Task CanDeleteUser() + protected override UserManager CreateManager() { - var manager = CreateManager(); - var user = new IdentityUser("DeleteAsync"); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - IdentityResultAssert.IsSuccess(await manager.DeleteAsync(user)); - Assert.Null(await manager.FindByIdAsync(user.Id)); - } - - [Fact] - public async Task CanUpdateUserName() - { - var manager = CreateManager(); - var user = new IdentityUser("UpdateAsync"); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - Assert.Null(await manager.FindByNameAsync("New")); - user.UserName = "New"; - IdentityResultAssert.IsSuccess(await manager.UpdateAsync(user)); - Assert.NotNull(await manager.FindByNameAsync("New")); - Assert.Null(await manager.FindByNameAsync("UpdateAsync")); - } - - [Fact] - public async Task CanSetUserName() - { - var manager = CreateManager(); - var user = new IdentityUser("UpdateAsync"); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - Assert.Null(await manager.FindByNameAsync("New")); - IdentityResultAssert.IsSuccess(await manager.SetUserNameAsync(user, "New")); - Assert.NotNull(await manager.FindByNameAsync("New")); - Assert.Null(await manager.FindByNameAsync("UpdateAsync")); - } - - [Fact] - public async Task CanFindById() - { - var manager = CreateManager(); - var user = new IdentityUser("FindByid"); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - Assert.NotNull(await manager.FindByIdAsync(user.Id)); - } - - [Fact] - public async Task UserValidatorCanBlockCreate() - { - var manager = CreateManager(); - var user = new IdentityUser("CreateBlocked"); - manager.UserValidator = new AlwaysBadValidator(); - IdentityResultAssert.IsFailure(await manager.CreateAsync(user), AlwaysBadValidator.ErrorMessage); - } - - [Fact] - public async Task UserValidatorCanBlockUpdate() - { - var manager = CreateManager(); - var user = new IdentityUser("UpdateBlocked"); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - manager.UserValidator = new AlwaysBadValidator(); - IdentityResultAssert.IsFailure(await manager.UpdateAsync(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.CreateAsync(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.CreateAsync(user), "Email '" + email + "' is invalid."); - } -#endif - - [Fact] - public async Task PasswordValidatorCanBlockAddPassword() - { - var manager = CreateManager(); - var user = new IdentityUser("AddPasswordBlocked"); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - manager.PasswordValidator = new AlwaysBadValidator(); - IdentityResultAssert.IsFailure(await manager.AddPasswordAsync(user, "password"), - AlwaysBadValidator.ErrorMessage); - } - - [Fact] - public async Task PasswordValidatorCanBlockChangePassword() - { - var manager = CreateManager(); - var user = new IdentityUser("ChangePasswordBlocked"); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, "password")); - manager.PasswordValidator = new AlwaysBadValidator(); - IdentityResultAssert.IsFailure(await manager.ChangePasswordAsync(user, "password", "new"), - AlwaysBadValidator.ErrorMessage); - } - - [Fact] - public async Task CanCreateUserNoPassword() - { - var manager = CreateManager(); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(new IdentityUser("CreateUserTest"))); - var user = await manager.FindByNameAsync("CreateUserTest"); - Assert.NotNull(user); - Assert.Null(user.PasswordHash); - var logins = await manager.GetLoginsAsync(user); - 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.CreateAsync(new IdentityUser(userName))); - var user = await manager.FindByNameAsync(userName); - var login = new UserLoginInfo(provider, providerKey); - IdentityResultAssert.IsSuccess(await manager.AddLoginAsync(user, login)); - var logins = await manager.GetLoginsAsync(user); - 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.CreateAsync(user)); - IdentityResultAssert.IsSuccess(await manager.AddLoginAsync(user, login)); - Assert.False(await manager.HasPasswordAsync(user)); - IdentityResultAssert.IsSuccess(await manager.AddPasswordAsync(user, "password")); - Assert.True(await manager.HasPasswordAsync(user)); - var logins = await manager.GetLoginsAsync(user); - Assert.NotNull(logins); - Assert.Equal(1, logins.Count()); - Assert.Equal(user, await manager.FindByLoginAsync(login)); - Assert.Equal(user, await manager.FindByUserNamePasswordAsync(user.UserName, "password")); - } - - [Fact] - public async Task AddPasswordFailsIfAlreadyHave() - { - var manager = CreateManager(); - var user = new IdentityUser("CannotAddAnotherPassword"); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, "Password")); - Assert.True(await manager.HasPasswordAsync(user)); - IdentityResultAssert.IsFailure(await manager.AddPasswordAsync(user, "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.CreateAsync(user); - Assert.NotNull(user); - IdentityResultAssert.IsSuccess(result); - IdentityResultAssert.IsSuccess(await manager.AddLoginAsync(user, login)); - Assert.Equal(user, await manager.FindByLoginAsync(login)); - var logins = await manager.GetLoginsAsync(user); - 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.RemoveLoginAsync(user, login)); - Assert.Null(await manager.FindByLoginAsync(login)); - logins = await manager.GetLoginsAsync(user); - 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.CreateAsync(user, password)); - var stamp = user.SecurityStamp; - IdentityResultAssert.IsSuccess(await manager.RemovePasswordAsync(user)); - var u = await manager.FindByNameAsync(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.CreateAsync(user, password)); - var stamp = user.SecurityStamp; - Assert.NotNull(stamp); - IdentityResultAssert.IsSuccess(await manager.ChangePasswordAsync(user, password, newPassword)); - Assert.Null(await manager.FindByUserNamePasswordAsync(user.UserName, password)); - Assert.Equal(user, await manager.FindByUserNamePasswordAsync(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.CreateAsync(user)); - Claim[] claims = {new Claim("c", "v"), new Claim("c2", "v2"), new Claim("c2", "v3")}; - foreach (Claim c in claims) + var services = new ServiceCollection(); + services.AddTransient, UserValidator>(); + services.AddTransient, PasswordValidator>(); + var options = new IdentityOptions { - IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user, c)); - } - var userClaims = await manager.GetClaimsAsync(user); - Assert.Equal(3, userClaims.Count); - IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(user, claims[0])); - userClaims = await manager.GetClaimsAsync(user); - Assert.Equal(2, userClaims.Count); - IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(user, claims[1])); - userClaims = await manager.GetClaimsAsync(user); - Assert.Equal(1, userClaims.Count); - IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(user, claims[2])); - userClaims = await manager.GetClaimsAsync(user); - Assert.Equal(0, userClaims.Count); - } - - [Fact] - public async Task ChangePasswordFallsIfPasswordWrong() - { - var manager = CreateManager(); - var user = new IdentityUser("user"); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, "password")); - var result = await manager.ChangePasswordAsync(user, "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.CreateAsync(user)); - IdentityResultAssert.IsFailure(await manager.CreateAsync(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.CreateAsync(user)); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(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.CreateAsync(user)); - IdentityResultAssert.IsFailure(await manager.CreateAsync(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.CreateAsync(user)); - var stamp = user.SecurityStamp; - Assert.NotNull(stamp); - IdentityResultAssert.IsSuccess(await manager.UpdateSecurityStampAsync(user)); - 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.CreateAsync(user)); - IdentityResultAssert.IsSuccess(await manager.AddLoginAsync(user, login)); - var result = await manager.AddLoginAsync(user, 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.CreateAsync(user)); - var fetch = await manager.FindByEmailAsync(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 (IdentityUser u in users) - { - IdentityResultAssert.IsSuccess(await mgr.CreateAsync(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 manager = CreateManager(); - var role = CreateRoleManager(); - var user = new IdentityUser("Hao"); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - IdentityResultAssert.IsSuccess(await role.CreateAsync(new IdentityRole("Admin"))); - IdentityResultAssert.IsSuccess(await role.CreateAsync(new IdentityRole("Local"))); - IdentityResultAssert.IsSuccess(await manager.AddToRoleAsync(user, "Admin")); - IdentityResultAssert.IsSuccess(await manager.AddToRoleAsync(user, "Local")); - Claim[] userClaims = - { - new Claim("Whatever", "Value"), - new Claim("Whatever2", "Value2") - }; - foreach (var c in userClaims) - { - IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user, c)); - } - - var identity = await manager.CreateIdentityAsync(user, "test"); - var claimsFactory = (ClaimsIdentityFactory)manager.ClaimsIdentityFactory; - Assert.NotNull(claimsFactory); - var claims = identity.Claims.ToList(); - 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.CreateAsync(user)); - Assert.False(await manager.IsEmailConfirmedAsync(user)); - } - - // TODO: No token provider implementations yet - private class StaticTokenProvider : IUserTokenProvider - { - public Task GenerateAsync(string purpose, UserManager manager, - IdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - return Task.FromResult(MakeToken(purpose, user)); - } - - public Task ValidateAsync(string purpose, string token, UserManager manager, - IdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - return Task.FromResult(token == MakeToken(purpose, user)); - } - - public Task NotifyAsync(string token, UserManager manager, IdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - return Task.FromResult(0); - } - - public Task IsValidProviderForUserAsync(UserManager manager, IdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - return Task.FromResult(true); - } - - private static string MakeToken(string purpose, IdentityUser 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.CreateAsync(user, password)); - var stamp = user.SecurityStamp; - Assert.NotNull(stamp); - var token = await manager.GeneratePasswordResetTokenAsync(user); - Assert.NotNull(token); - IdentityResultAssert.IsSuccess(await manager.ResetPassword(user, token, newPassword)); - Assert.Null(await manager.FindByUserNamePasswordAsync(user.UserName, password)); - Assert.Equal(user, await manager.FindByUserNamePasswordAsync(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.CreateAsync(user, password)); - var stamp = user.SecurityStamp; - Assert.NotNull(stamp); - var token = await manager.GeneratePasswordResetTokenAsync(user); - Assert.NotNull(token); - manager.PasswordValidator = new AlwaysBadValidator(); - IdentityResultAssert.IsFailure(await manager.ResetPassword(user, token, newPassword), - AlwaysBadValidator.ErrorMessage); - Assert.NotNull(await manager.FindByUserNamePasswordAsync(user.UserName, password)); - Assert.Equal(user, await manager.FindByUserNamePasswordAsync(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.CreateAsync(user, password)); - var stamp = user.SecurityStamp; - Assert.NotNull(stamp); - IdentityResultAssert.IsFailure(await manager.ResetPassword(user, "bogus", newPassword), "Invalid token."); - Assert.NotNull(await manager.FindByUserNamePasswordAsync(user.UserName, password)); - Assert.Equal(user, await manager.FindByUserNamePasswordAsync(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.CreateAsync(user)); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user2)); - var token = await manager.GenerateUserTokenAsync("test", user); - Assert.True(await manager.VerifyUserTokenAsync(user, "test", token)); - Assert.False(await manager.VerifyUserTokenAsync(user, "test2", token)); - Assert.False(await manager.VerifyUserTokenAsync(user, "test", token + "a")); - Assert.False(await manager.VerifyUserTokenAsync(user2, "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.CreateAsync(user)); - var token = await manager.GenerateEmailConfirmationTokenAsync(user); - Assert.NotNull(token); - IdentityResultAssert.IsSuccess(await manager.ConfirmEmailAsync(user, token)); - Assert.True(await manager.IsEmailConfirmedAsync(user)); - IdentityResultAssert.IsSuccess(await manager.SetEmailAsync(user, null)); - Assert.False(await manager.IsEmailConfirmedAsync(user)); - } - - [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.CreateAsync(user)); - IdentityResultAssert.IsFailure(await manager.ConfirmEmailAsync(user, "bogus"), "Invalid token."); - Assert.False(await manager.IsEmailConfirmedAsync(user)); - } - - // 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.CreateAsync(user, "password")); - // var token = await manager.GenerateEmailConfirmationTokenAsync(user); - // Assert.NotNull(token); - // IdentityResultAssert.IsSuccess(await manager.ChangePasswordAsync(user, "password", "newpassword")); - // IdentityResultAssert.IsFailure(await manager.ConfirmEmailAsync(user, token), "Invalid token."); - // Assert.False(await manager.IsEmailConfirmedAsync(user)); - //} - - // Lockout tests - - [Fact] - public async Task SingleFailureLockout() - { - var mgr = CreateManager(); - mgr.LockoutPolicy = new LockoutPolicy - { - DefaultAccountLockoutTimeSpan = TimeSpan.FromHours(1), - UserLockoutEnabledByDefault = true - }; - var user = new IdentityUser("fastLockout"); - IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); - Assert.True(await mgr.GetLockoutEnabledAsync(user)); - Assert.True(user.LockoutEnabled); - Assert.False(await mgr.IsLockedOutAsync(user)); - IdentityResultAssert.IsSuccess(await mgr.AccessFailedAsync(user)); - Assert.True(await mgr.IsLockedOutAsync(user)); - Assert.True(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); - Assert.Equal(0, await mgr.GetAccessFailedCountAsync(user)); - } - - [Fact] - public async Task TwoFailureLockout() - { - var mgr = CreateManager(); - mgr.LockoutPolicy = new LockoutPolicy - { - DefaultAccountLockoutTimeSpan = TimeSpan.FromHours(1), - UserLockoutEnabledByDefault = true, - MaxFailedAccessAttemptsBeforeLockout = 2 - }; - var user = new IdentityUser("twoFailureLockout"); - IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); - Assert.True(await mgr.GetLockoutEnabledAsync(user)); - Assert.True(user.LockoutEnabled); - Assert.False(await mgr.IsLockedOutAsync(user)); - IdentityResultAssert.IsSuccess(await mgr.AccessFailedAsync(user)); - Assert.False(await mgr.IsLockedOutAsync(user)); - Assert.False(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); - Assert.Equal(1, await mgr.GetAccessFailedCountAsync(user)); - IdentityResultAssert.IsSuccess(await mgr.AccessFailedAsync(user)); - Assert.True(await mgr.IsLockedOutAsync(user)); - Assert.True(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); - Assert.Equal(0, await mgr.GetAccessFailedCountAsync(user)); - } - - [Fact] - public async Task ResetAccessCountPreventsLockout() - { - var mgr = CreateManager(); - mgr.LockoutPolicy = new LockoutPolicy - { - DefaultAccountLockoutTimeSpan = TimeSpan.FromHours(1), - UserLockoutEnabledByDefault = true, - MaxFailedAccessAttemptsBeforeLockout = 2 - }; - var user = new IdentityUser("resetLockout"); - IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); - Assert.True(await mgr.GetLockoutEnabledAsync(user)); - Assert.True(user.LockoutEnabled); - Assert.False(await mgr.IsLockedOutAsync(user)); - IdentityResultAssert.IsSuccess(await mgr.AccessFailedAsync(user)); - Assert.False(await mgr.IsLockedOutAsync(user)); - Assert.False(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); - Assert.Equal(1, await mgr.GetAccessFailedCountAsync(user)); - IdentityResultAssert.IsSuccess(await mgr.ResetAccessFailedCountAsync(user)); - Assert.Equal(0, await mgr.GetAccessFailedCountAsync(user)); - Assert.False(await mgr.IsLockedOutAsync(user)); - Assert.False(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); - IdentityResultAssert.IsSuccess(await mgr.AccessFailedAsync(user)); - Assert.False(await mgr.IsLockedOutAsync(user)); - Assert.False(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); - Assert.Equal(1, await mgr.GetAccessFailedCountAsync(user)); - } - - [Fact] - public async Task CanEnableLockoutManuallyAndLockout() - { - var mgr = CreateManager(); - mgr.LockoutPolicy = new LockoutPolicy - { - DefaultAccountLockoutTimeSpan = TimeSpan.FromHours(1), - MaxFailedAccessAttemptsBeforeLockout = 2 - }; - var user = new IdentityUser("manualLockout"); - IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); - Assert.False(await mgr.GetLockoutEnabledAsync(user)); - Assert.False(user.LockoutEnabled); - IdentityResultAssert.IsSuccess(await mgr.SetLockoutEnabledAsync(user, true)); - Assert.True(await mgr.GetLockoutEnabledAsync(user)); - Assert.True(user.LockoutEnabled); - Assert.False(await mgr.IsLockedOutAsync(user)); - IdentityResultAssert.IsSuccess(await mgr.AccessFailedAsync(user)); - Assert.False(await mgr.IsLockedOutAsync(user)); - Assert.False(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); - Assert.Equal(1, await mgr.GetAccessFailedCountAsync(user)); - IdentityResultAssert.IsSuccess(await mgr.AccessFailedAsync(user)); - Assert.True(await mgr.IsLockedOutAsync(user)); - Assert.True(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); - Assert.Equal(0, await mgr.GetAccessFailedCountAsync(user)); - } - - [Fact] - public async Task UserNotLockedOutWithNullDateTimeAndIsSetToNullDate() - { - var mgr = CreateManager(); - mgr.LockoutPolicy = new LockoutPolicy - { - UserLockoutEnabledByDefault = true, - }; - var user = new IdentityUser("LockoutTest"); - IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); - Assert.True(await mgr.GetLockoutEnabledAsync(user)); - Assert.True(user.LockoutEnabled); - IdentityResultAssert.IsSuccess(await mgr.SetLockoutEndDateAsync(user, new DateTimeOffset())); - Assert.False(await mgr.IsLockedOutAsync(user)); - Assert.Equal(new DateTimeOffset(), await mgr.GetLockoutEndDateAsync(user)); - Assert.Equal(new DateTimeOffset(), user.LockoutEnd); - } - - [Fact] - public async Task LockoutFailsIfNotEnabled() - { - var mgr = CreateManager(); - var user = new IdentityUser("LockoutNotEnabledTest"); - IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); - Assert.False(await mgr.GetLockoutEnabledAsync(user)); - Assert.False(user.LockoutEnabled); - IdentityResultAssert.IsFailure(await mgr.SetLockoutEndDateAsync(user, new DateTimeOffset()), - "Lockout is not enabled for this user."); - Assert.False(await mgr.IsLockedOutAsync(user)); - } - - [Fact] - public async Task LockoutEndToUtcNowMinus1SecInUserShouldNotBeLockedOut() - { - var mgr = CreateManager(); - mgr.LockoutPolicy = new LockoutPolicy - { - UserLockoutEnabledByDefault = true, - }; - var user = new IdentityUser("LockoutUtcNowTest") { LockoutEnd = DateTimeOffset.UtcNow.AddSeconds(-1) }; - IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); - Assert.True(await mgr.GetLockoutEnabledAsync(user)); - Assert.True(user.LockoutEnabled); - Assert.False(await mgr.IsLockedOutAsync(user)); - } - - [Fact] - public async Task LockoutEndToUtcNowSubOneSecondWithManagerShouldNotBeLockedOut() - { - var mgr = CreateManager(); - mgr.LockoutPolicy = new LockoutPolicy - { - UserLockoutEnabledByDefault = true, - }; - var user = new IdentityUser("LockoutUtcNowTest"); - IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); - Assert.True(await mgr.GetLockoutEnabledAsync(user)); - Assert.True(user.LockoutEnabled); - IdentityResultAssert.IsSuccess(await mgr.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow.AddSeconds(-1))); - Assert.False(await mgr.IsLockedOutAsync(user)); - } - - [Fact] - public async Task LockoutEndToUtcNowPlus5ShouldBeLockedOut() - { - var mgr = CreateManager(); - mgr.LockoutPolicy = new LockoutPolicy - { - UserLockoutEnabledByDefault = true, - }; - var user = new IdentityUser("LockoutUtcNowTest") { LockoutEnd = DateTimeOffset.UtcNow.AddMinutes(5) }; - IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); - Assert.True(await mgr.GetLockoutEnabledAsync(user)); - Assert.True(user.LockoutEnabled); - Assert.True(await mgr.IsLockedOutAsync(user)); - } - - [Fact] - public async Task UserLockedOutWithDateTimeLocalKindNowPlus30() - { - var mgr = CreateManager(); - mgr.LockoutPolicy = new LockoutPolicy - { - UserLockoutEnabledByDefault = true, - }; - var user = new IdentityUser("LockoutTest"); - IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); - Assert.True(await mgr.GetLockoutEnabledAsync(user)); - Assert.True(user.LockoutEnabled); - var lockoutEnd = new DateTimeOffset(DateTime.Now.AddMinutes(30).ToLocalTime()); - IdentityResultAssert.IsSuccess(await mgr.SetLockoutEndDateAsync(user, lockoutEnd)); - Assert.True(await mgr.IsLockedOutAsync(user)); - var end = await mgr.GetLockoutEndDateAsync(user); - Assert.Equal(lockoutEnd, end); - } - - // Role Tests - [Fact] - public async Task CanCreateRoleTest() - { - var manager = CreateRoleManager(); - var role = new IdentityRole("create"); - Assert.False(await manager.RoleExistsAsync(role.Name)); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(role)); - Assert.True(await manager.RoleExistsAsync(role.Name)); - } - - private class AlwaysBadValidator : IUserValidator, IRoleValidator, - IPasswordValidator - { - public const string ErrorMessage = "I'm Bad."; - - public Task ValidateAsync(string password, CancellationToken cancellationToken = default(CancellationToken)) - { - return Task.FromResult(IdentityResult.Failed(ErrorMessage)); - } - - public Task ValidateAsync(RoleManager manager, IdentityRole role, CancellationToken cancellationToken = default(CancellationToken)) - { - return Task.FromResult(IdentityResult.Failed(ErrorMessage)); - } - - public Task ValidateAsync(UserManager manager, IdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - return Task.FromResult(IdentityResult.Failed(ErrorMessage)); - } - } - - [Fact] - public async Task BadValidatorBlocksCreateRole() - { - var manager = CreateRoleManager(); - manager.RoleValidator = new AlwaysBadValidator(); - IdentityResultAssert.IsFailure(await manager.CreateAsync(new IdentityRole("blocked")), - AlwaysBadValidator.ErrorMessage); - } - - [Fact] - public async Task BadValidatorBlocksRoleUpdate() - { - var manager = CreateRoleManager(); - var role = new IdentityRole("poorguy"); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(role)); - var error = AlwaysBadValidator.ErrorMessage; - manager.RoleValidator = new AlwaysBadValidator(); - IdentityResultAssert.IsFailure(await manager.UpdateAsync(role), error); - } - - [Fact] - public async Task CanDeleteRoleTest() - { - var manager = CreateRoleManager(); - var role = new IdentityRole("delete"); - Assert.False(await manager.RoleExistsAsync(role.Name)); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(role)); - IdentityResultAssert.IsSuccess(await manager.DeleteAsync(role)); - Assert.False(await manager.RoleExistsAsync(role.Name)); - } - - [Fact] - public async Task CanRoleFindByIdTest() - { - var manager = CreateRoleManager(); - var role = new IdentityRole("FindByIdAsync"); - Assert.Null(await manager.FindByIdAsync(role.Id)); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(role)); - Assert.Equal(role, await manager.FindByIdAsync(role.Id)); - } - - [Fact] - public async Task CanRoleFindByName() - { - var manager = CreateRoleManager(); - var role = new IdentityRole("FindByNameAsync"); - Assert.Null(await manager.FindByNameAsync(role.Name)); - Assert.False(await manager.RoleExistsAsync(role.Name)); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(role)); - Assert.Equal(role, await manager.FindByNameAsync(role.Name)); - } - - [Fact] - public async Task CanUpdateRoleNameTest() - { - var manager = CreateRoleManager(); - var role = new IdentityRole("update"); - Assert.False(await manager.RoleExistsAsync(role.Name)); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(role)); - Assert.True(await manager.RoleExistsAsync(role.Name)); - role.Name = "Changed"; - IdentityResultAssert.IsSuccess(await manager.UpdateAsync(role)); - Assert.False(await manager.RoleExistsAsync("update")); - Assert.Equal(role, await manager.FindByNameAsync(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.CreateAsync(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.RoleExistsAsync(role.Name)); - // IdentityResultAssert.IsSuccess(await roleMgr.CreateAsync(role)); - // var user = new IdentityUser("t"); - // IdentityResultAssert.IsSuccess(await userMgr.CreateAsync(user)); - // IdentityResultAssert.IsSuccess(await userMgr.AddToRoleAsync(user, role.Name)); - // IdentityResultAssert.IsSuccess(await roleMgr.DeleteAsync(role)); - // Assert.Null(await roleMgr.FindByNameAsync(role.Name)); - // Assert.False(await roleMgr.RoleExistsAsync(role.Name)); - // // REVIEW: We should throw if deleteing a non empty role? - // var roles = await userMgr.GetRolesAsync(user); - - // // 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.RoleExistsAsync(role.Name)); - //// IdentityResultAssert.IsSuccess(await roleMgr.CreateAsync(role)); - //// var user = new IdentityUser("t"); - //// IdentityResultAssert.IsSuccess(await userMgr.CreateAsync(user)); - //// IdentityResultAssert.IsSuccess(await userMgr.AddToRoleAsync(user, role.Name)); - //// IdentityResultAssert.IsSuccess(await userMgr.DeleteAsync(user)); - //// role = roleMgr.FindByIdAsync(role.Id); - ////} - - [Fact] - public async Task CreateRoleFailsIfExists() - { - var manager = CreateRoleManager(); - var role = new IdentityRole("dupeRole"); - Assert.False(await manager.RoleExistsAsync(role.Name)); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(role)); - Assert.True(await manager.RoleExistsAsync(role.Name)); - var role2 = new IdentityRole("dupeRole"); - IdentityResultAssert.IsFailure(await manager.CreateAsync(role2)); - } - - [Fact] - public async Task CanAddUsersToRole() - { - var manager = CreateManager(); - var roleManager = CreateRoleManager(); - var role = new IdentityRole("addUserTest"); - IdentityResultAssert.IsSuccess(await roleManager.CreateAsync(role)); - IdentityUser[] users = - { - new IdentityUser("1"), new IdentityUser("2"), new IdentityUser("3"), - new IdentityUser("4") - }; - foreach (var u in users) - { - IdentityResultAssert.IsSuccess(await manager.CreateAsync(u)); - IdentityResultAssert.IsSuccess(await manager.AddToRoleAsync(u, role.Name)); - Assert.True(await manager.IsInRoleAsync(u, role.Name)); - } - } - - [Fact] - public async Task CanGetRolesForUser() - { - var userManager = CreateManager(); - var roleManager = CreateRoleManager(); - 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.CreateAsync(u)); - } - foreach (var r in roles) - { - IdentityResultAssert.IsSuccess(await roleManager.CreateAsync(r)); - foreach (var u in users) - { - IdentityResultAssert.IsSuccess(await userManager.AddToRoleAsync(u, r.Name)); - Assert.True(await userManager.IsInRoleAsync(u, r.Name)); + Password = new PasswordOptions { + RequireDigit = false, + RequireLowercase = false, + RequireNonLetterOrDigit = false, + RequireUppercase = false + }, + User = new UserOptions { + AllowOnlyAlphanumericNames = false } - } - - foreach (var u in users) - { - var rs = await userManager.GetRolesAsync(u); - 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 userManager = CreateManager(); - var roleManager = CreateRoleManager(); - var user = new IdentityUser("MultiRoleUser"); - IdentityResultAssert.IsSuccess(await userManager.CreateAsync(user)); - IdentityRole[] roles = - { - new IdentityRole("r1"), new IdentityRole("r2"), new IdentityRole("r3"), - new IdentityRole("r4") }; - foreach (var r in roles) - { - IdentityResultAssert.IsSuccess(await roleManager.CreateAsync(r)); - IdentityResultAssert.IsSuccess(await userManager.AddToRoleAsync(user, r.Name)); - Assert.True(await userManager.IsInRoleAsync(user, r.Name)); - } - IdentityResultAssert.IsSuccess(await userManager.RemoveFromRoleAsync(user, roles[2].Name)); - Assert.False(await userManager.IsInRoleAsync(user, roles[2].Name)); + var optionsAccessor = new OptionsAccessor(new[] {new TestSetup(options)}); + //services.AddInstance>(optionsAccessor); + //services.AddInstance>(new InMemoryUserStore()); + //services.AddSingleton, UserManager>(); + //return services.BuildServiceProvider().GetService>(); + return new UserManager(services.BuildServiceProvider(), new InMemoryUserStore(), optionsAccessor); } - [Fact] - public async Task CanRemoveUsersFromRole() + protected override RoleManager CreateRoleManager() { - var userManager = CreateManager(); - var roleManager = CreateRoleManager(); - IdentityUser[] users = - { - new IdentityUser("1"), new IdentityUser("2"), new IdentityUser("3"), - new IdentityUser("4") - }; - foreach (var u in users) - { - IdentityResultAssert.IsSuccess(await userManager.CreateAsync(u)); - } - var r = new IdentityRole("r1"); - IdentityResultAssert.IsSuccess(await roleManager.CreateAsync(r)); - foreach (var u in users) - { - IdentityResultAssert.IsSuccess(await userManager.AddToRoleAsync(u, r.Name)); - Assert.True(await userManager.IsInRoleAsync(u, r.Name)); - } - foreach (var u in users) - { - IdentityResultAssert.IsSuccess(await userManager.RemoveFromRoleAsync(u, r.Name)); - Assert.False(await userManager.IsInRoleAsync(u, r.Name)); - } - } - - [Fact] - public async Task RemoveUserNotInRoleFails() - { - var userMgr = CreateManager(); - var roleMgr = CreateRoleManager(); - var role = new IdentityRole("addUserDupeTest"); - var user = new IdentityUser("user1"); - IdentityResultAssert.IsSuccess(await userMgr.CreateAsync(user)); - IdentityResultAssert.IsSuccess(await roleMgr.CreateAsync(role)); - var result = await userMgr.RemoveFromRoleAsync(user, role.Name); - IdentityResultAssert.IsFailure(result, "User is not in role."); - } - - [Fact] - public async Task AddUserToRoleFailsIfAlreadyInRole() - { - var userMgr = CreateManager(); - var roleMgr = CreateRoleManager(); - var role = new IdentityRole("addUserDupeTest"); - var user = new IdentityUser("user1"); - IdentityResultAssert.IsSuccess(await userMgr.CreateAsync(user)); - IdentityResultAssert.IsSuccess(await roleMgr.CreateAsync(role)); - IdentityResultAssert.IsSuccess(await userMgr.AddToRoleAsync(user, role.Name)); - Assert.True(await userMgr.IsInRoleAsync(user, role.Name)); - IdentityResultAssert.IsFailure(await userMgr.AddToRoleAsync(user, role.Name), "User already in role."); - } - - [Fact] - public async Task CanFindRoleByNameWithManager() - { - var roleMgr = CreateRoleManager(); - var role = new IdentityRole("findRoleByNameTest"); - IdentityResultAssert.IsSuccess(await roleMgr.CreateAsync(role)); - Assert.Equal(role.Id, (await roleMgr.FindByNameAsync(role.Name)).Id); - } - - [Fact] - public async Task CanFindRoleWithManager() - { - var roleMgr = CreateRoleManager(); - var role = new IdentityRole("findRoleTest"); - IdentityResultAssert.IsSuccess(await roleMgr.CreateAsync(role)); - Assert.Equal(role.Name, (await roleMgr.FindByIdAsync(role.Id)).Name); - } - - [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.CreateAsync(user)); - var stamp = await manager.GetSecurityStampAsync(user); - Assert.Equal(await manager.GetPhoneNumberAsync(user), "123-456-7890"); - IdentityResultAssert.IsSuccess(await manager.SetPhoneNumberAsync(user, "111-111-1111")); - Assert.Equal(await manager.GetPhoneNumberAsync(user), "111-111-1111"); - Assert.NotEqual(stamp, user.SecurityStamp); - } - -#if NET45 - [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.CreateAsync(user)); - Assert.False(await manager.IsPhoneNumberConfirmedAsync(user)); - var stamp = await manager.GetSecurityStampAsync(user); - var token1 = await manager.GenerateChangePhoneNumberTokenAsync(user, "111-111-1111"); - IdentityResultAssert.IsSuccess(await manager.ChangePhoneNumberAsync(user, "111-111-1111", token1)); - Assert.True(await manager.IsPhoneNumberConfirmedAsync(user)); - Assert.Equal(await manager.GetPhoneNumberAsync(user), "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.CreateAsync(user)); - Assert.False(await manager.IsPhoneNumberConfirmedAsync(user)); - var stamp = await manager.GetSecurityStampAsync(user); - IdentityResultAssert.IsFailure(await manager.ChangePhoneNumberAsync(user, "111-111-1111", "bogus"), - "Invalid token."); - Assert.False(await manager.IsPhoneNumberConfirmedAsync(user)); - Assert.Equal(await manager.GetPhoneNumberAsync(user), "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.CreateAsync(user)); - const string num1 = "111-123-4567"; - const string num2 = "111-111-1111"; - var token1 = await manager.GenerateChangePhoneNumberTokenAsync(user, num1); - var token2 = await manager.GenerateChangePhoneNumberTokenAsync(user, num2); - Assert.NotEqual(token1, token2); - Assert.True(await manager.VerifyChangePhoneNumberTokenAsync(user, token1, num1)); - Assert.True(await manager.VerifyChangePhoneNumberTokenAsync(user, token2, num2)); - Assert.False(await manager.VerifyChangePhoneNumberTokenAsync(user, token2, num1)); - Assert.False(await manager.VerifyChangePhoneNumberTokenAsync(user, token1, num2)); - } -#endif - - private class EmailTokenProvider : IUserTokenProvider - { - public Task GenerateAsync(string purpose, UserManager manager, IdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - return Task.FromResult(MakeToken(purpose)); - } - - public Task ValidateAsync(string purpose, string token, UserManager manager, - IdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - return Task.FromResult(token == MakeToken(purpose)); - } - - public Task NotifyAsync(string token, UserManager manager, IdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - return manager.SendEmailAsync(user, token, token); - } - - public async Task IsValidProviderForUserAsync(UserManager manager, IdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - return !string.IsNullOrEmpty(await manager.GetEmailAsync(user)); - } - - private static string MakeToken(string purpose) - { - return "email:" + purpose; - } - } - - private class SmsTokenProvider : IUserTokenProvider - { - public Task GenerateAsync(string purpose, UserManager manager, IdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - return Task.FromResult(MakeToken(purpose)); - } - - public Task ValidateAsync(string purpose, string token, UserManager manager, - IdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - return Task.FromResult(token == MakeToken(purpose)); - } - - public Task NotifyAsync(string token, UserManager manager, IdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - return manager.SendSmsAsync(user, token, cancellationToken); - } - - public async Task IsValidProviderForUserAsync(UserManager manager, IdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - return !string.IsNullOrEmpty(await manager.GetPhoneNumberAsync(user, cancellationToken)); - } - - 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.CreateAsync(user, password)); - var stamp = user.SecurityStamp; - Assert.NotNull(stamp); - var token = await manager.GenerateTwoFactorTokenAsync(user, factorId); - Assert.NotNull(token); - Assert.Null(messageService.Message); - IdentityResultAssert.IsSuccess(await manager.NotifyTwoFactorTokenAsync(user, factorId, token)); - Assert.NotNull(messageService.Message); - Assert.Equal(token, messageService.Message.Subject); - Assert.Equal(token, messageService.Message.Body); - Assert.True(await manager.VerifyTwoFactorTokenAsync(user, factorId, token)); - } - - [Fact] - public async Task NotifyWithUnknownProviderFails() - { - var manager = CreateManager(); - var user = new IdentityUser("NotifyFail"); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - await - ExceptionAssert.ThrowsAsync( - async () => await manager.NotifyTwoFactorTokenAsync(user, "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.CreateAsync(user, password)); - // var stamp = user.SecurityStamp; - // Assert.NotNull(stamp); - // var token = await manager.GenerateTwoFactorTokenAsync(user, factorId); - // Assert.NotNull(token); - // Assert.Null(messageService.Message); - // IdentityResultAssert.IsSuccess(await manager.NotifyTwoFactorTokenAsync(user, 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.VerifyTwoFactorTokenAsync(user, 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.CreateAsync(user)); - // var stamp = user.SecurityStamp; - // Assert.NotNull(stamp); - // var token = await manager.GenerateTwoFactorTokenAsync(user, factorId); - // Assert.NotNull(token); - // IdentityResultAssert.IsSuccess(await manager.UpdateSecurityStampAsync(user)); - // Assert.False(await manager.VerifyTwoFactorTokenAsync(user, factorId, token)); - //} - - [Fact] - public async Task EnableTwoFactorChangesSecurityStamp() - { - var manager = CreateManager(); - var user = new IdentityUser("TwoFactorEnabledTest"); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - var stamp = user.SecurityStamp; - Assert.NotNull(stamp); - IdentityResultAssert.IsSuccess(await manager.SetTwoFactorEnabledAsync(user, true)); - Assert.NotEqual(stamp, await manager.GetSecurityStampAsync(user)); - Assert.True(await manager.GetTwoFactorEnabledAsync(user)); - } - - [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.CreateAsync(user)); - await manager.SendSmsAsync(user, "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.CreateAsync(user)); - await manager.SendEmailAsync(user, "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.CreateAsync(user)); - var stamp = user.SecurityStamp; - Assert.NotNull(stamp); - var token = await manager.GenerateTwoFactorTokenAsync(user, factorId); - Assert.NotNull(token); - Assert.Null(messageService.Message); - IdentityResultAssert.IsSuccess(await manager.NotifyTwoFactorTokenAsync(user, factorId, token)); - Assert.NotNull(messageService.Message); - Assert.Equal(token, messageService.Message.Body); - Assert.True(await manager.VerifyTwoFactorTokenAsync(user, 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.CreateAsync(user)); - // var stamp = user.SecurityStamp; - // Assert.NotNull(stamp); - // var token = await manager.GenerateTwoFactorTokenAsync(user, factorId); - // Assert.NotNull(token); - // Assert.Null(messageService.Message); - // IdentityResultAssert.IsSuccess(await manager.NotifyTwoFactorTokenAsync(user, factorId, token)); - // Assert.NotNull(messageService.Message); - // Assert.Equal("Your code is: " + token, messageService.Message.Body); - // Assert.True(await manager.VerifyTwoFactorTokenAsync(user, factorId, token)); - //} - - [Fact] - public async Task GenerateTwoFactorWithUnknownFactorProviderWillThrow() - { - var manager = CreateManager(); - var user = new IdentityUser("PhoneCodeTest"); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - const string error = "No IUserTwoFactorProvider for 'bogus' is registered."; - await - ExceptionAssert.ThrowsAsync( - () => manager.GenerateTwoFactorTokenAsync(user, "bogus"), error); - await ExceptionAssert.ThrowsAsync( - () => manager.VerifyTwoFactorTokenAsync(user, "bogus", "bogus"), error); - } - - [Fact] - public async Task GetValidTwoFactorTestEmptyWithNoProviders() - { - var manager = CreateManager(); - var user = new IdentityUser("test"); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - var factors = await manager.GetValidTwoFactorProvidersAsync(user); - 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.CreateAsync(user)); - var factors = await manager.GetValidTwoFactorProvidersAsync(user); - Assert.NotNull(factors); - Assert.True(!factors.Any()); - IdentityResultAssert.IsSuccess(await manager.SetPhoneNumberAsync(user, "111-111-1111")); - factors = await manager.GetValidTwoFactorProvidersAsync(user); - Assert.NotNull(factors); - Assert.True(factors.Count() == 1); - Assert.Equal("phone", factors[0]); - IdentityResultAssert.IsSuccess(await manager.SetEmailAsync(user, "test@test.com")); - factors = await manager.GetValidTwoFactorProvidersAsync(user); - Assert.NotNull(factors); - Assert.True(factors.Count() == 2); - IdentityResultAssert.IsSuccess(await manager.SetEmailAsync(user, null)); - factors = await manager.GetValidTwoFactorProvidersAsync(user); - 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.CreateAsync(user)); - // var stamp = user.SecurityStamp; - // Assert.NotNull(stamp); - // var token = await manager.GenerateTwoFactorTokenAsync(user, factorId); - // Assert.NotNull(token); - // IdentityResultAssert.IsSuccess(await manager.UpdateSecurityStampAsync(user)); - // Assert.False(await manager.VerifyTwoFactorTokenAsync(user, 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.CreateAsync(user)); - var token = await manager.GenerateTwoFactorTokenAsync(user, "PhoneCode"); - Assert.NotNull(token); - Assert.False(await manager.VerifyTwoFactorTokenAsync(user, "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.CreateAsync(user)); - Assert.False(await manager.VerifyTwoFactorTokenAsync(user, factorId, "bogus")); - } - - private static UserManager CreateManager() - { - var manager = new UserManager(new InMemoryUserStore()) - { - UserValidator = new UserValidator(), - PasswordValidator = new PasswordValidator() - }; - return manager; - } - - private static RoleManager CreateRoleManager() - { - return new RoleManager(new InMemoryRoleStore()); - } - - public class TestMessageService : IIdentityMessageService - { - public IdentityMessage Message { get; set; } - - public Task SendAsync(IdentityMessage message, CancellationToken cancellationToken = default(CancellationToken)) - { - Message = message; - return Task.FromResult(0); - } + var services = new ServiceCollection(); + services.AddTransient, RoleValidator>(); + services.AddInstance>(new InMemoryRoleStore()); + //return services.BuildServiceProvider().GetService>(); + return new RoleManager(services.BuildServiceProvider(), new InMemoryRoleStore()); } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.InMemory.Test/Microsoft.AspNet.Identity.InMemory.Test.kproj b/test/Microsoft.AspNet.Identity.InMemory.Test/Microsoft.AspNet.Identity.InMemory.Test.kproj index 9893ee3a74..f8e992d95c 100644 --- a/test/Microsoft.AspNet.Identity.InMemory.Test/Microsoft.AspNet.Identity.InMemory.Test.kproj +++ b/test/Microsoft.AspNet.Identity.InMemory.Test/Microsoft.AspNet.Identity.InMemory.Test.kproj @@ -23,6 +23,10 @@ + + + + \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.InMemory.Test/StartupTest.cs b/test/Microsoft.AspNet.Identity.InMemory.Test/StartupTest.cs index d08242dd79..9bcd6bab1c 100644 --- a/test/Microsoft.AspNet.Identity.InMemory.Test/StartupTest.cs +++ b/test/Microsoft.AspNet.Identity.InMemory.Test/StartupTest.cs @@ -18,6 +18,7 @@ using Microsoft.AspNet.Abstractions; using Microsoft.AspNet.DependencyInjection; using Microsoft.AspNet.DependencyInjection.Fallback; +using Microsoft.AspNet.Identity.Test; using Microsoft.AspNet.PipelineCore; using System; using System.Threading.Tasks; @@ -27,23 +28,78 @@ namespace Microsoft.AspNet.Identity.InMemory.Test { public class StartupTest { + public class ApplicationUser : IdentityUser { } + + public class ApplicationUserManager : UserManager + { + public ApplicationUserManager(IServiceProvider services, IUserStore store, IOptionsAccessor options) : base(services, store, options) { } + } + + public class PasswordsNegativeLengthSetup : IOptionsSetup + { + public int Order { get { return 0; } } + public void Setup(IdentityOptions options) + { + options.Password.RequiredLength = -1; + } + } + + [Fact] + public void CanCustomizeIdentityOptions() + { + IBuilder builder = new Builder(new ServiceCollection().BuildServiceProvider()); + builder.UseServices(services => { + services.AddIdentity(identityServices => { }); + services.AddSetup(); + }); + + var setup = builder.ApplicationServices.GetService>(); + Assert.IsType(typeof(PasswordsNegativeLengthSetup), setup); + var optionsGetter = builder.ApplicationServices.GetService>(); + Assert.NotNull(optionsGetter); + setup.Setup(optionsGetter.Options); + + var myOptions = optionsGetter.Options; + Assert.True(myOptions.Password.RequireLowercase); + Assert.True(myOptions.Password.RequireDigit); + Assert.True(myOptions.Password.RequireNonLetterOrDigit); + Assert.True(myOptions.Password.RequireUppercase); + Assert.Equal(-1, myOptions.Password.RequiredLength); + } + + [Fact] + public void CanSetupIdentityOptions() + { + IBuilder app = new Builder(new ServiceCollection().BuildServiceProvider()); + app.UseServices(services => services.AddIdentity(identityServices => identityServices.SetupOptions(options => options.User.RequireUniqueEmail = true))); + + var optionsGetter = app.ApplicationServices.GetService>(); + Assert.NotNull(optionsGetter); + + var myOptions = optionsGetter.Options; + Assert.True(myOptions.User.RequireUniqueEmail); + } [Fact] public async Task EnsureStartupUsageWorks() { IBuilder builder = new Builder(new ServiceCollection().BuildServiceProvider()); + //builder.UseServices(services => services.AddIdentity(s => + // s.AddEntity() + //{ + builder.UseServices(services => services.AddIdentity(s => { - s.UseUserStore(() => new InMemoryUserStore()); - s.UseUserManager(); - s.UseRoleStore(() => new InMemoryRoleStore()); - s.UseRoleManager(); + s.AddInMemory(); + s.AddUserManager(); + s.AddRoleManager(); })); var userStore = builder.ApplicationServices.GetService>(); var roleStore = builder.ApplicationServices.GetService>(); var userManager = builder.ApplicationServices.GetService(); + //TODO: var userManager = builder.ApplicationServices.GetService(); var roleManager = builder.ApplicationServices.GetService(); Assert.NotNull(userStore); @@ -67,21 +123,5 @@ namespace Microsoft.AspNet.Identity.InMemory.Test IdentityResultAssert.IsSuccess(await roleManager.CreateAsync(new IdentityRole { Name = roleName })); IdentityResultAssert.IsSuccess(await userManager.AddToRoleAsync(user, roleName)); } - - - public class ApplicationUserManager : UserManager - { - public ApplicationUserManager(IServiceProvider services) : base(services) { } - } - - public class ApplicationRoleManager : RoleManager - { - public ApplicationRoleManager(IServiceProvider services) : base(services) { } - } - - public class ApplicationUser : IdentityUser - { - } - } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.InMemory.Test/project.json b/test/Microsoft.AspNet.Identity.InMemory.Test/project.json index 64d5006a6f..6dedf9a2f8 100644 --- a/test/Microsoft.AspNet.Identity.InMemory.Test/project.json +++ b/test/Microsoft.AspNet.Identity.InMemory.Test/project.json @@ -2,7 +2,7 @@ "version": "0.1-alpha-*", "dependencies": { "Microsoft.AspNet.Abstractions" : "0.1-alpha-*", - "Microsoft.AspNet.ConfigurationModel" : "0.1-alpha-*", + "Microsoft.AspNet.ConfigurationModel": "0.1-alpha-*", "Microsoft.AspNet.DependencyInjection" : "0.1-alpha-*", "Microsoft.AspNet.Identity" : "0.1-alpha-*", "Microsoft.AspNet.Identity.InMemory" : "0.1-alpha-*", @@ -15,9 +15,11 @@ "xunit.core": "2.0.0-aspnet-*", "xunit.execution": "2.0.0-aspnet-*" }, + "code": "**\\*.cs;..\\Shared\\*.cs", "configurations": { "net45": { "dependencies": { + "Moq" : "4.2.1312.1622", "System.Runtime": "", "System.Collections": "" } diff --git a/test/Microsoft.AspNet.Identity.Security.Test/Microsoft.AspNet.Identity.Security.Test.kproj b/test/Microsoft.AspNet.Identity.Security.Test/Microsoft.AspNet.Identity.Security.Test.kproj index be0773fb59..aa2be0f249 100644 --- a/test/Microsoft.AspNet.Identity.Security.Test/Microsoft.AspNet.Identity.Security.Test.kproj +++ b/test/Microsoft.AspNet.Identity.Security.Test/Microsoft.AspNet.Identity.Security.Test.kproj @@ -21,8 +21,12 @@ + + + + \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.Security.Test/SignInManagerTest.cs b/test/Microsoft.AspNet.Identity.Security.Test/SignInManagerTest.cs index 1b57131068..830e20e92f 100644 --- a/test/Microsoft.AspNet.Identity.Security.Test/SignInManagerTest.cs +++ b/test/Microsoft.AspNet.Identity.Security.Test/SignInManagerTest.cs @@ -15,9 +15,18 @@ // See the Apache 2 License for the specific language governing // permissions and limitations under the License. +using System; using System.Threading; using Microsoft.AspNet.Abstractions; using Microsoft.AspNet.Abstractions.Security; +using Microsoft.AspNet.DependencyInjection.Fallback; +using Microsoft.AspNet.Identity.InMemory; +using Microsoft.AspNet.Identity.Test; +using Microsoft.AspNet.DependencyInjection; +using Microsoft.AspNet.Logging; +using Microsoft.AspNet.PipelineCore; +using Microsoft.AspNet.FeatureModel; +using Microsoft.AspNet.Security.Cookies; using Moq; using System.Security.Claims; using System.Threading.Tasks; @@ -27,16 +36,117 @@ namespace Microsoft.AspNet.Identity.Security.Test { public class SignInManagerTest { - #if NET45 + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task VerifyAccountControllerSignIn(bool isPersistent) + { + IBuilder app = new Builder(new ServiceCollection().BuildServiceProvider()); + app.UseCookieAuthentication(new CookieAuthenticationOptions + { + AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie + }); + + var context = new Mock(); + var response = new Mock(); + context.Setup(c => c.Response).Returns(response.Object).Verifiable(); + response.Setup(r => r.SignIn(It.IsAny(), It.Is(v => v.IsPersistent == isPersistent))).Verifiable(); + var contextAccessor = new Mock>(); + contextAccessor.Setup(a => a.Value).Returns(context.Object); + app.UseServices(services => + { + services.AddInstance(contextAccessor.Object); + services.AddInstance(new NullLoggerFactory()); + services.AddIdentity(s => + { + s.AddInMemory(); + s.AddUserManager(); + s.AddRoleManager(); + }); + services.AddTransient(); + }); + + // Act + var user = new ApplicationUser + { + UserName = "Yolo" + }; + const string password = "Yol0Sw@g!"; + var userManager = app.ApplicationServices.GetService(); + var signInManager = app.ApplicationServices.GetService(); + + IdentityResultAssert.IsSuccess(await userManager.CreateAsync(user, password)); + var result = await signInManager.PasswordSignInAsync(user.UserName, password, isPersistent, false); + + // Assert + Assert.Equal(SignInStatus.Success, result); + context.VerifyAll(); + response.VerifyAll(); + contextAccessor.VerifyAll(); + } + + //[Theory] + //[InlineData(true)] + //[InlineData(false)] + //public async Task VerifyAccountControllerSignInFunctional(bool isPersistent) + //{ + // IBuilder app = new Builder(new ServiceCollection().BuildServiceProvider()); + // app.UseCookieAuthentication(new CookieAuthenticationOptions + // { + // AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie + // }); + + // TODO: how to functionally test context? + // var context = new DefaultHttpContext(new FeatureCollection()); + // var contextAccessor = new Mock>(); + // contextAccessor.Setup(a => a.Value).Returns(context); + // app.UseServices(services => + // { + // services.AddInstance(contextAccessor.Object); + // services.AddInstance(new NullLoggerFactory()); + // services.AddIdentity(s => + // { + // s.AddUserStore(() => new InMemoryUserStore()); + // s.AddUserManager(); + // s.AddRoleStore(() => new InMemoryRoleStore()); + // s.AddRoleManager(); + // }); + // services.AddTransient(); + // }); + + // // Act + // var user = new ApplicationUser + // { + // UserName = "Yolo" + // }; + // const string password = "Yol0Sw@g!"; + // var userManager = app.ApplicationServices.GetService(); + // var signInManager = app.ApplicationServices.GetService(); + + // IdentityResultAssert.IsSuccess(await userManager.CreateAsync(user, password)); + // var result = await signInManager.PasswordSignInAsync(user.UserName, password, isPersistent, false); + + // // Assert + // Assert.Equal(SignInStatus.Success, result); + // contextAccessor.VerifyAll(); + //} + + [Fact] + public void ConstructorNullChecks() + { + Assert.Throws("userManager", () => new SignInManager(null, null)); + var userManager = MockHelpers.MockUserManager().Object; + Assert.Throws("contextAccessor", () => new SignInManager(userManager, null)); + } + //TODO: Mock fails in K (this works fine in net45) [Fact] public async Task EnsureClaimsIdentityFactoryCreateIdentityCalled() { // Setup - var store = new Mock>(); var user = new TestUser { UserName = "Foo" }; - var userManager = new UserManager(store.Object); + var userManager = MockHelpers.TestUserManager(); var identityFactory = new Mock>(); const string authType = "Test"; var testIdentity = new ClaimsIdentity(authType); @@ -46,13 +156,21 @@ namespace Microsoft.AspNet.Identity.Security.Test var response = new Mock(); context.Setup(c => c.Response).Returns(response.Object).Verifiable(); response.Setup(r => r.SignIn(testIdentity, It.IsAny())).Verifiable(); - var helper = new SignInManager { UserManager = userManager, AuthenticationType = authType, Context = context.Object }; + var contextAccessor = new Mock>(); + contextAccessor.Setup(a => a.Value).Returns(context.Object); + var helper = new SignInManager(userManager, contextAccessor.Object) + { + AuthenticationType = authType + }; // Act await helper.SignInAsync(user, false, false); // Assert identityFactory.VerifyAll(); + context.VerifyAll(); + contextAccessor.VerifyAll(); + response.VerifyAll(); } [Fact] @@ -60,10 +178,13 @@ namespace Microsoft.AspNet.Identity.Security.Test { // Setup var user = new TestUser { UserName = "Foo" }; - var manager = new Mock>(); + var manager = MockHelpers.MockUserManager(); manager.Setup(m => m.IsLockedOutAsync(user, CancellationToken.None)).ReturnsAsync(true).Verifiable(); manager.Setup(m => m.FindByNameAsync(user.UserName, CancellationToken.None)).ReturnsAsync(user).Verifiable(); - var helper = new SignInManager { UserManager = manager.Object }; + var context = new Mock(); + var contextAccessor = new Mock>(); + contextAccessor.Setup(a => a.Value).Returns(context.Object); + var helper = new SignInManager(manager.Object, contextAccessor.Object); // Act var result = await helper.PasswordSignInAsync(user.UserName, "bogus", false, false); @@ -72,13 +193,15 @@ namespace Microsoft.AspNet.Identity.Security.Test Assert.Equal(SignInStatus.LockedOut, result); manager.VerifyAll(); } - - [Fact] - public async Task CanPasswordSignIn() + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task CanPasswordSignIn(bool isPersistent) { // Setup var user = new TestUser { UserName = "Foo" }; - var manager = new Mock>(); + var manager = MockHelpers.MockUserManager(); manager.Setup(m => m.IsLockedOutAsync(user, CancellationToken.None)).ReturnsAsync(false).Verifiable(); manager.Setup(m => m.FindByNameAsync(user.UserName, CancellationToken.None)).ReturnsAsync(user).Verifiable(); manager.Setup(m => m.CheckPasswordAsync(user, "password", CancellationToken.None)).ReturnsAsync(true).Verifiable(); @@ -86,28 +209,39 @@ namespace Microsoft.AspNet.Identity.Security.Test var context = new Mock(); var response = new Mock(); context.Setup(c => c.Response).Returns(response.Object).Verifiable(); - response.Setup(r => r.SignIn(It.IsAny(), It.IsAny())).Verifiable(); - var helper = new SignInManager { UserManager = manager.Object, Context = context.Object }; + response.Setup(r => r.SignIn(It.IsAny(), It.Is(v => v.IsPersistent == isPersistent))).Verifiable(); + var contextAccessor = new Mock>(); + contextAccessor.Setup(a => a.Value).Returns(context.Object); + var helper = new SignInManager(manager.Object, contextAccessor.Object); // Act - var result = await helper.PasswordSignInAsync(user.UserName, "password", false, false); + var result = await helper.PasswordSignInAsync(user.UserName, "password", isPersistent, false); // Assert Assert.Equal(SignInStatus.Success, result); manager.VerifyAll(); + context.VerifyAll(); + response.VerifyAll(); + contextAccessor.VerifyAll(); } [Theory] - [InlineData(DefaultAuthenticationTypes.ApplicationCookie)] + [InlineData("Microsoft.AspNet.Identity.Security.Application")] [InlineData("Foo")] public void SignOutCallsContextResponseSignOut(string authenticationType) { // Setup + var manager = MockHelpers.MockUserManager(); var context = new Mock(); var response = new Mock(); context.Setup(c => c.Response).Returns(response.Object).Verifiable(); response.Setup(r => r.SignOut(authenticationType)).Verifiable(); - var helper = new SignInManager { Context = context.Object, AuthenticationType = authenticationType }; + var contextAccessor = new Mock>(); + contextAccessor.Setup(a => a.Value).Returns(context.Object); + var helper = new SignInManager(manager.Object, contextAccessor.Object) + { + AuthenticationType = authenticationType + }; // Act helper.SignOut(); @@ -122,12 +256,14 @@ namespace Microsoft.AspNet.Identity.Security.Test { // Setup var user = new TestUser { UserName = "Foo" }; - var manager = new Mock>(); + var manager = MockHelpers.MockUserManager(); manager.Setup(m => m.IsLockedOutAsync(user, CancellationToken.None)).ReturnsAsync(false).Verifiable(); manager.Setup(m => m.FindByNameAsync(user.UserName, CancellationToken.None)).ReturnsAsync(user).Verifiable(); manager.Setup(m => m.CheckPasswordAsync(user, "bogus", CancellationToken.None)).ReturnsAsync(false).Verifiable(); - var helper = new SignInManager { UserManager = manager.Object }; - + var context = new Mock(); + var contextAccessor = new Mock>(); + contextAccessor.Setup(a => a.Value).Returns(context.Object); + var helper = new SignInManager(manager.Object, contextAccessor.Object); // Act var result = await helper.PasswordSignInAsync(user.UserName, "bogus", false, false); @@ -141,9 +277,12 @@ namespace Microsoft.AspNet.Identity.Security.Test public async Task PasswordSignInFailsWithUnknownUser() { // Setup - var manager = new Mock>(); + var manager = MockHelpers.MockUserManager(); manager.Setup(m => m.FindByNameAsync("bogus", CancellationToken.None)).ReturnsAsync(null).Verifiable(); - var helper = new SignInManager { UserManager = manager.Object }; + var context = new Mock(); + var contextAccessor = new Mock>(); + contextAccessor.Setup(a => a.Value).Returns(context.Object); + var helper = new SignInManager(manager.Object, contextAccessor.Object); // Act var result = await helper.PasswordSignInAsync("bogus", "bogus", false, false); @@ -153,60 +292,12 @@ namespace Microsoft.AspNet.Identity.Security.Test manager.VerifyAll(); } - [Fact] - public async Task PasswordSignInFailsWithNoUserManager() - { - // Setup - var helper = new SignInManager(); - - // Act - var result = await helper.PasswordSignInAsync("bogus", "bogus", false, false); - - // Assert - Assert.Equal(SignInStatus.Failure, result); - } - - [Fact] - public async Task SignInWithNoContextDoesNotBlowUp() - { - // Setup - var helper = new SignInManager(); - - // Act - await helper.SignInAsync(null, false, false); - } - - [Fact] - public void SignOutWithNoContextDoesNotBlowUp() - { - // Setup - var helper = new SignInManager(); - - // Act - helper.SignOut(); - } - - [Fact] - public async Task CreateUserIdentityReturnsNullNoUserManager() - { - // Setup - var user = new TestUser(); - var helper = new SignInManager(); - - // Act - var result = await helper.CreateUserIdentityAsync(user); - - // Assert - Assert.Null(result); - } - - [Fact] public async Task PasswordSignInFailsWithWrongPasswordCanAccessFailedAndLockout() { // Setup var user = new TestUser { UserName = "Foo" }; - var manager = new Mock>(); + var manager = MockHelpers.MockUserManager(); var lockedout = false; manager.Setup(m => m.AccessFailedAsync(user, CancellationToken.None)).Returns(() => { @@ -216,7 +307,10 @@ namespace Microsoft.AspNet.Identity.Security.Test manager.Setup(m => m.IsLockedOutAsync(user, CancellationToken.None)).Returns(() => Task.FromResult(lockedout)); manager.Setup(m => m.FindByNameAsync(user.UserName, CancellationToken.None)).ReturnsAsync(user).Verifiable(); manager.Setup(m => m.CheckPasswordAsync(user, "bogus", CancellationToken.None)).ReturnsAsync(false).Verifiable(); - var helper = new SignInManager { UserManager = manager.Object }; + var context = new Mock(); + var contextAccessor = new Mock>(); + contextAccessor.Setup(a => a.Value).Returns(context.Object); + var helper = new SignInManager(manager.Object, contextAccessor.Object); // Act var result = await helper.PasswordSignInAsync(user.UserName, "bogus", false, true); @@ -226,5 +320,30 @@ namespace Microsoft.AspNet.Identity.Security.Test manager.VerifyAll(); } #endif + public class ApplicationSignInManager : SignInManager + { + public ApplicationSignInManager(ApplicationUserManager manager, IContextAccessor contextAccessor) + : base(manager, contextAccessor) + { + AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie; + } + } + + public class NullLoggerFactory : ILoggerFactory + { + public ILogger Create(string name) + { + return new NullLogger(); + } + } + + public class NullLogger : ILogger + { + public bool WriteCore(TraceType eventType, int eventId, object state, Exception exception, Func formatter) + { + return false; + } + } + } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.Security.Test/project.json b/test/Microsoft.AspNet.Identity.Security.Test/project.json index 4eee20595a..62d2814d32 100644 --- a/test/Microsoft.AspNet.Identity.Security.Test/project.json +++ b/test/Microsoft.AspNet.Identity.Security.Test/project.json @@ -2,10 +2,18 @@ "version": "0.1-alpha-*", "dependencies": { "Microsoft.AspNet.Abstractions" : "0.1-alpha-*", - "Microsoft.AspNet.ConfigurationModel" : "0.1-alpha-*", + "Microsoft.AspNet.ConfigurationModel": "0.1-alpha-*", "Microsoft.AspNet.DependencyInjection" : "0.1-alpha-*", - "Microsoft.AspNet.Identity" : "", - "Microsoft.AspNet.Identity.Security" : "", + "Microsoft.AspNet.Identity" : "0.1-alpha-*", + "Microsoft.AspNet.Identity.InMemory" : "0.1-alpha-*", + "Microsoft.AspNet.Identity.Security" : "0.1-alpha-*", + "Microsoft.AspNet.Logging" : "0.1-alpha-*", + "Microsoft.AspNet.HttpFeature" : "0.1-alpha-*", + "Microsoft.AspNet.FeatureModel" : "0.1-alpha-*", + "Microsoft.AspNet.PipelineCore" : "0.1-alpha-*", + "Microsoft.AspNet.RequestContainer" : "0.1-alpha-*", + "Microsoft.AspNet.Security" : "0.1-alpha-*", + "Microsoft.AspNet.Security.Cookies" : "0.1-alpha-*", "Microsoft.AspNet.Testing" : "0.1-alpha-*", "System.Security.Claims" : "0.1-alpha-*", "Xunit.KRunner": "0.1-alpha-*", @@ -14,6 +22,7 @@ "xunit.core": "2.0.0-aspnet-*", "xunit.execution": "2.0.0-aspnet-*" }, + "code": "**\\*.cs;..\\Shared\\*.cs", "configurations": { "net45": { "dependencies": { diff --git a/test/Microsoft.AspNet.Identity.Test/ClaimsIdentityFactoryTest.cs b/test/Microsoft.AspNet.Identity.Test/ClaimsIdentityFactoryTest.cs index 88f7f4f500..cc1eec092e 100644 --- a/test/Microsoft.AspNet.Identity.Test/ClaimsIdentityFactoryTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/ClaimsIdentityFactoryTest.cs @@ -20,6 +20,8 @@ using System.Linq; using System.Security.Claims; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNet.DependencyInjection; +using Microsoft.AspNet.DependencyInjection.Fallback; using Moq; using Xunit; @@ -31,7 +33,7 @@ namespace Microsoft.AspNet.Identity.Test public async Task CreateIdentityNullChecks() { var factory = new ClaimsIdentityFactory(); - var manager = new UserManager(new NoopUserStore()); + var manager = MockHelpers.MockUserManager().Object; await Assert.ThrowsAsync("manager", async () => await factory.CreateAsync(null, null, "whatever")); await Assert.ThrowsAsync("user", @@ -50,7 +52,7 @@ namespace Microsoft.AspNet.Identity.Test public async Task EnsureClaimsIdentityHasExpectedClaims(bool supportRoles, bool supportClaims) { // Setup - var userManager = new Mock>(); + var userManager = MockHelpers.MockUserManager(); var user = new TestUser { UserName = "Foo" }; userManager.Setup(m => m.SupportsUserRole).Returns(supportRoles); userManager.Setup(m => m.SupportsUserClaim).Returns(supportClaims); @@ -60,6 +62,7 @@ namespace Microsoft.AspNet.Identity.Test userManager.Setup(m => m.GetRolesAsync(user, CancellationToken.None)).ReturnsAsync(roleClaims); var userClaims = new[] { new Claim("Whatever", "Value"), new Claim("Whatever2", "Value2") }; userManager.Setup(m => m.GetClaimsAsync(user, CancellationToken.None)).ReturnsAsync(userClaims); + userManager.Object.Options = new IdentityOptions(); const string authType = "Microsoft.AspNet.Identity"; var factory = new ClaimsIdentityFactory(); @@ -68,15 +71,16 @@ namespace Microsoft.AspNet.Identity.Test var identity = await factory.CreateAsync(userManager.Object, user, authType); // Assert + var manager = userManager.Object; Assert.NotNull(identity); Assert.Equal(authType, identity.AuthenticationType); var claims = identity.Claims.ToList(); Assert.NotNull(claims); Assert.True( - claims.Any(c => c.Type == factory.UserNameClaimType && c.Value == user.UserName)); - Assert.True(claims.Any(c => c.Type == factory.UserIdClaimType && c.Value == user.Id)); - Assert.Equal(supportRoles, claims.Any(c => c.Type == factory.RoleClaimType && c.Value == "Admin")); - Assert.Equal(supportRoles, claims.Any(c => c.Type == factory.RoleClaimType && c.Value == "Local")); + claims.Any(c => c.Type == manager.Options.ClaimType.UserName && c.Value == user.UserName)); + Assert.True(claims.Any(c => c.Type == manager.Options.ClaimType.UserId && c.Value == user.Id)); + Assert.Equal(supportRoles, claims.Any(c => c.Type == manager.Options.ClaimType.Role && c.Value == "Admin")); + Assert.Equal(supportRoles, claims.Any(c => c.Type == manager.Options.ClaimType.Role && c.Value == "Local")); foreach (var cl in userClaims) { Assert.Equal(supportClaims, claims.Any(c => c.Type == cl.Type && c.Value == cl.Value)); diff --git a/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs b/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs index 8c26146ce7..a087eaf117 100644 --- a/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs @@ -29,7 +29,7 @@ namespace Microsoft.AspNet.Identity.Test { var services = new ServiceCollection(); var validator = new UserValidator(); - services.AddIdentity(b => b.UseUserValidator(() => validator)); + services.AddIdentity(b => b.AddUserValidator(() => validator)); Assert.Equal(validator, services.BuildServiceProvider().GetService>()); } @@ -37,69 +37,47 @@ namespace Microsoft.AspNet.Identity.Test public void CanSpecifyPasswordValidatorInstance() { var services = new ServiceCollection(); - var validator = new PasswordValidator(); - services.AddIdentity(b => b.UsePasswordValidator(() => validator)); - Assert.Equal(validator, services.BuildServiceProvider().GetService()); - } - - [Fact] - public void CanSpecifyLockoutPolicyInstance() - { - var services = new ServiceCollection(); - var policy = new LockoutPolicy(); - services.AddIdentity(b => b.UseLockoutPolicy(() => policy)); - Assert.Equal(policy, services.BuildServiceProvider().GetService()); + var validator = new PasswordValidator(); + services.AddIdentity(b => b.AddPasswordValidator(() => validator)); + Assert.Equal(validator, services.BuildServiceProvider().GetService>()); } [Fact] public void CanSpecifyPasswordHasherInstance() { - CanOverride(); + CanOverride(new PasswordHasher()); } [Fact] public void CanSpecifyClaimsIdentityFactoryInstance() { - CanOverride, ClaimsIdentityFactory>(); + CanOverride>(new ClaimsIdentityFactory()); } [Fact] public void EnsureDefaultServices() { var services = new ServiceCollection(); - var builder = new IdentityBuilder(services); - builder.UseIdentity(); + services.AddIdentity(identity => { }); var provider = services.BuildServiceProvider(); var userValidator = provider.GetService>() as UserValidator; Assert.NotNull(userValidator); - Assert.True(userValidator.AllowOnlyAlphanumericUserNames); - Assert.False(userValidator.RequireUniqueEmail); - var pwdValidator = provider.GetService() as PasswordValidator; - Assert.NotNull(userValidator); - Assert.True(pwdValidator.RequireDigit); - Assert.True(pwdValidator.RequireLowercase); - Assert.True(pwdValidator.RequireNonLetterOrDigit); - Assert.True(pwdValidator.RequireUppercase); - Assert.Equal(6, pwdValidator.RequiredLength); + var pwdValidator = provider.GetService>() as PasswordValidator; + Assert.NotNull(pwdValidator); var hasher = provider.GetService() as PasswordHasher; Assert.NotNull(hasher); var claimsFactory = provider.GetService>() as ClaimsIdentityFactory; Assert.NotNull(claimsFactory); - Assert.Equal(ClaimTypes.Role, claimsFactory.RoleClaimType); - Assert.Equal(ClaimsIdentityFactory.DefaultSecurityStampClaimType, claimsFactory.SecurityStampClaimType); - Assert.Equal(ClaimTypes.Name, claimsFactory.UserNameClaimType); - Assert.Equal(ClaimTypes.NameIdentifier, claimsFactory.UserIdClaimType); } - private static void CanOverride() where TImplementation : TService,new() + private static void CanOverride(TService instance) { var services = new ServiceCollection(); - var instance = new TImplementation(); - services.AddIdentity(b => b.Use(() => instance)); + services.AddIdentity(b => b.AddInstance(() => instance)); Assert.Equal(instance, services.BuildServiceProvider().GetService()); } diff --git a/test/Microsoft.AspNet.Identity.Test/IdentityOptionsTest.cs b/test/Microsoft.AspNet.Identity.Test/IdentityOptionsTest.cs new file mode 100644 index 0000000000..36ace5390c --- /dev/null +++ b/test/Microsoft.AspNet.Identity.Test/IdentityOptionsTest.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Security.Claims; +using Microsoft.AspNet.ConfigurationModel; +using Microsoft.AspNet.ConfigurationModel.Sources; +using Xunit; + +namespace Microsoft.AspNet.Identity.Test +{ + public class IdentityOptionsTest + { + [Fact] + public void VerifyDefaultOptions() + { + var options = new IdentityOptions(); + Assert.False(options.Lockout.EnabledByDefault); + Assert.Equal(TimeSpan.FromMinutes(5), options.Lockout.DefaultLockoutTimeSpan); + Assert.Equal(5, options.Lockout.MaxFailedAccessAttempts); + + Assert.True(options.Password.RequireDigit); + Assert.True(options.Password.RequireLowercase); + Assert.True(options.Password.RequireNonLetterOrDigit); + Assert.True(options.Password.RequireUppercase); + Assert.Equal(6, options.Password.RequiredLength); + + Assert.True(options.User.AllowOnlyAlphanumericNames); + Assert.False(options.User.RequireUniqueEmail); + + Assert.Equal(ClaimTypes.Role, options.ClaimType.Role); + Assert.Equal(ClaimTypes.Name, options.ClaimType.UserName); + Assert.Equal(ClaimTypes.NameIdentifier, options.ClaimType.UserId); + Assert.Equal(ClaimTypeOptions.DefaultSecurityStampClaimType, options.ClaimType.SecurityStamp); + } + + [Fact] + public void CopyNullIsNoop() + { + var options = new IdentityOptions(); + options.Copy(null); + } + + [Fact] + public void IdentityOptionsFromConfig() + { + const string roleClaimType = "rolez"; + const string usernameClaimType = "namez"; + const string useridClaimType = "idz"; + const string securityStampClaimType = "stampz"; + + var dic = new Dictionary + { + {"identity:claimtype:role", roleClaimType}, + {"identity:claimtype:username", usernameClaimType}, + {"identity:claimtype:userid", useridClaimType}, + {"identity:claimtype:securitystamp", securityStampClaimType}, + {"identity:user:requireUniqueEmail", "true"}, + {"identity:password:RequiredLength", "10"}, + {"identity:password:RequireNonLetterOrDigit", "false"}, + {"identity:password:RequireUpperCase", "false"}, + {"identity:password:RequireDigit", "false"}, + {"identity:password:RequireLowerCase", "false"} + }; + var config = new ConfigurationModel.Configuration { new MemoryConfigurationSource(dic) }; + Assert.Equal(roleClaimType, config.Get("identity:claimtype:role")); + var options = new IdentityOptions(config); + Assert.Equal(roleClaimType, options.ClaimType.Role); + Assert.Equal(useridClaimType, options.ClaimType.UserId); + Assert.Equal(usernameClaimType, options.ClaimType.UserName); + Assert.Equal(securityStampClaimType, options.ClaimType.SecurityStamp); + Assert.True(options.User.RequireUniqueEmail); + Assert.True(options.User.AllowOnlyAlphanumericNames); + Assert.True(options.User.AllowOnlyAlphanumericNames); + Assert.False(options.Password.RequireDigit); + Assert.False(options.Password.RequireLowercase); + Assert.False(options.Password.RequireNonLetterOrDigit); + Assert.False(options.Password.RequireUppercase); + Assert.Equal(10, options.Password.RequiredLength); + } + + [Fact] + public void ClaimTypeOptionsFromConfig() + { + const string roleClaimType = "rolez"; + const string usernameClaimType = "namez"; + const string useridClaimType = "idz"; + const string securityStampClaimType = "stampz"; + + var dic = new Dictionary + { + {"role", roleClaimType}, + {"username", usernameClaimType}, + {"userid", useridClaimType}, + {"securitystamp", securityStampClaimType} + }; + var config = new ConfigurationModel.Configuration {new MemoryConfigurationSource(dic)}; + Assert.Equal(roleClaimType, config.Get("role")); + var options = new ClaimTypeOptions(config); + Assert.Equal(roleClaimType, options.Role); + Assert.Equal(useridClaimType, options.UserId); + Assert.Equal(usernameClaimType, options.UserName); + Assert.Equal(securityStampClaimType, options.SecurityStamp); + } + + [Fact] + public void PasswordOptionsFromConfig() + { + var dic = new Dictionary + { + {"RequiredLength", "10"}, + {"RequireNonLetterOrDigit", "false"}, + {"RequireUpperCase", "false"}, + {"RequireDigit", "false"}, + {"RequireLowerCase", "false"} + }; + var config = new ConfigurationModel.Configuration { new MemoryConfigurationSource(dic) }; + var options = new PasswordOptions(config); + Assert.False(options.RequireDigit); + Assert.False(options.RequireLowercase); + Assert.False(options.RequireNonLetterOrDigit); + Assert.False(options.RequireUppercase); + Assert.Equal(10, options.RequiredLength); + } + + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.Test/Microsoft.AspNet.Identity.Test.kproj b/test/Microsoft.AspNet.Identity.Test/Microsoft.AspNet.Identity.Test.kproj index 0ece19afba..817b6809e9 100644 --- a/test/Microsoft.AspNet.Identity.Test/Microsoft.AspNet.Identity.Test.kproj +++ b/test/Microsoft.AspNet.Identity.Test/Microsoft.AspNet.Identity.Test.kproj @@ -22,6 +22,7 @@ + @@ -30,10 +31,12 @@ - + + + \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.Test/PasswordValidatorTest.cs b/test/Microsoft.AspNet.Identity.Test/PasswordValidatorTest.cs index 24a1700332..f24abed774 100644 --- a/test/Microsoft.AspNet.Identity.Test/PasswordValidatorTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/PasswordValidatorTest.cs @@ -39,11 +39,12 @@ namespace Microsoft.AspNet.Identity.Test public async Task ValidateThrowsWithNullTest() { // Setup - var validator = new PasswordValidator(); + var validator = new PasswordValidator(); // Act // Assert - await Assert.ThrowsAsync(() => validator.ValidateAsync(null)); + await Assert.ThrowsAsync("password", () => validator.ValidateAsync(null, null)); + await Assert.ThrowsAsync("manager", () => validator.ValidateAsync("foo", null)); } @@ -54,8 +55,13 @@ namespace Microsoft.AspNet.Identity.Test public async Task FailsIfTooShortTests(string input) { const string error = "Passwords must be at least 6 characters."; - var valid = new PasswordValidator {RequiredLength = 6}; - IdentityResultAssert.IsFailure(await valid.ValidateAsync(input), error); + var manager = MockHelpers.TestUserManager(); + var valid = new PasswordValidator(); + manager.Options.Password.RequireUppercase = false; + manager.Options.Password.RequireNonLetterOrDigit = false; + manager.Options.Password.RequireLowercase = false; + manager.Options.Password.RequireDigit = false; + IdentityResultAssert.IsFailure(await valid.ValidateAsync(input, manager), error); } [Theory] @@ -63,8 +69,13 @@ namespace Microsoft.AspNet.Identity.Test [InlineData("aaaaaaaaaaa")] public async Task SuccessIfLongEnoughTests(string input) { - var valid = new PasswordValidator {RequiredLength = 6}; - IdentityResultAssert.IsSuccess(await valid.ValidateAsync(input)); + var manager = MockHelpers.TestUserManager(); + var valid = new PasswordValidator(); + manager.Options.Password.RequireUppercase = false; + manager.Options.Password.RequireNonLetterOrDigit = false; + manager.Options.Password.RequireLowercase = false; + manager.Options.Password.RequireDigit = false; + IdentityResultAssert.IsSuccess(await valid.ValidateAsync(input, manager)); } [Theory] @@ -72,8 +83,14 @@ namespace Microsoft.AspNet.Identity.Test [InlineData("aaaaaaaaaaa")] public async Task FailsWithoutRequiredNonAlphanumericTests(string input) { - var valid = new PasswordValidator {RequireNonLetterOrDigit = true}; - IdentityResultAssert.IsFailure(await valid.ValidateAsync(input), + var manager = MockHelpers.TestUserManager(); + var valid = new PasswordValidator(); + manager.Options.Password.RequireUppercase = false; + manager.Options.Password.RequireNonLetterOrDigit = true; + manager.Options.Password.RequireLowercase = false; + manager.Options.Password.RequireDigit = false; + manager.Options.Password.RequiredLength = 0; + IdentityResultAssert.IsFailure(await valid.ValidateAsync(input, manager), "Passwords must have at least one non letter or digit character."); } @@ -83,8 +100,14 @@ namespace Microsoft.AspNet.Identity.Test [InlineData("!!!!!!")] public async Task SucceedsWithRequiredNonAlphanumericTests(string input) { - var valid = new PasswordValidator {RequireNonLetterOrDigit = true}; - IdentityResultAssert.IsSuccess(await valid.ValidateAsync(input)); + var manager = MockHelpers.TestUserManager(); + var valid = new PasswordValidator(); + manager.Options.Password.RequireUppercase = false; + manager.Options.Password.RequireNonLetterOrDigit = true; + manager.Options.Password.RequireLowercase = false; + manager.Options.Password.RequireDigit = false; + manager.Options.Password.RequiredLength = 0; + IdentityResultAssert.IsSuccess(await valid.ValidateAsync(input, manager)); } [Theory] @@ -101,14 +124,8 @@ namespace Microsoft.AspNet.Identity.Test const string lowerError = "Passwords must have at least one lowercase ('a'-'z')."; const string digitError = "Passwords must have at least one digit ('0'-'9')."; const string lengthError = "Passwords must be at least 6 characters."; - var valid = new PasswordValidator - { - RequireNonLetterOrDigit = true, - RequireDigit = true, - RequireLowercase = true, - RequireUppercase = true, - RequiredLength = 6 - }; + var manager = MockHelpers.TestUserManager(); + var valid = new PasswordValidator(); var errors = new List(); if ((errorMask & Errors.Length) != Errors.None) { @@ -132,11 +149,11 @@ namespace Microsoft.AspNet.Identity.Test } if (errors.Count == 0) { - IdentityResultAssert.IsSuccess(await valid.ValidateAsync(input)); + IdentityResultAssert.IsSuccess(await valid.ValidateAsync(input, manager)); } else { - IdentityResultAssert.IsFailure(await valid.ValidateAsync(input), string.Join(" ", errors)); + IdentityResultAssert.IsFailure(await valid.ValidateAsync(input, manager), string.Join(" ", errors)); } } } diff --git a/test/Microsoft.AspNet.Identity.Test/RoleManagerTest.cs b/test/Microsoft.AspNet.Identity.Test/RoleManagerTest.cs index 786434edd6..b180d9bf59 100644 --- a/test/Microsoft.AspNet.Identity.Test/RoleManagerTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/RoleManagerTest.cs @@ -19,22 +19,18 @@ using System; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNet.DependencyInjection; +using Microsoft.AspNet.DependencyInjection.Fallback; using Xunit; namespace Microsoft.AspNet.Identity.Test { public class RoleManagerTest { - [Fact] - public void ConstructorThrowsWithNullStore() - { - Assert.Throws("store", () => new RoleManager((IRoleStore)null)); - } - [Fact] public void RolesQueryableFailWhenStoreNotImplemented() { - var manager = new RoleManager(new NoopRoleStore()); + var manager = new RoleManager(new ServiceCollection().BuildServiceProvider(), new NoopRoleStore()); Assert.False(manager.SupportsQueryableRoles); Assert.Throws(() => manager.Roles.Count()); } @@ -42,7 +38,7 @@ namespace Microsoft.AspNet.Identity.Test [Fact] public void DisposeAfterDisposeDoesNotThrow() { - var manager = new RoleManager(new NoopRoleStore()); + var manager = new RoleManager(new ServiceCollection().BuildServiceProvider(), new NoopRoleStore()); manager.Dispose(); manager.Dispose(); } @@ -50,10 +46,12 @@ namespace Microsoft.AspNet.Identity.Test [Fact] public async Task RoleManagerPublicNullChecks() { + var provider = new ServiceCollection().BuildServiceProvider(); Assert.Throws("store", - () => new RoleManager((IRoleStore)null)); - var manager = new RoleManager(new NotImplementedStore()); - Assert.Throws("services", () => manager.Initialize(null)); + () => new RoleManager(provider, null)); + Assert.Throws("services", + () => new RoleManager(null, new NotImplementedStore())); + var manager = new RoleManager(provider, new NotImplementedStore()); await Assert.ThrowsAsync("role", async () => await manager.CreateAsync(null)); await Assert.ThrowsAsync("role", async () => await manager.UpdateAsync(null)); await Assert.ThrowsAsync("role", async () => await manager.DeleteAsync(null)); @@ -64,7 +62,7 @@ namespace Microsoft.AspNet.Identity.Test [Fact] public async Task RoleStoreMethodsThrowWhenDisposed() { - var manager = new RoleManager(new NoopRoleStore()); + var manager = new RoleManager(new ServiceCollection().BuildServiceProvider(), new NoopRoleStore()); manager.Dispose(); await Assert.ThrowsAsync(() => manager.FindByIdAsync(null)); await Assert.ThrowsAsync(() => manager.FindByNameAsync(null)); diff --git a/test/Microsoft.AspNet.Identity.Test/RoleValidatorTest.cs b/test/Microsoft.AspNet.Identity.Test/RoleValidatorTest.cs index cc6f96c19f..55c8c38738 100644 --- a/test/Microsoft.AspNet.Identity.Test/RoleValidatorTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/RoleValidatorTest.cs @@ -17,6 +17,8 @@ using System; using System.Threading.Tasks; +using Microsoft.AspNet.DependencyInjection; +using Microsoft.AspNet.DependencyInjection.Fallback; using Xunit; namespace Microsoft.AspNet.Identity.Test @@ -27,7 +29,7 @@ namespace Microsoft.AspNet.Identity.Test public async Task ValidateThrowsWithNull() { // Setup - var manager = new RoleManager(new NoopRoleStore()); + var manager = new RoleManager(new ServiceCollection().BuildServiceProvider(), new NoopRoleStore()); var validator = new RoleValidator(); // Act @@ -42,7 +44,7 @@ namespace Microsoft.AspNet.Identity.Test public async Task ValidateFailsWithTooShortRoleName(string input) { // Setup - var manager = new RoleManager(new NoopRoleStore()); + var manager = new RoleManager(new ServiceCollection().BuildServiceProvider(), new NoopRoleStore()); var validator = new RoleValidator(); var user = new TestRole {Name = input}; diff --git a/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs b/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs index a5b2af6dce..1d0719aa4e 100644 --- a/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs @@ -34,23 +34,22 @@ namespace Microsoft.AspNet.Identity.Test { public IUserStore StorePublic { get { return Store; } } - public TestManager(IServiceProvider provider) : base(provider) { } + public TestManager(IServiceProvider provider, IUserStore store, IOptionsAccessor options) : base(provider, store, options) { } } [Fact] public void EnsureDefaultServicesDefaultsWithStoreWorks() { var services = new ServiceCollection {IdentityServices.GetDefaultUserServices()}; - services.AddInstance>(new NoopUserStore()); - var manager = new TestManager(services.BuildServiceProvider()); + services.AddInstance>(new OptionsAccessor(null)); + services.AddTransient, NoopUserStore>(); + services.AddTransient(); + var manager = services.BuildServiceProvider().GetService(); Assert.NotNull(manager.PasswordHasher); Assert.NotNull(manager.PasswordValidator); Assert.NotNull(manager.UserValidator); Assert.NotNull(manager.StorePublic); - Assert.NotNull(manager.LockoutPolicy); - Assert.Equal(TimeSpan.FromMinutes(5), manager.LockoutPolicy.DefaultAccountLockoutTimeSpan); - Assert.Equal(5, manager.LockoutPolicy.MaxFailedAccessAttemptsBeforeLockout); - Assert.False(manager.LockoutPolicy.UserLockoutEnabledByDefault); + Assert.NotNull(manager.Options); } #if NET45 @@ -61,11 +60,9 @@ namespace Microsoft.AspNet.Identity.Test // Setup var store = new Mock>(); var user = new TestUser { UserName = "Foo" }; + var options = new OptionsAccessor(null); store.Setup(s => s.CreateAsync(user, CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable(); - var validator = new Mock>(); - var userManager = new UserManager(store.Object); - validator.Setup(v => v.ValidateAsync(userManager, user, CancellationToken.None)).Returns(Task.FromResult(IdentityResult.Success)).Verifiable(); - userManager.UserValidator = validator.Object; + var userManager = MockHelpers.TestUserManager(store.Object); // Act var result = await userManager.CreateAsync(user); @@ -81,8 +78,9 @@ namespace Microsoft.AspNet.Identity.Test // Setup var store = new Mock>(); var user = new TestUser { UserName = "Foo" }; + var options = new OptionsAccessor(null); store.Setup(s => s.DeleteAsync(user, CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable(); - var userManager = new UserManager(store.Object); + var userManager = new UserManager(new ServiceCollection().BuildServiceProvider(), store.Object, options); // Act var result = await userManager.DeleteAsync(user); @@ -99,10 +97,7 @@ namespace Microsoft.AspNet.Identity.Test var store = new Mock>(); var user = new TestUser { UserName = "Foo" }; store.Setup(s => s.UpdateAsync(user, CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable(); - var validator = new Mock>(); - var userManager = new UserManager(store.Object); - validator.Setup(v => v.ValidateAsync(userManager, user, CancellationToken.None)).Returns(Task.FromResult(IdentityResult.Success)).Verifiable(); - userManager.UserValidator = validator.Object; + var userManager = MockHelpers.TestUserManager(store.Object); // Act var result = await userManager.UpdateAsync(user); @@ -119,10 +114,7 @@ namespace Microsoft.AspNet.Identity.Test var store = new Mock>(); var user = new TestUser(); store.Setup(s => s.SetUserNameAsync(user, It.IsAny(), CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable(); - var validator = new Mock>(); - var userManager = new UserManager(store.Object); - validator.Setup(v => v.ValidateAsync(userManager, user, CancellationToken.None)).Returns(Task.FromResult(IdentityResult.Success)).Verifiable(); - userManager.UserValidator = validator.Object; + var userManager = MockHelpers.TestUserManager(store.Object); // Act var result = await userManager.SetUserNameAsync(user, "foo"); @@ -139,7 +131,7 @@ namespace Microsoft.AspNet.Identity.Test var store = new Mock>(); var user = new TestUser { UserName = "Foo" }; store.Setup(s => s.FindByIdAsync(user.Id, CancellationToken.None)).Returns(Task.FromResult(user)).Verifiable(); - var userManager = new UserManager(store.Object); + var userManager = MockHelpers.TestUserManager(store.Object); // Act var result = await userManager.FindByIdAsync(user.Id); @@ -156,7 +148,7 @@ namespace Microsoft.AspNet.Identity.Test var store = new Mock>(); var user = new TestUser {UserName="Foo"}; store.Setup(s => s.FindByNameAsync(user.UserName, CancellationToken.None)).Returns(Task.FromResult(user)).Verifiable(); - var userManager = new UserManager(store.Object); + var userManager = MockHelpers.TestUserManager(store.Object); // Act var result = await userManager.FindByNameAsync(user.UserName); @@ -184,7 +176,7 @@ namespace Microsoft.AspNet.Identity.Test .Verifiable(); store.Setup(s => s.UpdateAsync(user, CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable(); store.Setup(s => s.GetRolesAsync(user, CancellationToken.None)).ReturnsAsync(new List()).Verifiable(); - var userManager = new UserManager(store.Object) {UserValidator = null}; + var userManager = MockHelpers.TestUserManager(store.Object); // Act var result = await userManager.AddToRolesAsync(user, roles); @@ -200,12 +192,12 @@ namespace Microsoft.AspNet.Identity.Test // Setup var store = new Mock>(); var user = new TestUser { UserName = "Foo" }; - var roles = new string[] { "A", "B", "C" }; + var roles = new[] { "A", "B", "C" }; store.Setup(s => s.AddToRoleAsync(user, "A", CancellationToken.None)) .Returns(Task.FromResult(0)) .Verifiable(); store.Setup(s => s.GetRolesAsync(user, CancellationToken.None)).ReturnsAsync(new List { "B" }).Verifiable(); - var userManager = new UserManager(store.Object) { UserValidator = null }; + var userManager = MockHelpers.TestUserManager(store.Object); // Act var result = await userManager.AddToRolesAsync(user, roles); @@ -221,7 +213,7 @@ namespace Microsoft.AspNet.Identity.Test // Setup var store = new Mock>(); var user = new TestUser { UserName = "Foo" }; - var roles = new string[] { "A", "B", "C" }; + var roles = new[] { "A", "B", "C" }; store.Setup(s => s.RemoveFromRoleAsync(user, "A", CancellationToken.None)) .Returns(Task.FromResult(0)) .Verifiable(); @@ -241,7 +233,7 @@ namespace Microsoft.AspNet.Identity.Test store.Setup(s => s.IsInRoleAsync(user, "C", CancellationToken.None)) .Returns(Task.FromResult(true)) .Verifiable(); - var userManager = new UserManager(store.Object) { UserValidator = null }; + var userManager = MockHelpers.TestUserManager(store.Object); // Act var result = await userManager.RemoveFromRolesAsync(user, roles); @@ -267,7 +259,7 @@ namespace Microsoft.AspNet.Identity.Test store.Setup(s => s.IsInRoleAsync(user, "B", CancellationToken.None)) .Returns(Task.FromResult(false)) .Verifiable(); - var userManager = new UserManager(store.Object) { UserValidator = null }; + var userManager = MockHelpers.TestUserManager(store.Object); // Act var result = await userManager.RemoveFromRolesAsync(user, roles); @@ -282,21 +274,21 @@ namespace Microsoft.AspNet.Identity.Test [Fact] public async Task CheckPasswordWithNullUserReturnsFalse() { - var manager = new UserManager(new EmptyStore()); + var manager = MockHelpers.TestUserManager(new EmptyStore()); Assert.False(await manager.CheckPasswordAsync(null, "whatevs")); } [Fact] public async Task FindWithUnknownUserAndPasswordReturnsNull() { - var manager = new UserManager(new EmptyStore()); + var manager = MockHelpers.TestUserManager(new EmptyStore()); Assert.Null(await manager.FindByUserNamePasswordAsync("bogus", "whatevs")); } [Fact] public void UsersQueryableFailWhenStoreNotImplemented() { - var manager = new UserManager(new NoopUserStore()); + var manager = MockHelpers.TestUserManager(new NoopUserStore()); Assert.False(manager.SupportsQueryableUsers); Assert.Throws(() => manager.Users.Count()); } @@ -304,7 +296,7 @@ namespace Microsoft.AspNet.Identity.Test [Fact] public async Task UsersEmailMethodsFailWhenStoreNotImplemented() { - var manager = new UserManager(new NoopUserStore()); + var manager = MockHelpers.TestUserManager(new NoopUserStore()); Assert.False(manager.SupportsUserEmail); await Assert.ThrowsAsync(() => manager.FindByEmailAsync(null)); await Assert.ThrowsAsync(() => manager.SetEmailAsync(null, null)); @@ -316,7 +308,7 @@ namespace Microsoft.AspNet.Identity.Test [Fact] public async Task UsersPhoneNumberMethodsFailWhenStoreNotImplemented() { - var manager = new UserManager(new NoopUserStore()); + var manager = MockHelpers.TestUserManager(new NoopUserStore()); Assert.False(manager.SupportsUserPhoneNumber); await Assert.ThrowsAsync(async () => await manager.SetPhoneNumberAsync(null, null)); await Assert.ThrowsAsync(async () => await manager.SetPhoneNumberAsync(null, null)); @@ -326,7 +318,7 @@ namespace Microsoft.AspNet.Identity.Test [Fact] public async Task TokenMethodsThrowWithNoTokenProvider() { - var manager = new UserManager(new NoopUserStore()); + var manager = MockHelpers.TestUserManager(new NoopUserStore()); await Assert.ThrowsAsync( async () => await manager.GenerateUserTokenAsync(null, null)); await Assert.ThrowsAsync( @@ -336,7 +328,7 @@ namespace Microsoft.AspNet.Identity.Test [Fact] public async Task PasswordMethodsFailWhenStoreNotImplemented() { - var manager = new UserManager(new NoopUserStore()); + var manager = MockHelpers.TestUserManager(new NoopUserStore()); Assert.False(manager.SupportsUserPassword); await Assert.ThrowsAsync(() => manager.CreateAsync(null, null)); await Assert.ThrowsAsync(() => manager.ChangePasswordAsync(null, null, null)); @@ -349,7 +341,7 @@ namespace Microsoft.AspNet.Identity.Test [Fact] public async Task SecurityStampMethodsFailWhenStoreNotImplemented() { - var manager = new UserManager(new NoopUserStore()); + var manager = MockHelpers.TestUserManager(new NoopUserStore()); Assert.False(manager.SupportsUserSecurityStamp); await Assert.ThrowsAsync(() => manager.UpdateSecurityStampAsync(null)); await Assert.ThrowsAsync(() => manager.GetSecurityStampAsync(null)); @@ -366,7 +358,7 @@ namespace Microsoft.AspNet.Identity.Test [Fact] public async Task LoginMethodsFailWhenStoreNotImplemented() { - var manager = new UserManager(new NoopUserStore()); + var manager = MockHelpers.TestUserManager(new NoopUserStore()); Assert.False(manager.SupportsUserLogin); await Assert.ThrowsAsync(async () => await manager.AddLoginAsync(null, null)); await Assert.ThrowsAsync(async () => await manager.RemoveLoginAsync(null, null)); @@ -377,7 +369,7 @@ namespace Microsoft.AspNet.Identity.Test [Fact] public async Task ClaimMethodsFailWhenStoreNotImplemented() { - var manager = new UserManager(new NoopUserStore()); + var manager = MockHelpers.TestUserManager(new NoopUserStore()); Assert.False(manager.SupportsUserClaim); await Assert.ThrowsAsync(async () => await manager.AddClaimAsync(null, null)); await Assert.ThrowsAsync(async () => await manager.RemoveClaimAsync(null, null)); @@ -387,7 +379,7 @@ namespace Microsoft.AspNet.Identity.Test [Fact] public async Task TwoFactorStoreMethodsFailWhenStoreNotImplemented() { - var manager = new UserManager(new NoopUserStore()); + var manager = MockHelpers.TestUserManager(new NoopUserStore()); Assert.False(manager.SupportsUserTwoFactor); await Assert.ThrowsAsync(async () => await manager.GetTwoFactorEnabledAsync(null)); await @@ -397,7 +389,7 @@ namespace Microsoft.AspNet.Identity.Test [Fact] public async Task LockoutStoreMethodsFailWhenStoreNotImplemented() { - var manager = new UserManager(new NoopUserStore()); + var manager = MockHelpers.TestUserManager(new NoopUserStore()); Assert.False(manager.SupportsUserLockout); await Assert.ThrowsAsync(async () => await manager.GetLockoutEnabledAsync(null)); await Assert.ThrowsAsync(async () => await manager.SetLockoutEnabledAsync(null, true)); @@ -410,7 +402,7 @@ namespace Microsoft.AspNet.Identity.Test [Fact] public async Task RoleMethodsFailWhenStoreNotImplemented() { - var manager = new UserManager(new NoopUserStore()); + var manager = MockHelpers.TestUserManager(new NoopUserStore()); Assert.False(manager.SupportsUserRole); await Assert.ThrowsAsync(async () => await manager.AddToRoleAsync(null, "bogus")); await Assert.ThrowsAsync(async () => await manager.AddToRolesAsync(null, null)); @@ -423,7 +415,7 @@ namespace Microsoft.AspNet.Identity.Test [Fact] public void DisposeAfterDisposeDoesNotThrow() { - var manager = new UserManager(new NoopUserStore()); + var manager = MockHelpers.TestUserManager(new NoopUserStore()); manager.Dispose(); manager.Dispose(); } @@ -432,23 +424,26 @@ namespace Microsoft.AspNet.Identity.Test public async Task PasswordValidatorBlocksCreate() { // TODO: Can switch to Mock eventually - var manager = new UserManager(new EmptyStore()) - { - PasswordValidator = new BadPasswordValidtor() - }; + var manager = MockHelpers.TestUserManager(new EmptyStore()); + manager.PasswordValidator = new BadPasswordValidator(); IdentityResultAssert.IsFailure(await manager.CreateAsync(new TestUser(), "password"), - BadPasswordValidtor.ErrorMessage); + BadPasswordValidator.ErrorMessage); } [Fact] public async Task ManagerPublicNullChecks() { + var provider = new ServiceCollection().BuildServiceProvider(); Assert.Throws("serviceProvider", - () => new UserManager((IServiceProvider)null)); - var manager = new UserManager(new NotImplementedStore()); - Assert.Throws(() => manager.ClaimsIdentityFactory = null); - Assert.Throws(() => manager.PasswordHasher = null); - Assert.Throws("serviceProvider", () => manager.Initialize(null)); + () => new UserManager(null, null, null)); + Assert.Throws("store", + () => new UserManager(provider, null, null)); + Assert.Throws("optionsAccessor", + () => new UserManager(provider, new NotImplementedStore(), null)); + var manager = new UserManager(provider, new NotImplementedStore(), new OptionsAccessor(null)); + Assert.Throws("value", () => manager.ClaimsIdentityFactory = null); + Assert.Throws("value", () => manager.PasswordHasher = null); + Assert.Throws("value", () => manager.Options = null); await Assert.ThrowsAsync("user", async () => await manager.CreateIdentityAsync(null, "whatever")); @@ -476,10 +471,8 @@ namespace Microsoft.AspNet.Identity.Test [Fact] public async Task MethodsFailWithUnknownUserTest() { - var manager = new UserManager(new EmptyStore()) - { - UserTokenProvider = new NoOpTokenProvider() - }; + var manager = MockHelpers.TestUserManager(new EmptyStore()); + manager.UserTokenProvider = new NoOpTokenProvider(); await Assert.ThrowsAsync("user", async () => await manager.GetUserNameAsync(null)); await Assert.ThrowsAsync("user", @@ -583,7 +576,7 @@ namespace Microsoft.AspNet.Identity.Test [Fact] public async Task MethodsThrowWhenDisposedTest() { - var manager = new UserManager(new NoopUserStore()); + var manager = MockHelpers.TestUserManager(new NoopUserStore()); manager.Dispose(); Assert.Throws(() => manager.ClaimsIdentityFactory); await Assert.ThrowsAsync(() => manager.AddClaimAsync(null, null)); @@ -620,11 +613,11 @@ namespace Microsoft.AspNet.Identity.Test await Assert.ThrowsAsync(() => manager.ConfirmEmailAsync(null, null)); } - private class BadPasswordValidtor : IPasswordValidator + private class BadPasswordValidator : IPasswordValidator where TUser : class { public const string ErrorMessage = "I'm Bad."; - public Task ValidateAsync(string password, CancellationToken cancellationToken = default(CancellationToken)) + public Task ValidateAsync(string password, UserManager manager, CancellationToken cancellationToken = default(CancellationToken)) { return Task.FromResult(IdentityResult.Failed(ErrorMessage)); } diff --git a/test/Microsoft.AspNet.Identity.Test/UserValidatorTest.cs b/test/Microsoft.AspNet.Identity.Test/UserValidatorTest.cs index 232b17eec1..c8fe257c04 100644 --- a/test/Microsoft.AspNet.Identity.Test/UserValidatorTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/UserValidatorTest.cs @@ -17,6 +17,8 @@ using System; using System.Threading.Tasks; +using Microsoft.AspNet.DependencyInjection; +using Microsoft.AspNet.DependencyInjection.Fallback; using Xunit; namespace Microsoft.AspNet.Identity.Test @@ -27,7 +29,7 @@ namespace Microsoft.AspNet.Identity.Test public async Task ValidateThrowsWithNull() { // Setup - var manager = new UserManager(new NoopUserStore()); + var manager = MockHelpers.TestUserManager(new NoopUserStore()); var validator = new UserValidator(); // Act @@ -42,7 +44,7 @@ namespace Microsoft.AspNet.Identity.Test public async Task ValidateFailsWithTooShortUserNames(string input) { // Setup - var manager = new UserManager(new NoopUserStore()); + var manager = MockHelpers.TestUserManager(new NoopUserStore()); var validator = new UserValidator(); var user = new TestUser {UserName = input}; @@ -62,7 +64,7 @@ namespace Microsoft.AspNet.Identity.Test public async Task DefaultAlphaNumericOnlyUserNameValidation(string userName, bool expectSuccess) { // Setup - var manager = new UserManager(new NoopUserStore()); + var manager = MockHelpers.TestUserManager(new NoopUserStore()); var validator = new UserValidator(); var user = new TestUser {UserName = userName}; @@ -89,8 +91,9 @@ namespace Microsoft.AspNet.Identity.Test public async Task CanAllowNonAlphaNumericUserName(string userName, bool expectSuccess) { // Setup - var manager = new UserManager(new NoopUserStore()); - var validator = new UserValidator {AllowOnlyAlphanumericUserNames = false}; + var manager = MockHelpers.TestUserManager(new NoopUserStore()); + manager.Options.User.AllowOnlyAlphanumericNames = false; + var validator = new UserValidator(); var user = new TestUser {UserName = userName}; // Act diff --git a/test/Microsoft.AspNet.Identity.Test/project.json b/test/Microsoft.AspNet.Identity.Test/project.json index 3619ac420d..99f8457433 100644 --- a/test/Microsoft.AspNet.Identity.Test/project.json +++ b/test/Microsoft.AspNet.Identity.Test/project.json @@ -11,6 +11,7 @@ "xunit.core": "2.0.0-aspnet-*", "xunit.execution": "2.0.0-aspnet-*" }, + "code": "**\\*.cs;..\\Shared\\*.cs", "configurations": { "net45": { "dependencies": { diff --git a/test/Shared/IdentityConfig.cs b/test/Shared/IdentityConfig.cs new file mode 100644 index 0000000000..057c31f48b --- /dev/null +++ b/test/Shared/IdentityConfig.cs @@ -0,0 +1,19 @@ +using System; +using Microsoft.AspNet.DependencyInjection; + +namespace Microsoft.AspNet.Identity.Test +{ + public class ApplicationUserManager : UserManager + { + public ApplicationUserManager(IServiceProvider services, IUserStore store, IOptionsAccessor options) : base(services, store, options) { } + } + + public class ApplicationRoleManager : RoleManager + { + public ApplicationRoleManager(IServiceProvider services, IRoleStore store) : base(services, store) { } + } + + public class ApplicationUser : IdentityUser + { + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.Test/IdentityResultAssert.cs b/test/Shared/IdentityResultAssert.cs similarity index 100% rename from test/Microsoft.AspNet.Identity.Test/IdentityResultAssert.cs rename to test/Shared/IdentityResultAssert.cs diff --git a/test/Shared/MockHelpers.cs b/test/Shared/MockHelpers.cs new file mode 100644 index 0000000000..a14e795b86 --- /dev/null +++ b/test/Shared/MockHelpers.cs @@ -0,0 +1,33 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNet.DependencyInjection; +using Microsoft.AspNet.DependencyInjection.Fallback; +using Moq; + +namespace Microsoft.AspNet.Identity.Test +{ + public static class MockHelpers + { + public static Mock> MockUserManager() where TUser : class + { + var store = new Mock>(); + var options = new OptionsAccessor(null); + return new Mock>(new ServiceCollection().BuildServiceProvider(), store.Object, options); + } + + public static UserManager TestUserManager() where TUser : class + { + return TestUserManager(new Mock>().Object); + } + + public static UserManager TestUserManager(IUserStore store) where TUser : class + { + var options = new OptionsAccessor(null); + var validator = new Mock>(); + var userManager = new UserManager(new ServiceCollection().BuildServiceProvider(), store, options); + validator.Setup(v => v.ValidateAsync(userManager, It.IsAny(), CancellationToken.None)).Returns(Task.FromResult(IdentityResult.Success)).Verifiable(); + userManager.UserValidator = validator.Object; + return userManager; + } + } +} \ No newline at end of file diff --git a/test/Shared/TestMessageService.cs b/test/Shared/TestMessageService.cs new file mode 100644 index 0000000000..409971a019 --- /dev/null +++ b/test/Shared/TestMessageService.cs @@ -0,0 +1,17 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.AspNet.Identity.Test +{ + public class TestMessageService : IIdentityMessageService + { + public IdentityMessage Message { get; set; } + + public Task SendAsync(IdentityMessage message, CancellationToken cancellationToken = default(CancellationToken)) + { + Message = message; + return Task.FromResult(0); + } + } + +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.Test/TestRole.cs b/test/Shared/TestRole.cs similarity index 100% rename from test/Microsoft.AspNet.Identity.Test/TestRole.cs rename to test/Shared/TestRole.cs diff --git a/test/Microsoft.AspNet.Identity.Test/TestUser.cs b/test/Shared/TestUser.cs similarity index 95% rename from test/Microsoft.AspNet.Identity.Test/TestUser.cs rename to test/Shared/TestUser.cs index 5b403701e3..f240ecf622 100644 --- a/test/Microsoft.AspNet.Identity.Test/TestUser.cs +++ b/test/Shared/TestUser.cs @@ -26,7 +26,7 @@ namespace Microsoft.AspNet.Identity.Test Id = Guid.NewGuid().ToString(); } - public string Id { get; set; } + public string Id { get; private set; } public string UserName { get; set; } } } \ No newline at end of file diff --git a/test/Shared/UserManagerTestBase.cs b/test/Shared/UserManagerTestBase.cs new file mode 100644 index 0000000000..74001c18c7 --- /dev/null +++ b/test/Shared/UserManagerTestBase.cs @@ -0,0 +1,1462 @@ +using System; +using System.Linq; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNet.DependencyInjection; +using Microsoft.AspNet.DependencyInjection.Fallback; +using Microsoft.AspNet.Identity.Test; +using Microsoft.AspNet.Testing; +using Xunit; + +namespace Microsoft.AspNet.Identity.Test +{ + public abstract class UserManagerTestBase where TUser: IdentityUser, new() where TRole: IdentityRole, new() + { + protected abstract UserManager CreateManager(); + protected abstract RoleManager CreateRoleManager(); + + protected TUser CreateTestUser() { + return new TUser() { UserName = Guid.NewGuid().ToString() }; + } + + protected TRole CreateRole(string name) { + return new TRole() { Name = name }; + } + + + [Fact] + public async Task CanDeleteUser() + { + var manager = CreateManager(); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + IdentityResultAssert.IsSuccess(await manager.DeleteAsync(user)); + Assert.Null(await manager.FindByIdAsync(user.Id)); + } + + [Fact] + public async Task CanUpdateUserName() + { + var manager = CreateManager(); + var user = new TUser() { UserName = "UpdateAsync" }; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + Assert.Null(await manager.FindByNameAsync("New")); + user.UserName = "New"; + IdentityResultAssert.IsSuccess(await manager.UpdateAsync(user)); + Assert.NotNull(await manager.FindByNameAsync("New")); + Assert.Null(await manager.FindByNameAsync("UpdateAsync")); + } + + [Fact] + public async Task CanSetUserName() + { + var manager = CreateManager(); + var user = new TUser() { UserName = "UpdateAsync" }; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + Assert.Null(await manager.FindByNameAsync("New")); + IdentityResultAssert.IsSuccess(await manager.SetUserNameAsync(user, "New")); + Assert.NotNull(await manager.FindByNameAsync("New")); + Assert.Null(await manager.FindByNameAsync("UpdateAsync")); + } + + [Fact] + public async Task CanFindById() + { + var manager = CreateManager(); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + Assert.NotNull(await manager.FindByIdAsync(user.Id)); + } + + [Fact] + public async Task UserValidatorCanBlockCreate() + { + var manager = CreateManager(); + var user = CreateTestUser(); + manager.UserValidator = new AlwaysBadValidator(); + IdentityResultAssert.IsFailure(await manager.CreateAsync(user), AlwaysBadValidator.ErrorMessage); + } + + [Fact] + public async Task UserValidatorCanBlockUpdate() + { + var manager = CreateManager(); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + manager.UserValidator = new AlwaysBadValidator(); + IdentityResultAssert.IsFailure(await manager.UpdateAsync(user), AlwaysBadValidator.ErrorMessage); + } + + [Theory] + [InlineData("")] + [InlineData(null)] + public async Task UserValidatorBlocksShortEmailsWhenRequiresUniqueEmail(string email) + { + var manager = CreateManager(); + var user = CreateTestUser(); + manager.Options.User.RequireUniqueEmail = true; + IdentityResultAssert.IsFailure(await manager.CreateAsync(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 TUser() { UserName = "UpdateBlocked", Email = email }; + manager.Options.User.RequireUniqueEmail = true; + IdentityResultAssert.IsFailure(await manager.CreateAsync(user), "Email '" + email + "' is invalid."); + } +#endif + + [Fact] + public async Task PasswordValidatorCanBlockAddPassword() + { + var manager = CreateManager(); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + manager.PasswordValidator = new AlwaysBadValidator(); + IdentityResultAssert.IsFailure(await manager.AddPasswordAsync(user, "password"), + AlwaysBadValidator.ErrorMessage); + } + + [Fact] + public async Task PasswordValidatorCanBlockChangePassword() + { + var manager = CreateManager(); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, "password")); + manager.PasswordValidator = new AlwaysBadValidator(); + IdentityResultAssert.IsFailure(await manager.ChangePasswordAsync(user, "password", "new"), + AlwaysBadValidator.ErrorMessage); + } + + [Fact] + public async Task CanCreateUserNoPassword() + { + var manager = CreateManager(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(new TUser() { UserName = "CreateUserTest" })); + var user = await manager.FindByNameAsync("CreateUserTest"); + Assert.NotNull(user); + Assert.Null(user.PasswordHash); + var logins = await manager.GetLoginsAsync(user); + 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.CreateAsync(new TUser() { UserName = userName })); + var user = await manager.FindByNameAsync(userName); + var login = new UserLoginInfo(provider, providerKey); + IdentityResultAssert.IsSuccess(await manager.AddLoginAsync(user, login)); + var logins = await manager.GetLoginsAsync(user); + 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 TUser() { UserName = "CreateUserLoginAddPasswordTest" }; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + IdentityResultAssert.IsSuccess(await manager.AddLoginAsync(user, login)); + Assert.False(await manager.HasPasswordAsync(user)); + IdentityResultAssert.IsSuccess(await manager.AddPasswordAsync(user, "password")); + Assert.True(await manager.HasPasswordAsync(user)); + var logins = await manager.GetLoginsAsync(user); + Assert.NotNull(logins); + Assert.Equal(1, logins.Count()); + Assert.Equal(user, await manager.FindByLoginAsync(login)); + Assert.Equal(user, await manager.FindByUserNamePasswordAsync(user.UserName, "password")); + } + + [Fact] + public async Task AddPasswordFailsIfAlreadyHave() + { + var manager = CreateManager(); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, "Password")); + Assert.True(await manager.HasPasswordAsync(user)); + IdentityResultAssert.IsFailure(await manager.AddPasswordAsync(user, "password"), + "User already has a password set."); + } + + [Fact] + public async Task CanCreateUserAddRemoveLogin() + { + var manager = CreateManager(); + var user = CreateTestUser(); + var login = new UserLoginInfo("Provider", "key"); + var result = await manager.CreateAsync(user); + Assert.NotNull(user); + IdentityResultAssert.IsSuccess(result); + IdentityResultAssert.IsSuccess(await manager.AddLoginAsync(user, login)); + Assert.Equal(user, await manager.FindByLoginAsync(login)); + var logins = await manager.GetLoginsAsync(user); + 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.RemoveLoginAsync(user, login)); + Assert.Null(await manager.FindByLoginAsync(login)); + logins = await manager.GetLoginsAsync(user); + Assert.NotNull(logins); + Assert.Equal(0, logins.Count()); + Assert.NotEqual(stamp, user.SecurityStamp); + } + + [Fact] + public async Task CanRemovePassword() + { + var manager = CreateManager(); + var user = new TUser() { UserName = "RemovePasswordTest" }; + const string password = "password"; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, password)); + var stamp = user.SecurityStamp; + IdentityResultAssert.IsSuccess(await manager.RemovePasswordAsync(user)); + var u = await manager.FindByNameAsync(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 TUser() { UserName = "ChangePasswordTest" }; + const string password = "password"; + const string newPassword = "newpassword"; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, password)); + var stamp = user.SecurityStamp; + Assert.NotNull(stamp); + IdentityResultAssert.IsSuccess(await manager.ChangePasswordAsync(user, password, newPassword)); + Assert.Null(await manager.FindByUserNamePasswordAsync(user.UserName, password)); + Assert.Equal(user, await manager.FindByUserNamePasswordAsync(user.UserName, newPassword)); + Assert.NotEqual(stamp, user.SecurityStamp); + } + + [Fact] + public async Task CanAddRemoveUserClaim() + { + var manager = CreateManager(); + var user = new TUser() { UserName = "ClaimsAddRemove" }; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + Claim[] claims = { new Claim("c", "v"), new Claim("c2", "v2"), new Claim("c2", "v3") }; + foreach (Claim c in claims) + { + IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user, c)); + } + var userClaims = await manager.GetClaimsAsync(user); + Assert.Equal(3, userClaims.Count); + IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(user, claims[0])); + userClaims = await manager.GetClaimsAsync(user); + Assert.Equal(2, userClaims.Count); + IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(user, claims[1])); + userClaims = await manager.GetClaimsAsync(user); + Assert.Equal(1, userClaims.Count); + IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(user, claims[2])); + userClaims = await manager.GetClaimsAsync(user); + Assert.Equal(0, userClaims.Count); + } + + [Fact] + public async Task ChangePasswordFallsIfPasswordWrong() + { + var manager = CreateManager(); + var user = new TUser() { UserName = "user" }; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, "password")); + var result = await manager.ChangePasswordAsync(user, "bogus", "newpassword"); + IdentityResultAssert.IsFailure(result, "Incorrect password."); + } + + [Fact] + public async Task AddDupeUserNameFails() + { + var manager = CreateManager(); + var user = new TUser() { UserName = "dupe" }; + var user2 = new TUser() { UserName = "dupe" }; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + IdentityResultAssert.IsFailure(await manager.CreateAsync(user2), "Name dupe is already taken."); + } + + [Fact] + public async Task AddDupeEmailAllowedByDefault() + { + var manager = CreateManager(); + var user = new TUser() { UserName = "dupe", Email = "yup@yup.com" }; + var user2 = new TUser() { UserName = "dupeEmail", Email = "yup@yup.com" }; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user2)); + } + + [Fact] + public async Task AddDupeEmailFallsWhenUniqueEmailRequired() + { + var manager = CreateManager(); + manager.Options.User.RequireUniqueEmail = true; + var user = new TUser() { UserName = "dupe", Email = "yup@yup.com" }; + var user2 = new TUser() { UserName = "dupeEmail", Email = "yup@yup.com" }; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + IdentityResultAssert.IsFailure(await manager.CreateAsync(user2), "Email 'yup@yup.com' is already taken."); + } + + [Fact] + public async Task UpdateSecurityStampActuallyChanges() + { + var manager = CreateManager(); + var user = new TUser() { UserName = "stampMe" }; + Assert.Null(user.SecurityStamp); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + var stamp = user.SecurityStamp; + Assert.NotNull(stamp); + IdentityResultAssert.IsSuccess(await manager.UpdateSecurityStampAsync(user)); + Assert.NotEqual(stamp, user.SecurityStamp); + } + + [Fact] + public async Task AddDupeLoginFails() + { + var manager = CreateManager(); + var user = new TUser() { UserName = "DupeLogin" }; + var login = new UserLoginInfo("provder", "key"); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + IdentityResultAssert.IsSuccess(await manager.AddLoginAsync(user, login)); + var result = await manager.AddLoginAsync(user, 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 TUser() { UserName = userName, Email = email }; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + var fetch = await manager.FindByEmailAsync(email); + Assert.Equal(user, fetch); + } + + [Fact] + public async Task CanFindUsersViaUserQuerable() + { + var mgr = CreateManager(); + var users = new[] + { + new TUser() { UserName = "user1" }, + new TUser() { UserName = "user2" }, + new TUser() { UserName = "user3" } + }; + foreach (TUser u in users) + { + IdentityResultAssert.IsSuccess(await mgr.CreateAsync(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 manager = CreateManager(); + var role = CreateRoleManager(); + var user = new TUser() { UserName = "Hao" }; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + IdentityResultAssert.IsSuccess(await role.CreateAsync(CreateRole("Admin"))); + IdentityResultAssert.IsSuccess(await role.CreateAsync(CreateRole("Local"))); + IdentityResultAssert.IsSuccess(await manager.AddToRoleAsync(user, "Admin")); + IdentityResultAssert.IsSuccess(await manager.AddToRoleAsync(user, "Local")); + Claim[] userClaims = + { + new Claim("Whatever", "Value"), + new Claim("Whatever2", "Value2") + }; + foreach (var c in userClaims) + { + IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user, c)); + } + + var identity = await manager.CreateIdentityAsync(user, "test"); + var claimsFactory = (ClaimsIdentityFactory)manager.ClaimsIdentityFactory; + Assert.NotNull(claimsFactory); + var claims = identity.Claims.ToList(); + Assert.NotNull(claims); + Assert.True( + claims.Any(c => c.Type == manager.Options.ClaimType.UserName && c.Value == user.UserName)); + Assert.True(claims.Any(c => c.Type == manager.Options.ClaimType.UserId && c.Value == user.Id)); + Assert.True(claims.Any(c => c.Type == manager.Options.ClaimType.Role && c.Value == "Admin")); + Assert.True(claims.Any(c => c.Type == manager.Options.ClaimType.Role && 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 TUser() { UserName = "test" }; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + Assert.False(await manager.IsEmailConfirmedAsync(user)); + } + + // TODO: No token provider implementations yet + private class StaticTokenProvider : IUserTokenProvider + { + public Task GenerateAsync(string purpose, UserManager manager, + TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult(MakeToken(purpose, user)); + } + + public Task ValidateAsync(string purpose, string token, UserManager manager, + TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult(token == MakeToken(purpose, user)); + } + + public Task NotifyAsync(string token, UserManager manager, TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult(0); + } + + public Task IsValidProviderForUserAsync(UserManager manager, TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult(true); + } + + private static string MakeToken(string purpose, TUser user) + { + return string.Join(":", user.Id, purpose, "ImmaToken"); + } + } + + [Fact] + public async Task CanResetPasswordWithStaticTokenProvider() + { + var manager = CreateManager(); + manager.UserTokenProvider = new StaticTokenProvider(); + var user = new TUser() { UserName = "ResetPasswordTest" }; + const string password = "password"; + const string newPassword = "newpassword"; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, password)); + var stamp = user.SecurityStamp; + Assert.NotNull(stamp); + var token = await manager.GeneratePasswordResetTokenAsync(user); + Assert.NotNull(token); + IdentityResultAssert.IsSuccess(await manager.ResetPassword(user, token, newPassword)); + Assert.Null(await manager.FindByUserNamePasswordAsync(user.UserName, password)); + Assert.Equal(user, await manager.FindByUserNamePasswordAsync(user.UserName, newPassword)); + Assert.NotEqual(stamp, user.SecurityStamp); + } + + [Fact] + public async Task PasswordValidatorCanBlockResetPasswordWithStaticTokenProvider() + { + var manager = CreateManager(); + manager.UserTokenProvider = new StaticTokenProvider(); + var user = new TUser() { UserName = "ResetPasswordTest" }; + const string password = "password"; + const string newPassword = "newpassword"; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, password)); + var stamp = user.SecurityStamp; + Assert.NotNull(stamp); + var token = await manager.GeneratePasswordResetTokenAsync(user); + Assert.NotNull(token); + manager.PasswordValidator = new AlwaysBadValidator(); + IdentityResultAssert.IsFailure(await manager.ResetPassword(user, token, newPassword), + AlwaysBadValidator.ErrorMessage); + Assert.NotNull(await manager.FindByUserNamePasswordAsync(user.UserName, password)); + Assert.Equal(user, await manager.FindByUserNamePasswordAsync(user.UserName, password)); + Assert.Equal(stamp, user.SecurityStamp); + } + + [Fact] + public async Task ResetPasswordWithStaticTokenProviderFailsWithWrongToken() + { + var manager = CreateManager(); + manager.UserTokenProvider = new StaticTokenProvider(); + var user = new TUser() { UserName = "ResetPasswordTest" }; + const string password = "password"; + const string newPassword = "newpassword"; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, password)); + var stamp = user.SecurityStamp; + Assert.NotNull(stamp); + IdentityResultAssert.IsFailure(await manager.ResetPassword(user, "bogus", newPassword), "Invalid token."); + Assert.NotNull(await manager.FindByUserNamePasswordAsync(user.UserName, password)); + Assert.Equal(user, await manager.FindByUserNamePasswordAsync(user.UserName, password)); + Assert.Equal(stamp, user.SecurityStamp); + } + + [Fact] + public async Task CanGenerateAndVerifyUserTokenWithStaticTokenProvider() + { + var manager = CreateManager(); + manager.UserTokenProvider = new StaticTokenProvider(); + var user = new TUser() { UserName = "UserTokenTest" }; + var user2 = new TUser() { UserName = "UserTokenTest2" }; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user2)); + var token = await manager.GenerateUserTokenAsync("test", user); + Assert.True(await manager.VerifyUserTokenAsync(user, "test", token)); + Assert.False(await manager.VerifyUserTokenAsync(user, "test2", token)); + Assert.False(await manager.VerifyUserTokenAsync(user, "test", token + "a")); + Assert.False(await manager.VerifyUserTokenAsync(user2, "test", token)); + } + + [Fact] + public async Task CanConfirmEmailWithStaticToken() + { + var manager = CreateManager(); + manager.UserTokenProvider = new StaticTokenProvider(); + var user = new TUser() { UserName = "test" }; + Assert.False(user.EmailConfirmed); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + var token = await manager.GenerateEmailConfirmationTokenAsync(user); + Assert.NotNull(token); + IdentityResultAssert.IsSuccess(await manager.ConfirmEmailAsync(user, token)); + Assert.True(await manager.IsEmailConfirmedAsync(user)); + IdentityResultAssert.IsSuccess(await manager.SetEmailAsync(user, null)); + Assert.False(await manager.IsEmailConfirmedAsync(user)); + } + + [Fact] + public async Task ConfirmEmailWithStaticTokenFailsWithWrongToken() + { + var manager = CreateManager(); + manager.UserTokenProvider = new StaticTokenProvider(); + var user = new TUser() { UserName = "test" }; + Assert.False(user.EmailConfirmed); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + IdentityResultAssert.IsFailure(await manager.ConfirmEmailAsync(user, "bogus"), "Invalid token."); + Assert.False(await manager.IsEmailConfirmedAsync(user)); + } + + // TODO: Can't reenable til we have a SecurityStamp linked token provider + //[Fact] + //public async Task ConfirmTokenFailsAfterPasswordChange() + //{ + // var manager = CreateManager(); + // var user = new TUser() { UserName = "test" }; + // Assert.False(user.EmailConfirmed); + // IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, "password")); + // var token = await manager.GenerateEmailConfirmationTokenAsync(user); + // Assert.NotNull(token); + // IdentityResultAssert.IsSuccess(await manager.ChangePasswordAsync(user, "password", "newpassword")); + // IdentityResultAssert.IsFailure(await manager.ConfirmEmailAsync(user, token), "Invalid token."); + // Assert.False(await manager.IsEmailConfirmedAsync(user)); + //} + + // Lockout tests + + [Fact] + public async Task SingleFailureLockout() + { + var mgr = CreateManager(); + mgr.Options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromHours(1); + mgr.Options.Lockout.EnabledByDefault = true; + mgr.Options.Lockout.MaxFailedAccessAttempts = 0; + var user = new TUser() { UserName = "fastLockout" }; + IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); + Assert.True(await mgr.GetLockoutEnabledAsync(user)); + Assert.True(user.LockoutEnabled); + Assert.False(await mgr.IsLockedOutAsync(user)); + IdentityResultAssert.IsSuccess(await mgr.AccessFailedAsync(user)); + Assert.True(await mgr.IsLockedOutAsync(user)); + Assert.True(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); + Assert.Equal(0, await mgr.GetAccessFailedCountAsync(user)); + } + + [Fact] + public async Task TwoFailureLockout() + { + var mgr = CreateManager(); + mgr.Options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromHours(1); + mgr.Options.Lockout.EnabledByDefault = true; + mgr.Options.Lockout.MaxFailedAccessAttempts = 2; + var user = new TUser() { UserName = "twoFailureLockout" }; + IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); + Assert.True(await mgr.GetLockoutEnabledAsync(user)); + Assert.True(user.LockoutEnabled); + Assert.False(await mgr.IsLockedOutAsync(user)); + IdentityResultAssert.IsSuccess(await mgr.AccessFailedAsync(user)); + Assert.False(await mgr.IsLockedOutAsync(user)); + Assert.False(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); + Assert.Equal(1, await mgr.GetAccessFailedCountAsync(user)); + IdentityResultAssert.IsSuccess(await mgr.AccessFailedAsync(user)); + Assert.True(await mgr.IsLockedOutAsync(user)); + Assert.True(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); + Assert.Equal(0, await mgr.GetAccessFailedCountAsync(user)); + } + + [Fact] + public async Task ResetAccessCountPreventsLockout() + { + var mgr = CreateManager(); + mgr.Options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromHours(1); + mgr.Options.Lockout.EnabledByDefault = true; + mgr.Options.Lockout.MaxFailedAccessAttempts = 2; + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); + Assert.True(await mgr.GetLockoutEnabledAsync(user)); + Assert.True(user.LockoutEnabled); + Assert.False(await mgr.IsLockedOutAsync(user)); + IdentityResultAssert.IsSuccess(await mgr.AccessFailedAsync(user)); + Assert.False(await mgr.IsLockedOutAsync(user)); + Assert.False(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); + Assert.Equal(1, await mgr.GetAccessFailedCountAsync(user)); + IdentityResultAssert.IsSuccess(await mgr.ResetAccessFailedCountAsync(user)); + Assert.Equal(0, await mgr.GetAccessFailedCountAsync(user)); + Assert.False(await mgr.IsLockedOutAsync(user)); + Assert.False(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); + IdentityResultAssert.IsSuccess(await mgr.AccessFailedAsync(user)); + Assert.False(await mgr.IsLockedOutAsync(user)); + Assert.False(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); + Assert.Equal(1, await mgr.GetAccessFailedCountAsync(user)); + } + + [Fact] + public async Task CanEnableLockoutManuallyAndLockout() + { + var mgr = CreateManager(); + mgr.Options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromHours(1); + mgr.Options.Lockout.MaxFailedAccessAttempts = 2; + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); + Assert.False(await mgr.GetLockoutEnabledAsync(user)); + Assert.False(user.LockoutEnabled); + IdentityResultAssert.IsSuccess(await mgr.SetLockoutEnabledAsync(user, true)); + Assert.True(await mgr.GetLockoutEnabledAsync(user)); + Assert.True(user.LockoutEnabled); + Assert.False(await mgr.IsLockedOutAsync(user)); + IdentityResultAssert.IsSuccess(await mgr.AccessFailedAsync(user)); + Assert.False(await mgr.IsLockedOutAsync(user)); + Assert.False(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); + Assert.Equal(1, await mgr.GetAccessFailedCountAsync(user)); + IdentityResultAssert.IsSuccess(await mgr.AccessFailedAsync(user)); + Assert.True(await mgr.IsLockedOutAsync(user)); + Assert.True(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); + Assert.Equal(0, await mgr.GetAccessFailedCountAsync(user)); + } + + [Fact] + public async Task UserNotLockedOutWithNullDateTimeAndIsSetToNullDate() + { + var mgr = CreateManager(); + mgr.Options.Lockout.EnabledByDefault = true; + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); + Assert.True(await mgr.GetLockoutEnabledAsync(user)); + Assert.True(user.LockoutEnabled); + IdentityResultAssert.IsSuccess(await mgr.SetLockoutEndDateAsync(user, new DateTimeOffset())); + Assert.False(await mgr.IsLockedOutAsync(user)); + Assert.Equal(new DateTimeOffset(), await mgr.GetLockoutEndDateAsync(user)); + Assert.Equal(new DateTimeOffset(), user.LockoutEnd); + } + + [Fact] + public async Task LockoutFailsIfNotEnabled() + { + var mgr = CreateManager(); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); + Assert.False(await mgr.GetLockoutEnabledAsync(user)); + Assert.False(user.LockoutEnabled); + IdentityResultAssert.IsFailure(await mgr.SetLockoutEndDateAsync(user, new DateTimeOffset()), + "Lockout is not enabled for this user."); + Assert.False(await mgr.IsLockedOutAsync(user)); + } + + [Fact] + public async Task LockoutEndToUtcNowMinus1SecInUserShouldNotBeLockedOut() + { + var mgr = CreateManager(); + mgr.Options.Lockout.EnabledByDefault = true; + var user = new TUser() { UserName = "LockoutUtcNowTest", LockoutEnd = DateTimeOffset.UtcNow.AddSeconds(-1) }; + IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); + Assert.True(await mgr.GetLockoutEnabledAsync(user)); + Assert.True(user.LockoutEnabled); + Assert.False(await mgr.IsLockedOutAsync(user)); + } + + [Fact] + public async Task LockoutEndToUtcNowSubOneSecondWithManagerShouldNotBeLockedOut() + { + var mgr = CreateManager(); + mgr.Options.Lockout.EnabledByDefault = true; + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); + Assert.True(await mgr.GetLockoutEnabledAsync(user)); + Assert.True(user.LockoutEnabled); + IdentityResultAssert.IsSuccess(await mgr.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow.AddSeconds(-1))); + Assert.False(await mgr.IsLockedOutAsync(user)); + } + + [Fact] + public async Task LockoutEndToUtcNowPlus5ShouldBeLockedOut() + { + var mgr = CreateManager(); + mgr.Options.Lockout.EnabledByDefault = true; + var user = new TUser() { UserName = "LockoutUtcNowTest", LockoutEnd = DateTimeOffset.UtcNow.AddMinutes(5) }; + IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); + Assert.True(await mgr.GetLockoutEnabledAsync(user)); + Assert.True(user.LockoutEnabled); + Assert.True(await mgr.IsLockedOutAsync(user)); + } + + [Fact] + public async Task UserLockedOutWithDateTimeLocalKindNowPlus30() + { + var mgr = CreateManager(); + mgr.Options.Lockout.EnabledByDefault = true; + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); + Assert.True(await mgr.GetLockoutEnabledAsync(user)); + Assert.True(user.LockoutEnabled); + var lockoutEnd = new DateTimeOffset(DateTime.Now.AddMinutes(30).ToLocalTime()); + IdentityResultAssert.IsSuccess(await mgr.SetLockoutEndDateAsync(user, lockoutEnd)); + Assert.True(await mgr.IsLockedOutAsync(user)); + var end = await mgr.GetLockoutEndDateAsync(user); + Assert.Equal(lockoutEnd, end); + } + + // Role Tests + [Fact] + public async Task CanCreateRoleTest() + { + var manager = CreateRoleManager(); + var role = CreateRole("create"); + Assert.False(await manager.RoleExistsAsync(role.Name)); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(role)); + Assert.True(await manager.RoleExistsAsync(role.Name)); + } + + private class AlwaysBadValidator : IUserValidator, IRoleValidator, + IPasswordValidator + { + public const string ErrorMessage = "I'm Bad."; + + public Task ValidateAsync(string password, UserManager manager, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult(IdentityResult.Failed(ErrorMessage)); + } + + public Task ValidateAsync(RoleManager manager, TRole role, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult(IdentityResult.Failed(ErrorMessage)); + } + + public Task ValidateAsync(UserManager manager, TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult(IdentityResult.Failed(ErrorMessage)); + } + } + + [Fact] + public async Task BadValidatorBlocksCreateRole() + { + var manager = CreateRoleManager(); + manager.RoleValidator = new AlwaysBadValidator(); + IdentityResultAssert.IsFailure(await manager.CreateAsync(CreateRole("blocked")), + AlwaysBadValidator.ErrorMessage); + } + + [Fact] + public async Task BadValidatorBlocksRoleUpdate() + { + var manager = CreateRoleManager(); + var role = CreateRole("poorguy"); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(role)); + var error = AlwaysBadValidator.ErrorMessage; + manager.RoleValidator = new AlwaysBadValidator(); + IdentityResultAssert.IsFailure(await manager.UpdateAsync(role), error); + } + + [Fact] + public async Task CanDeleteRoleTest() + { + var manager = CreateRoleManager(); + var role = CreateRole("delete"); + Assert.False(await manager.RoleExistsAsync(role.Name)); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(role)); + IdentityResultAssert.IsSuccess(await manager.DeleteAsync(role)); + Assert.False(await manager.RoleExistsAsync(role.Name)); + } + + [Fact] + public async Task CanRoleFindByIdTest() + { + var manager = CreateRoleManager(); + var role = CreateRole("FindByIdAsync"); + Assert.Null(await manager.FindByIdAsync(role.Id)); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(role)); + Assert.Equal(role, await manager.FindByIdAsync(role.Id)); + } + + [Fact] + public async Task CanRoleFindByName() + { + var manager = CreateRoleManager(); + var role = CreateRole("FindByNameAsync"); + Assert.Null(await manager.FindByNameAsync(role.Name)); + Assert.False(await manager.RoleExistsAsync(role.Name)); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(role)); + Assert.Equal(role, await manager.FindByNameAsync(role.Name)); + } + + [Fact] + public async Task CanUpdateRoleNameTest() + { + var manager = CreateRoleManager(); + var role = CreateRole("update"); + Assert.False(await manager.RoleExistsAsync(role.Name)); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(role)); + Assert.True(await manager.RoleExistsAsync(role.Name)); + role.Name = "Changed"; + IdentityResultAssert.IsSuccess(await manager.UpdateAsync(role)); + Assert.False(await manager.RoleExistsAsync("update")); + Assert.Equal(role, await manager.FindByNameAsync(role.Name)); + } + + [Fact] + public async Task CanQuerableRolesTest() + { + var manager = CreateRoleManager(); + TRole[] roles = + { + CreateRole("r1"), CreateRole("r2"), CreateRole("r3"), + CreateRole("r4") + }; + foreach (var r in roles) + { + IdentityResultAssert.IsSuccess(await manager.CreateAsync(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 = CreateRole("deleteNonEmpty"); + // Assert.False(await roleMgr.RoleExistsAsync(role.Name)); + // IdentityResultAssert.IsSuccess(await roleMgr.CreateAsync(role)); + // var user = new TUser() { UserName = "t"); + // IdentityResultAssert.IsSuccess(await userMgr.CreateAsync(user)); + // IdentityResultAssert.IsSuccess(await userMgr.AddToRoleAsync(user, role.Name)); + // IdentityResultAssert.IsSuccess(await roleMgr.DeleteAsync(role)); + // Assert.Null(await roleMgr.FindByNameAsync(role.Name)); + // Assert.False(await roleMgr.RoleExistsAsync(role.Name)); + // // REVIEW: We should throw if deleteing a non empty role? + // var roles = await userMgr.GetRolesAsync(user); + + // // 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 = CreateRole("deleteNonEmpty"); + //// Assert.False(await roleMgr.RoleExistsAsync(role.Name)); + //// IdentityResultAssert.IsSuccess(await roleMgr.CreateAsync(role)); + //// var user = new TUser() { UserName = "t"); + //// IdentityResultAssert.IsSuccess(await userMgr.CreateAsync(user)); + //// IdentityResultAssert.IsSuccess(await userMgr.AddToRoleAsync(user, role.Name)); + //// IdentityResultAssert.IsSuccess(await userMgr.DeleteAsync(user)); + //// role = roleMgr.FindByIdAsync(role.Id); + ////} + + [Fact] + public async Task CreateRoleFailsIfExists() + { + var manager = CreateRoleManager(); + var role = CreateRole("dupeRole"); + Assert.False(await manager.RoleExistsAsync(role.Name)); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(role)); + Assert.True(await manager.RoleExistsAsync(role.Name)); + var role2 = CreateRole("dupeRole"); + IdentityResultAssert.IsFailure(await manager.CreateAsync(role2)); + } + + [Fact] + public async Task CanAddUsersToRole() + { + var manager = CreateManager(); + var roleManager = CreateRoleManager(); + var role = CreateRole("addUserTest"); + IdentityResultAssert.IsSuccess(await roleManager.CreateAsync(role)); + TUser[] users = + { + new TUser() { UserName = "1"}, new TUser() { UserName = "2"}, new TUser() { UserName = "3"}, + new TUser() { UserName = "4"} + }; + foreach (var u in users) + { + IdentityResultAssert.IsSuccess(await manager.CreateAsync(u)); + IdentityResultAssert.IsSuccess(await manager.AddToRoleAsync(u, role.Name)); + Assert.True(await manager.IsInRoleAsync(u, role.Name)); + } + } + + [Fact] + public async Task CanGetRolesForUser() + { + var userManager = CreateManager(); + var roleManager = CreateRoleManager(); + TUser[] users = + { + new TUser() { UserName = "1"}, new TUser() { UserName = "2"}, new TUser() { UserName = "3"}, + new TUser() { UserName = "4"} + }; + TRole[] roles = + { + CreateRole("r1"), CreateRole("r2"), CreateRole("r3"), + CreateRole("r4") + }; + foreach (var u in users) + { + IdentityResultAssert.IsSuccess(await userManager.CreateAsync(u)); + } + foreach (var r in roles) + { + IdentityResultAssert.IsSuccess(await roleManager.CreateAsync(r)); + foreach (var u in users) + { + IdentityResultAssert.IsSuccess(await userManager.AddToRoleAsync(u, r.Name)); + Assert.True(await userManager.IsInRoleAsync(u, r.Name)); + } + } + + foreach (var u in users) + { + var rs = await userManager.GetRolesAsync(u); + 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 userManager = CreateManager(); + var roleManager = CreateRoleManager(); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await userManager.CreateAsync(user)); + TRole[] roles = + { + CreateRole("r1"), CreateRole("r2"), CreateRole("r3"), + CreateRole("r4") + }; + foreach (var r in roles) + { + IdentityResultAssert.IsSuccess(await roleManager.CreateAsync(r)); + IdentityResultAssert.IsSuccess(await userManager.AddToRoleAsync(user, r.Name)); + Assert.True(await userManager.IsInRoleAsync(user, r.Name)); + } + IdentityResultAssert.IsSuccess(await userManager.RemoveFromRoleAsync(user, roles[2].Name)); + Assert.False(await userManager.IsInRoleAsync(user, roles[2].Name)); + } + + [Fact] + public async Task CanRemoveUsersFromRole() + { + var userManager = CreateManager(); + var roleManager = CreateRoleManager(); + TUser[] users = + { + new TUser() { UserName = "1"}, new TUser() { UserName = "2"}, new TUser() { UserName = "3"}, + new TUser() { UserName = "4"} + }; + foreach (var u in users) + { + IdentityResultAssert.IsSuccess(await userManager.CreateAsync(u)); + } + var r = CreateRole("r1"); + IdentityResultAssert.IsSuccess(await roleManager.CreateAsync(r)); + foreach (var u in users) + { + IdentityResultAssert.IsSuccess(await userManager.AddToRoleAsync(u, r.Name)); + Assert.True(await userManager.IsInRoleAsync(u, r.Name)); + } + foreach (var u in users) + { + IdentityResultAssert.IsSuccess(await userManager.RemoveFromRoleAsync(u, r.Name)); + Assert.False(await userManager.IsInRoleAsync(u, r.Name)); + } + } + + [Fact] + public async Task RemoveUserNotInRoleFails() + { + var userMgr = CreateManager(); + var roleMgr = CreateRoleManager(); + var role = CreateRole("addUserDupeTest"); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await userMgr.CreateAsync(user)); + IdentityResultAssert.IsSuccess(await roleMgr.CreateAsync(role)); + var result = await userMgr.RemoveFromRoleAsync(user, role.Name); + IdentityResultAssert.IsFailure(result, "User is not in role."); + } + + [Fact] + public async Task AddUserToRoleFailsIfAlreadyInRole() + { + var userMgr = CreateManager(); + var roleMgr = CreateRoleManager(); + var role = CreateRole("addUserDupeTest"); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await userMgr.CreateAsync(user)); + IdentityResultAssert.IsSuccess(await roleMgr.CreateAsync(role)); + IdentityResultAssert.IsSuccess(await userMgr.AddToRoleAsync(user, role.Name)); + Assert.True(await userMgr.IsInRoleAsync(user, role.Name)); + IdentityResultAssert.IsFailure(await userMgr.AddToRoleAsync(user, role.Name), "User already in role."); + } + + [Fact] + public async Task CanFindRoleByNameWithManager() + { + var roleMgr = CreateRoleManager(); + var role = CreateRole("findRoleByNameTest"); + IdentityResultAssert.IsSuccess(await roleMgr.CreateAsync(role)); + Assert.Equal(role.Id, (await roleMgr.FindByNameAsync(role.Name)).Id); + } + + [Fact] + public async Task CanFindRoleWithManager() + { + var roleMgr = CreateRoleManager(); + var role = CreateRole("findRoleTest"); + IdentityResultAssert.IsSuccess(await roleMgr.CreateAsync(role)); + Assert.Equal(role.Name, (await roleMgr.FindByIdAsync(role.Id)).Name); + } + + [Fact] + public async Task SetPhoneNumberTest() + { + var manager = CreateManager(); + var user = CreateTestUser(); + user.PhoneNumber = "123-456-7890"; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + var stamp = await manager.GetSecurityStampAsync(user); + Assert.Equal(await manager.GetPhoneNumberAsync(user), "123-456-7890"); + IdentityResultAssert.IsSuccess(await manager.SetPhoneNumberAsync(user, "111-111-1111")); + Assert.Equal(await manager.GetPhoneNumberAsync(user), "111-111-1111"); + Assert.NotEqual(stamp, user.SecurityStamp); + } + +#if NET45 + [Fact] + public async Task CanChangePhoneNumber() + { + var manager = CreateManager(); + var user = CreateTestUser(); + user.PhoneNumber = "123-456-7890"; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + Assert.False(await manager.IsPhoneNumberConfirmedAsync(user)); + var stamp = await manager.GetSecurityStampAsync(user); + var token1 = await manager.GenerateChangePhoneNumberTokenAsync(user, "111-111-1111"); + IdentityResultAssert.IsSuccess(await manager.ChangePhoneNumberAsync(user, "111-111-1111", token1)); + Assert.True(await manager.IsPhoneNumberConfirmedAsync(user)); + Assert.Equal(await manager.GetPhoneNumberAsync(user), "111-111-1111"); + Assert.NotEqual(stamp, user.SecurityStamp); + } + + [Fact] + public async Task ChangePhoneNumberFailsWithWrongToken() + { + var manager = CreateManager(); + var user = CreateTestUser(); + user.PhoneNumber = "123-456-7890"; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + Assert.False(await manager.IsPhoneNumberConfirmedAsync(user)); + var stamp = await manager.GetSecurityStampAsync(user); + IdentityResultAssert.IsFailure(await manager.ChangePhoneNumberAsync(user, "111-111-1111", "bogus"), + "Invalid token."); + Assert.False(await manager.IsPhoneNumberConfirmedAsync(user)); + Assert.Equal(await manager.GetPhoneNumberAsync(user), "123-456-7890"); + Assert.Equal(stamp, user.SecurityStamp); + } + + [Fact] + public async Task CanVerifyPhoneNumber() + { + var manager = CreateManager(); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + const string num1 = "111-123-4567"; + const string num2 = "111-111-1111"; + var token1 = await manager.GenerateChangePhoneNumberTokenAsync(user, num1); + var token2 = await manager.GenerateChangePhoneNumberTokenAsync(user, num2); + Assert.NotEqual(token1, token2); + Assert.True(await manager.VerifyChangePhoneNumberTokenAsync(user, token1, num1)); + Assert.True(await manager.VerifyChangePhoneNumberTokenAsync(user, token2, num2)); + Assert.False(await manager.VerifyChangePhoneNumberTokenAsync(user, token2, num1)); + Assert.False(await manager.VerifyChangePhoneNumberTokenAsync(user, token1, num2)); + } +#endif + + private class EmailTokenProvider : IUserTokenProvider + { + public Task GenerateAsync(string purpose, UserManager manager, TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult(MakeToken(purpose)); + } + + public Task ValidateAsync(string purpose, string token, UserManager manager, + TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult(token == MakeToken(purpose)); + } + + public Task NotifyAsync(string token, UserManager manager, TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + return manager.SendEmailAsync(user, token, token); + } + + public async Task IsValidProviderForUserAsync(UserManager manager, TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + return !string.IsNullOrEmpty(await manager.GetEmailAsync(user)); + } + + private static string MakeToken(string purpose) + { + return "email:" + purpose; + } + } + + private class SmsTokenProvider : IUserTokenProvider + { + public Task GenerateAsync(string purpose, UserManager manager, TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult(MakeToken(purpose)); + } + + public Task ValidateAsync(string purpose, string token, UserManager manager, + TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult(token == MakeToken(purpose)); + } + + public Task NotifyAsync(string token, UserManager manager, TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + return manager.SendSmsAsync(user, token, cancellationToken); + } + + public async Task IsValidProviderForUserAsync(UserManager manager, TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + return !string.IsNullOrEmpty(await manager.GetPhoneNumberAsync(user, cancellationToken)); + } + + 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 TUser() { UserName = "EmailCodeTest", Email = "foo@foo.com" }; + const string password = "password"; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, password)); + var stamp = user.SecurityStamp; + Assert.NotNull(stamp); + var token = await manager.GenerateTwoFactorTokenAsync(user, factorId); + Assert.NotNull(token); + Assert.Null(messageService.Message); + IdentityResultAssert.IsSuccess(await manager.NotifyTwoFactorTokenAsync(user, factorId, token)); + Assert.NotNull(messageService.Message); + Assert.Equal(token, messageService.Message.Subject); + Assert.Equal(token, messageService.Message.Body); + Assert.True(await manager.VerifyTwoFactorTokenAsync(user, factorId, token)); + } + + [Fact] + public async Task NotifyWithUnknownProviderFails() + { + var manager = CreateManager(); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + await + ExceptionAssert.ThrowsAsync( + async () => await manager.NotifyTwoFactorTokenAsync(user, "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 TUser() { UserName = "EmailCodeTest") { Email = "foo@foo.com" }; + // const string password = "password"; + // IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, password)); + // var stamp = user.SecurityStamp; + // Assert.NotNull(stamp); + // var token = await manager.GenerateTwoFactorTokenAsync(user, factorId); + // Assert.NotNull(token); + // Assert.Null(messageService.Message); + // IdentityResultAssert.IsSuccess(await manager.NotifyTwoFactorTokenAsync(user, 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.VerifyTwoFactorTokenAsync(user, factorId, token)); + //} + + //[Fact] + //public async Task EmailFactorFailsAfterSecurityStampChangeTest() + //{ + // var manager = CreateManager(); + // const string factorId = "EmailCode"; + // manager.RegisterTwoFactorProvider(factorId, new EmailTokenProvider()); + // var user = new TUser() { UserName = "EmailCodeTest") { Email = "foo@foo.com" }; + // IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + // var stamp = user.SecurityStamp; + // Assert.NotNull(stamp); + // var token = await manager.GenerateTwoFactorTokenAsync(user, factorId); + // Assert.NotNull(token); + // IdentityResultAssert.IsSuccess(await manager.UpdateSecurityStampAsync(user)); + // Assert.False(await manager.VerifyTwoFactorTokenAsync(user, factorId, token)); + //} + + [Fact] + public async Task EnableTwoFactorChangesSecurityStamp() + { + var manager = CreateManager(); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + var stamp = user.SecurityStamp; + Assert.NotNull(stamp); + IdentityResultAssert.IsSuccess(await manager.SetTwoFactorEnabledAsync(user, true)); + Assert.NotEqual(stamp, await manager.GetSecurityStampAsync(user)); + Assert.True(await manager.GetTwoFactorEnabledAsync(user)); + } + + [Fact] + public async Task CanSendSms() + { + var manager = CreateManager(); + var messageService = new TestMessageService(); + manager.SmsService = messageService; + var user = new TUser() { UserName = "SmsTest", PhoneNumber = "4251234567" }; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + await manager.SendSmsAsync(user, "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 TUser() { UserName = "EmailTest", Email = "foo@foo.com" }; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + await manager.SendEmailAsync(user, "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 TUser() { UserName = "PhoneCodeTest", PhoneNumber = "4251234567" }; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + var stamp = user.SecurityStamp; + Assert.NotNull(stamp); + var token = await manager.GenerateTwoFactorTokenAsync(user, factorId); + Assert.NotNull(token); + Assert.Null(messageService.Message); + IdentityResultAssert.IsSuccess(await manager.NotifyTwoFactorTokenAsync(user, factorId, token)); + Assert.NotNull(messageService.Message); + Assert.Equal(token, messageService.Message.Body); + Assert.True(await manager.VerifyTwoFactorTokenAsync(user, 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 TUser() { UserName = "PhoneCodeTest", PhoneNumber = "4251234567" }; + // IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + // var stamp = user.SecurityStamp; + // Assert.NotNull(stamp); + // var token = await manager.GenerateTwoFactorTokenAsync(user, factorId); + // Assert.NotNull(token); + // Assert.Null(messageService.Message); + // IdentityResultAssert.IsSuccess(await manager.NotifyTwoFactorTokenAsync(user, factorId, token)); + // Assert.NotNull(messageService.Message); + // Assert.Equal("Your code is: " + token, messageService.Message.Body); + // Assert.True(await manager.VerifyTwoFactorTokenAsync(user, factorId, token)); + //} + + [Fact] + public async Task GenerateTwoFactorWithUnknownFactorProviderWillThrow() + { + var manager = CreateManager(); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + const string error = "No IUserTwoFactorProvider for 'bogus' is registered."; + await + ExceptionAssert.ThrowsAsync( + () => manager.GenerateTwoFactorTokenAsync(user, "bogus"), error); + await ExceptionAssert.ThrowsAsync( + () => manager.VerifyTwoFactorTokenAsync(user, "bogus", "bogus"), error); + } + + [Fact] + public async Task GetValidTwoFactorTestEmptyWithNoProviders() + { + var manager = CreateManager(); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + var factors = await manager.GetValidTwoFactorProvidersAsync(user); + 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 = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + var factors = await manager.GetValidTwoFactorProvidersAsync(user); + Assert.NotNull(factors); + Assert.True(!factors.Any()); + IdentityResultAssert.IsSuccess(await manager.SetPhoneNumberAsync(user, "111-111-1111")); + factors = await manager.GetValidTwoFactorProvidersAsync(user); + Assert.NotNull(factors); + Assert.True(factors.Count() == 1); + Assert.Equal("phone", factors[0]); + IdentityResultAssert.IsSuccess(await manager.SetEmailAsync(user, "test@test.com")); + factors = await manager.GetValidTwoFactorProvidersAsync(user); + Assert.NotNull(factors); + Assert.True(factors.Count() == 2); + IdentityResultAssert.IsSuccess(await manager.SetEmailAsync(user, null)); + factors = await manager.GetValidTwoFactorProvidersAsync(user); + 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 TUser() { UserName = "PhoneCodeTest"); + // user.PhoneNumber = "4251234567"; + // IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + // var stamp = user.SecurityStamp; + // Assert.NotNull(stamp); + // var token = await manager.GenerateTwoFactorTokenAsync(user, factorId); + // Assert.NotNull(token); + // IdentityResultAssert.IsSuccess(await manager.UpdateSecurityStampAsync(user)); + // Assert.False(await manager.VerifyTwoFactorTokenAsync(user, factorId, token)); + //} + + [Fact] + public async Task VerifyTokenFromWrongTokenProviderFails() + { + var manager = CreateManager(); + manager.RegisterTwoFactorProvider("PhoneCode", new SmsTokenProvider()); + manager.RegisterTwoFactorProvider("EmailCode", new EmailTokenProvider()); + var user = new TUser() { UserName = "WrongTokenProviderTest", PhoneNumber = "4251234567" }; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + var token = await manager.GenerateTwoFactorTokenAsync(user, "PhoneCode"); + Assert.NotNull(token); + Assert.False(await manager.VerifyTwoFactorTokenAsync(user, "EmailCode", token)); + } + + [Fact] + public async Task VerifyWithWrongSmsTokenFails() + { + var manager = CreateManager(); + const string factorId = "PhoneCode"; + manager.RegisterTwoFactorProvider(factorId, new SmsTokenProvider()); + var user = new TUser() { UserName = "PhoneCodeTest", PhoneNumber = "4251234567" }; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + Assert.False(await manager.VerifyTwoFactorTokenAsync(user, factorId, "bogus")); + } + + public class TestSetup : IOptionsSetup + { + private readonly IdentityOptions _options; + + public TestSetup(IdentityOptions options) + { + _options = options; + } + + public int Order { get { return 0; } } + public void Setup(IdentityOptions options) + { + options.Copy(_options); + } + } + } +} \ No newline at end of file