From 5658af6b61a1b818cf17d657eb50a5285f719718 Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Thu, 4 Dec 2014 13:06:41 -0800 Subject: [PATCH] Allow multiple validators Role/Password/User validators are now IEnumerable instead of a single instance --- .../IPasswordValidator.cs | 2 +- .../PasswordValidator.cs | 2 +- src/Microsoft.AspNet.Identity/RoleManager.cs | 29 ++++++--- src/Microsoft.AspNet.Identity/UserManager.cs | 62 ++++++++++++++----- .../HttpSignInTest.cs | 10 ++- .../IdentityBuilderTest.cs | 2 +- .../PasswordValidatorTest.cs | 14 ++--- .../RoleManagerTest.cs | 12 ++-- .../RoleValidatorTest.cs | 6 +- .../UserManagerTest.cs | 17 +++-- test/Shared/MockHelpers.cs | 36 ++++------- test/Shared/UserManagerTestBase.cs | 62 ++++++++++++++++--- 12 files changed, 159 insertions(+), 95 deletions(-) diff --git a/src/Microsoft.AspNet.Identity/IPasswordValidator.cs b/src/Microsoft.AspNet.Identity/IPasswordValidator.cs index 4a3f652584..d851d324fb 100644 --- a/src/Microsoft.AspNet.Identity/IPasswordValidator.cs +++ b/src/Microsoft.AspNet.Identity/IPasswordValidator.cs @@ -15,7 +15,7 @@ namespace Microsoft.AspNet.Identity /// Validate the password for the user /// /// - Task ValidateAsync(TUser user, string password, UserManager manager, + Task ValidateAsync(UserManager manager, TUser user, string password, CancellationToken cancellationToken = default(CancellationToken)); } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/PasswordValidator.cs b/src/Microsoft.AspNet.Identity/PasswordValidator.cs index 813048bf5e..2a8bcf730f 100644 --- a/src/Microsoft.AspNet.Identity/PasswordValidator.cs +++ b/src/Microsoft.AspNet.Identity/PasswordValidator.cs @@ -22,7 +22,7 @@ namespace Microsoft.AspNet.Identity /// /// /// - public virtual Task ValidateAsync(TUser user, string password, UserManager manager, + public virtual Task ValidateAsync(UserManager manager, TUser user, string password, CancellationToken cancellationToken = default(CancellationToken)) { if (password == null) diff --git a/src/Microsoft.AspNet.Identity/RoleManager.cs b/src/Microsoft.AspNet.Identity/RoleManager.cs index 0533a0fe58..6b30e437fc 100644 --- a/src/Microsoft.AspNet.Identity/RoleManager.cs +++ b/src/Microsoft.AspNet.Identity/RoleManager.cs @@ -24,18 +24,21 @@ namespace Microsoft.AspNet.Identity /// /// The IRoleStore commits changes via the UpdateAsync/CreateAsync methods /// - public RoleManager(IRoleStore store, IRoleValidator roleValidator) + public RoleManager(IRoleStore store, IEnumerable> roleValidators) { if (store == null) { throw new ArgumentNullException("store"); } - if (roleValidator == null) - { - throw new ArgumentNullException("roleValidator"); - } - RoleValidator = roleValidator; Store = store; + + if (roleValidators != null) + { + foreach (var v in roleValidators) + { + RoleValidators.Add(v); + } + } } /// @@ -46,7 +49,7 @@ namespace Microsoft.AspNet.Identity /// /// Used to validate roles before persisting changes /// - public IRoleValidator RoleValidator { get; set; } + public IList> RoleValidators { get; } = new List>(); /// /// Returns an IQueryable of roles if the store is an IQueryableRoleStore @@ -99,8 +102,16 @@ namespace Microsoft.AspNet.Identity private async Task ValidateRoleInternal(TRole role, CancellationToken cancellationToken) { - return (RoleValidator == null) ? IdentityResult.Success : - await RoleValidator.ValidateAsync(this, role, cancellationToken); + var errors = new List(); + foreach (var v in RoleValidators) + { + var result = await v.ValidateAsync(this, role, cancellationToken); + if (!result.Succeeded) + { + errors.AddRange(result.Errors); + } + } + return errors.Count > 0 ? IdentityResult.Failed(errors.ToArray()) : IdentityResult.Success; } /// diff --git a/src/Microsoft.AspNet.Identity/UserManager.cs b/src/Microsoft.AspNet.Identity/UserManager.cs index 08ad898aa8..a42fa4c49f 100644 --- a/src/Microsoft.AspNet.Identity/UserManager.cs +++ b/src/Microsoft.AspNet.Identity/UserManager.cs @@ -41,8 +41,8 @@ namespace Microsoft.AspNet.Identity public UserManager(IUserStore store, IOptions optionsAccessor, IPasswordHasher passwordHasher, - IUserValidator userValidator, - IPasswordValidator passwordValidator, + IEnumerable> userValidators, + IEnumerable> passwordValidators, IUserNameNormalizer userNameNormalizer, IEnumerable> tokenProviders, IEnumerable msgProviders) @@ -62,11 +62,21 @@ namespace Microsoft.AspNet.Identity Store = store; Options = optionsAccessor.Options; PasswordHasher = passwordHasher; - UserValidator = userValidator; - PasswordValidator = passwordValidator; UserNameNormalizer = userNameNormalizer; - // TODO: Email/Sms/Token services - + if (userValidators != null) + { + foreach (var v in userValidators) + { + UserValidators.Add(v); + } + } + if (passwordValidators != null) + { + foreach (var v in passwordValidators) + { + PasswordValidators.Add(v); + } + } if (tokenProviders != null) { foreach (var tokenProvider in tokenProviders) @@ -114,12 +124,12 @@ namespace Microsoft.AspNet.Identity /// /// Used to validate users before persisting changes /// - public IUserValidator UserValidator { get; set; } + public IList> UserValidators { get; } = new List>(); /// /// Used to validate passwords before persisting changes /// - public IPasswordValidator PasswordValidator { get; set; } + public IList> PasswordValidators { get; } = new List>(); /// /// Used to normalize user names for uniqueness @@ -291,9 +301,30 @@ namespace Microsoft.AspNet.Identity private async Task ValidateUserInternal(TUser user, CancellationToken cancellationToken) { - return (UserValidator == null) - ? IdentityResult.Success - : await UserValidator.ValidateAsync(this, user, cancellationToken); + var errors = new List(); + foreach (var v in UserValidators) + { + var result = await v.ValidateAsync(this, user, cancellationToken); + if (!result.Succeeded) + { + errors.AddRange(result.Errors); + } + } + return errors.Count > 0 ? IdentityResult.Failed(errors.ToArray()) : IdentityResult.Success; + } + + private async Task ValidatePasswordInternal(TUser user, string password, CancellationToken cancellationToken) + { + var errors = new List(); + foreach (var v in PasswordValidators) + { + var result = await v.ValidateAsync(this, user, password, cancellationToken); + if (!result.Succeeded) + { + errors.AddRange(result.Errors); + } + } + return errors.Count > 0 ? IdentityResult.Failed(errors.ToArray()) : IdentityResult.Success; } /// @@ -629,13 +660,10 @@ namespace Microsoft.AspNet.Identity internal async Task UpdatePasswordInternal(IUserPasswordStore passwordStore, TUser user, string newPassword, CancellationToken cancellationToken) { - if (PasswordValidator != null) + var validate = await ValidatePasswordInternal(user, newPassword, cancellationToken); + if (!validate.Succeeded) { - var result = await PasswordValidator.ValidateAsync(user, newPassword, this, cancellationToken); - if (!result.Succeeded) - { - return result; - } + return validate; } await passwordStore.SetPasswordHashAsync(user, PasswordHasher.HashPassword(user, newPassword), cancellationToken); diff --git a/test/Microsoft.AspNet.Identity.InMemory.Test/HttpSignInTest.cs b/test/Microsoft.AspNet.Identity.InMemory.Test/HttpSignInTest.cs index a004c7c18c..ade7e69e73 100644 --- a/test/Microsoft.AspNet.Identity.InMemory.Test/HttpSignInTest.cs +++ b/test/Microsoft.AspNet.Identity.InMemory.Test/HttpSignInTest.cs @@ -1,18 +1,16 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Security.Claims; +using System.Threading.Tasks; using Microsoft.AspNet.Builder; using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Security; using Microsoft.AspNet.Identity.Test; -using Microsoft.AspNet.Security.Cookies; using Microsoft.Framework.DependencyInjection; -using Microsoft.Framework.DependencyInjection.Fallback; -using Moq; -using System.Security.Claims; -using System.Threading.Tasks; -using Xunit; using Microsoft.Framework.Runtime.Infrastructure; +using Moq; +using Xunit; namespace Microsoft.AspNet.Identity.InMemory.Test { diff --git a/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs b/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs index fd37a4c209..0bbf0f7f11 100644 --- a/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs @@ -187,7 +187,7 @@ namespace Microsoft.AspNet.Identity.Test throw new NotImplementedException(); } - public Task ValidateAsync(IdentityUser user, string password, UserManager manager, CancellationToken cancellationToken = default(CancellationToken)) + public Task ValidateAsync(UserManager manager, IdentityUser user, string password, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } diff --git a/test/Microsoft.AspNet.Identity.Test/PasswordValidatorTest.cs b/test/Microsoft.AspNet.Identity.Test/PasswordValidatorTest.cs index e735bf4b93..220fb2125b 100644 --- a/test/Microsoft.AspNet.Identity.Test/PasswordValidatorTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/PasswordValidatorTest.cs @@ -30,7 +30,7 @@ namespace Microsoft.AspNet.Identity.Test // Act // Assert await Assert.ThrowsAsync("password", () => validator.ValidateAsync(null, null, null)); - await Assert.ThrowsAsync("manager", () => validator.ValidateAsync(null, "foo", null)); + await Assert.ThrowsAsync("manager", () => validator.ValidateAsync(null, null, "foo")); } @@ -47,7 +47,7 @@ namespace Microsoft.AspNet.Identity.Test manager.Options.Password.RequireNonLetterOrDigit = false; manager.Options.Password.RequireLowercase = false; manager.Options.Password.RequireDigit = false; - IdentityResultAssert.IsFailure(await valid.ValidateAsync(null, input, manager), error); + IdentityResultAssert.IsFailure(await valid.ValidateAsync(manager, null, input), error); } [Theory] @@ -61,7 +61,7 @@ namespace Microsoft.AspNet.Identity.Test manager.Options.Password.RequireNonLetterOrDigit = false; manager.Options.Password.RequireLowercase = false; manager.Options.Password.RequireDigit = false; - IdentityResultAssert.IsSuccess(await valid.ValidateAsync(null, input, manager)); + IdentityResultAssert.IsSuccess(await valid.ValidateAsync(manager, null, input)); } [Theory] @@ -76,7 +76,7 @@ namespace Microsoft.AspNet.Identity.Test manager.Options.Password.RequireLowercase = false; manager.Options.Password.RequireDigit = false; manager.Options.Password.RequiredLength = 0; - IdentityResultAssert.IsFailure(await valid.ValidateAsync(null, input, manager), + IdentityResultAssert.IsFailure(await valid.ValidateAsync(manager, null, input), "Passwords must have at least one non letter and non digit character."); } @@ -93,7 +93,7 @@ namespace Microsoft.AspNet.Identity.Test manager.Options.Password.RequireLowercase = false; manager.Options.Password.RequireDigit = false; manager.Options.Password.RequiredLength = 0; - IdentityResultAssert.IsSuccess(await valid.ValidateAsync(null, input, manager)); + IdentityResultAssert.IsSuccess(await valid.ValidateAsync(manager, null, input)); } [Theory] @@ -135,11 +135,11 @@ namespace Microsoft.AspNet.Identity.Test } if (errors.Count == 0) { - IdentityResultAssert.IsSuccess(await valid.ValidateAsync(null, input, manager)); + IdentityResultAssert.IsSuccess(await valid.ValidateAsync(manager, null, input)); } else { - IdentityResultAssert.IsFailure(await valid.ValidateAsync(null, input, manager), string.Join(" ", errors)); + IdentityResultAssert.IsFailure(await valid.ValidateAsync(manager, null, input), string.Join(" ", errors)); } } } diff --git a/test/Microsoft.AspNet.Identity.Test/RoleManagerTest.cs b/test/Microsoft.AspNet.Identity.Test/RoleManagerTest.cs index 01bad66ab9..31fa379c79 100644 --- a/test/Microsoft.AspNet.Identity.Test/RoleManagerTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/RoleManagerTest.cs @@ -2,11 +2,10 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.Framework.DependencyInjection; -using Microsoft.Framework.DependencyInjection.Fallback; using Xunit; namespace Microsoft.AspNet.Identity.Test @@ -32,11 +31,8 @@ namespace Microsoft.AspNet.Identity.Test [Fact] public async Task RoleManagerPublicNullChecks() { - var provider = new ServiceCollection().BuildServiceProvider(); Assert.Throws("store", - () => new RoleManager(null, new RoleValidator())); - Assert.Throws("roleValidator", - () => new RoleManager(new NotImplementedStore(), null)); + () => new RoleManager(null, null)); var manager = CreateRoleManager(new NotImplementedStore()); await Assert.ThrowsAsync("role", async () => await manager.CreateAsync(null)); await Assert.ThrowsAsync("role", async () => await manager.UpdateAsync(null)); @@ -60,7 +56,9 @@ namespace Microsoft.AspNet.Identity.Test private static RoleManager CreateRoleManager(IRoleStore roleStore) { - return new RoleManager(roleStore, new RoleValidator()); + var v = new List>(); + v.Add(new RoleValidator()); + return new RoleManager(roleStore, v); } private class NotImplementedStore : IRoleStore diff --git a/test/Microsoft.AspNet.Identity.Test/RoleValidatorTest.cs b/test/Microsoft.AspNet.Identity.Test/RoleValidatorTest.cs index 809dd4c31d..c2f47f6870 100644 --- a/test/Microsoft.AspNet.Identity.Test/RoleValidatorTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/RoleValidatorTest.cs @@ -3,8 +3,6 @@ using System; using System.Threading.Tasks; -using Microsoft.Framework.DependencyInjection; -using Microsoft.Framework.DependencyInjection.Fallback; using Xunit; namespace Microsoft.AspNet.Identity.Test @@ -16,7 +14,7 @@ namespace Microsoft.AspNet.Identity.Test { // Setup var validator = new RoleValidator(); - var manager = new RoleManager(new NoopRoleStore(), validator); + var manager = new RoleManager(new NoopRoleStore(), null); // Act // Assert @@ -31,7 +29,7 @@ namespace Microsoft.AspNet.Identity.Test { // Setup var validator = new RoleValidator(); - var manager = new RoleManager(new NoopRoleStore(), validator); + var manager = new RoleManager(new NoopRoleStore(), null); var user = new TestRole {Name = input}; // Act diff --git a/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs b/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs index 406efad361..f9068d6e30 100644 --- a/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs @@ -22,8 +22,8 @@ namespace Microsoft.AspNet.Identity.Test public IUserStore StorePublic { get { return Store; } } public TestManager(IUserStore store, IOptions optionsAccessor, - IPasswordHasher passwordHasher, IUserValidator userValidator, - IPasswordValidator passwordValidator) + IPasswordHasher passwordHasher, IEnumerable> userValidator, + IEnumerable> passwordValidator) : base(store, optionsAccessor, passwordHasher, userValidator, passwordValidator, null, null, null) { } } @@ -36,8 +36,8 @@ namespace Microsoft.AspNet.Identity.Test services.AddIdentity(); var manager = services.BuildServiceProvider().GetRequiredService(); Assert.NotNull(manager.PasswordHasher); - Assert.NotNull(manager.PasswordValidator); - Assert.NotNull(manager.UserValidator); + Assert.Equal(1, manager.PasswordValidators.Count); + Assert.Equal(1, manager.UserValidators.Count); Assert.NotNull(manager.StorePublic); Assert.NotNull(manager.Options); } @@ -523,7 +523,8 @@ namespace Microsoft.AspNet.Identity.Test { // TODO: Can switch to Mock eventually var manager = MockHelpers.TestUserManager(new EmptyStore()); - manager.PasswordValidator = new BadPasswordValidator(); + manager.PasswordValidators.Clear(); + manager.PasswordValidators.Add(new BadPasswordValidator()); IdentityResultAssert.IsFailure(await manager.CreateAsync(new TestUser(), "password"), BadPasswordValidator.ErrorMessage); } @@ -534,8 +535,6 @@ namespace Microsoft.AspNet.Identity.Test var store = new NotImplementedStore(); var optionsAccessor = new OptionsManager(null); var passwordHasher = new PasswordHasher(new PasswordHasherOptionsAccessor()); - var userValidator = new UserValidator(); - var passwordValidator = new PasswordValidator(); Assert.Throws("store", () => new UserManager(null, null, null, null, null, null, null, null)); @@ -544,7 +543,7 @@ namespace Microsoft.AspNet.Identity.Test Assert.Throws("passwordHasher", () => new UserManager(store, optionsAccessor, null, null, null, null, null, null)); - var manager = new UserManager(store, optionsAccessor, passwordHasher, userValidator, passwordValidator, null, null, null); + var manager = new UserManager(store, optionsAccessor, passwordHasher, null, null, null, null, null); Assert.Throws("value", () => manager.PasswordHasher = null); Assert.Throws("value", () => manager.Options = null); @@ -717,7 +716,7 @@ namespace Microsoft.AspNet.Identity.Test { public const string ErrorMessage = "I'm Bad."; - public Task ValidateAsync(TUser user, string password, UserManager manager, CancellationToken cancellationToken = default(CancellationToken)) + public Task ValidateAsync(UserManager manager, TUser user, string password, CancellationToken cancellationToken = default(CancellationToken)) { return Task.FromResult(IdentityResult.Failed(ErrorMessage)); } diff --git a/test/Shared/MockHelpers.cs b/test/Shared/MockHelpers.cs index 72790950fb..87d113c96a 100644 --- a/test/Shared/MockHelpers.cs +++ b/test/Shared/MockHelpers.cs @@ -4,9 +4,6 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Microsoft.AspNet.Hosting; -using Microsoft.Framework.DependencyInjection; -using Microsoft.Framework.DependencyInjection.Fallback; using Microsoft.Framework.OptionsModel; using Moq; @@ -14,33 +11,20 @@ namespace Microsoft.AspNet.Identity.Test { public static class MockHelpers { - //public static UserManager CreateManager(IUserStore store) where TUser : class - //{ - // var services = new ServiceCollection(); - // services.Add(OptionsServices.GetDefaultServices()); - // services.Add(HostingServices.GetDefaultServices()); - // services.AddDefaultIdentity().AddUserStore(store); - // services.ConfigureIdentity(options => - // { - // options.Password.RequireDigit = false; - // options.Password.RequireLowercase = false; - // options.Password.RequireNonLetterOrDigit = false; - // options.Password.RequireUppercase = false; - // options.User.UserNameValidationRegex = null; - // }); - // return services.BuildServiceProvider().GetService>(); - //} - public static Mock> MockUserManager() where TUser : class { var store = new Mock>(); var options = new OptionsManager(null); + var userValidators = new List>(); + userValidators.Add(new UserValidator()); + var pwdValidators = new List>(); + pwdValidators.Add(new PasswordValidator()); return new Mock>( store.Object, options, new PasswordHasher(new PasswordHasherOptionsAccessor()), - new UserValidator(), - new PasswordValidator(), + userValidators, + pwdValidators, new UpperInvariantUserNameNormalizer(), new List>(), new List()); @@ -49,7 +33,9 @@ namespace Microsoft.AspNet.Identity.Test public static Mock> MockRoleManager() where TRole : class { var store = new Mock>(); - return new Mock>(store.Object, new RoleValidator()); + var roles = new List>(); + roles.Add(new RoleValidator()); + return new Mock>(store.Object, roles); } public static UserManager TestUserManager() where TUser : class @@ -62,7 +48,9 @@ namespace Microsoft.AspNet.Identity.Test var options = new OptionsManager(null); var validator = new Mock>(); var userManager = new UserManager(store, options, new PasswordHasher(new PasswordHasherOptionsAccessor()), - validator.Object, new PasswordValidator(), new UpperInvariantUserNameNormalizer(), null, null); + null, null, new UpperInvariantUserNameNormalizer(), null, null); + userManager.UserValidators.Add(validator.Object); + userManager.PasswordValidators.Add(new PasswordValidator()); validator.Setup(v => v.ValidateAsync(userManager, It.IsAny(), CancellationToken.None)) .Returns(Task.FromResult(IdentityResult.Success)).Verifiable(); return userManager; diff --git a/test/Shared/UserManagerTestBase.cs b/test/Shared/UserManagerTestBase.cs index 66a8cc1c6f..9f1fa064ce 100644 --- a/test/Shared/UserManagerTestBase.cs +++ b/test/Shared/UserManagerTestBase.cs @@ -8,9 +8,9 @@ using System.Security.Claims; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNet.Testing; -using Xunit; using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.DependencyInjection.Fallback; +using Xunit; namespace Microsoft.AspNet.Identity.Test { @@ -146,7 +146,8 @@ namespace Microsoft.AspNet.Identity.Test { var manager = CreateManager(); var user = CreateTestUser(); - manager.UserValidator = new AlwaysBadValidator(); + manager.UserValidators.Clear(); + manager.UserValidators.Add(new AlwaysBadValidator()); IdentityResultAssert.IsFailure(await manager.CreateAsync(user), AlwaysBadValidator.ErrorMessage); } @@ -156,10 +157,23 @@ namespace Microsoft.AspNet.Identity.Test var manager = CreateManager(); var user = CreateTestUser(); IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - manager.UserValidator = new AlwaysBadValidator(); + manager.UserValidators.Clear(); + manager.UserValidators.Add(new AlwaysBadValidator()); IdentityResultAssert.IsFailure(await manager.UpdateAsync(user), AlwaysBadValidator.ErrorMessage); } + [Fact] + public async Task CanChainUserValidators() + { + var manager = CreateManager(); + manager.UserValidators.Clear(); + manager.UserValidators.Add(new AlwaysBadValidator()); + manager.UserValidators.Add(new AlwaysBadValidator()); + var result = await manager.CreateAsync(CreateTestUser()); + IdentityResultAssert.IsFailure(result, AlwaysBadValidator.ErrorMessage); + Assert.Equal(2, result.Errors.Count()); + } + [Theory] [InlineData("")] [InlineData(null)] @@ -190,18 +204,34 @@ namespace Microsoft.AspNet.Identity.Test var manager = CreateManager(); var user = CreateTestUser(); IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - manager.PasswordValidator = new AlwaysBadValidator(); + manager.PasswordValidators.Clear(); + manager.PasswordValidators.Add(new AlwaysBadValidator()); IdentityResultAssert.IsFailure(await manager.AddPasswordAsync(user, "password"), AlwaysBadValidator.ErrorMessage); } + [Fact] + public async Task CanChainPasswordValidators() + { + var manager = CreateManager(); + manager.PasswordValidators.Clear(); + manager.PasswordValidators.Add(new AlwaysBadValidator()); + manager.PasswordValidators.Add(new AlwaysBadValidator()); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + var result = await manager.AddPasswordAsync(user, "pwd"); + IdentityResultAssert.IsFailure(result, AlwaysBadValidator.ErrorMessage); + Assert.Equal(2, result.Errors.Count()); + } + [Fact] public async Task PasswordValidatorCanBlockChangePassword() { var manager = CreateManager(); var user = CreateTestUser(); IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, "password")); - manager.PasswordValidator = new AlwaysBadValidator(); + manager.PasswordValidators.Clear(); + manager.PasswordValidators.Add(new AlwaysBadValidator()); IdentityResultAssert.IsFailure(await manager.ChangePasswordAsync(user, "password", "new"), AlwaysBadValidator.ErrorMessage); } @@ -539,7 +569,7 @@ namespace Microsoft.AspNet.Identity.Test Assert.NotNull(stamp); var token = await manager.GeneratePasswordResetTokenAsync(user); Assert.NotNull(token); - manager.PasswordValidator = new AlwaysBadValidator(); + manager.PasswordValidators.Add(new AlwaysBadValidator()); IdentityResultAssert.IsFailure(await manager.ResetPasswordAsync(user, token, newPassword), AlwaysBadValidator.ErrorMessage); Assert.True(await manager.CheckPasswordAsync(user, password)); @@ -814,7 +844,7 @@ namespace Microsoft.AspNet.Identity.Test { public const string ErrorMessage = "I'm Bad."; - public Task ValidateAsync(TUser user, string password, UserManager manager, CancellationToken cancellationToken = default(CancellationToken)) + public Task ValidateAsync(UserManager manager, TUser user, string password, CancellationToken cancellationToken = default(CancellationToken)) { return Task.FromResult(IdentityResult.Failed(ErrorMessage)); } @@ -834,11 +864,24 @@ namespace Microsoft.AspNet.Identity.Test public async Task BadValidatorBlocksCreateRole() { var manager = CreateRoleManager(); - manager.RoleValidator = new AlwaysBadValidator(); + manager.RoleValidators.Clear(); + manager.RoleValidators.Add(new AlwaysBadValidator()); IdentityResultAssert.IsFailure(await manager.CreateAsync(CreateRole("blocked")), AlwaysBadValidator.ErrorMessage); } + [Fact] + public async Task CanChainRoleValidators() + { + var manager = CreateRoleManager(); + manager.RoleValidators.Clear(); + manager.RoleValidators.Add(new AlwaysBadValidator()); + manager.RoleValidators.Add(new AlwaysBadValidator()); + var result = await manager.CreateAsync(CreateRole("blocked")); + IdentityResultAssert.IsFailure(result, AlwaysBadValidator.ErrorMessage); + Assert.Equal(2, result.Errors.Count()); + } + [Fact] public async Task BadValidatorBlocksRoleUpdate() { @@ -846,7 +889,8 @@ namespace Microsoft.AspNet.Identity.Test var role = CreateRole("poorguy"); IdentityResultAssert.IsSuccess(await manager.CreateAsync(role)); var error = AlwaysBadValidator.ErrorMessage; - manager.RoleValidator = new AlwaysBadValidator(); + manager.RoleValidators.Clear(); + manager.RoleValidators.Add(new AlwaysBadValidator()); IdentityResultAssert.IsFailure(await manager.UpdateAsync(role), error); }