diff --git a/samples/IdentitySample.Mvc/Controllers/AccountController.cs b/samples/IdentitySample.Mvc/Controllers/AccountController.cs index 728328eed1..48ad2824f4 100644 --- a/samples/IdentitySample.Mvc/Controllers/AccountController.cs +++ b/samples/IdentitySample.Mvc/Controllers/AccountController.cs @@ -1,11 +1,10 @@ -using Microsoft.AspNet.Http; -using Microsoft.AspNet.Identity; -using Microsoft.AspNet.Mvc; -using Microsoft.AspNet.Mvc.Rendering; -using System.Linq; +using System.Linq; using System.Security.Claims; using System.Security.Principal; using System.Threading.Tasks; +using Microsoft.AspNet.Identity; +using Microsoft.AspNet.Mvc; +using Microsoft.AspNet.Mvc.Rendering; namespace IdentitySample.Models { @@ -43,20 +42,23 @@ namespace IdentitySample.Models ViewBag.ReturnUrl = returnUrl; if (ModelState.IsValid) { - var signInStatus = await SignInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, shouldLockout: false); - switch (signInStatus) + var result = await SignInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, shouldLockout: false); + if (result.Succeeded) { - case SignInStatus.Success: - return RedirectToLocal(returnUrl); - case SignInStatus.LockedOut: - ModelState.AddModelError("", "User is locked out, try again later."); - return View(model); - case SignInStatus.RequiresVerification: - return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe }); - case SignInStatus.Failure: - default: - ModelState.AddModelError("", "Invalid username or password."); - return View(model); + return RedirectToLocal(returnUrl); + } + if (result.RequiresTwoFactor) + { + return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe }); + } + if (result.IsLockedOut) + { + return View("Lockout"); + } + else + { + ModelState.AddModelError("", "Invalid username or password."); + return View(model); } } @@ -143,22 +145,26 @@ namespace IdentitySample.Models // Sign in the user with this external login provider if the user already has a login var result = await SignInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false); - switch (result) + if (result.Succeeded) { - case SignInStatus.Success: - return RedirectToLocal(returnUrl); - case SignInStatus.LockedOut: - return View("Lockout"); - case SignInStatus.RequiresVerification: - return RedirectToAction("SendCode", new { ReturnUrl = returnUrl }); - case SignInStatus.Failure: - default: - // If the user does not have an account, then prompt the user to create an account - ViewBag.ReturnUrl = returnUrl; - ViewBag.LoginProvider = info.LoginProvider; - // REVIEW: handle case where email not in claims? - var email = info.ExternalIdentity.FindFirstValue(ClaimTypes.Email); - return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = email }); + return RedirectToLocal(returnUrl); + } + if (result.RequiresTwoFactor) + { + return RedirectToAction("SendCode", new { ReturnUrl = returnUrl }); + } + if (result.IsLockedOut) + { + return View("Lockout"); + } + else + { + // If the user does not have an account, then prompt the user to create an account + ViewBag.ReturnUrl = returnUrl; + ViewBag.LoginProvider = info.LoginProvider; + // REVIEW: handle case where email not in claims? + var email = info.ExternalIdentity.FindFirstValue(ClaimTypes.Email); + return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = email }); } } @@ -380,15 +386,18 @@ namespace IdentitySample.Models } var result = await SignInManager.TwoFactorSignInAsync(model.Provider, model.Code, model.RememberMe, model.RememberBrowser); - switch (result) + if (result.Succeeded) { - case SignInStatus.Success: - return RedirectToLocal(model.ReturnUrl); - case SignInStatus.LockedOut: - return View("Lockout"); - default: - ModelState.AddModelError("", "Invalid code."); - return View(model); + return RedirectToLocal(model.ReturnUrl); + } + if (result.IsLockedOut) + { + return View("Lockout"); + } + else + { + ModelState.AddModelError("", "Invalid code."); + return View(model); } } @@ -398,7 +407,7 @@ namespace IdentitySample.Models { foreach (var error in result.Errors) { - ModelState.AddModelError("", error); + ModelState.AddModelError("", error.Description); } } diff --git a/samples/IdentitySample.Mvc/Controllers/ManageController.cs b/samples/IdentitySample.Mvc/Controllers/ManageController.cs index e67342fb0a..5a4359dd11 100644 --- a/samples/IdentitySample.Mvc/Controllers/ManageController.cs +++ b/samples/IdentitySample.Mvc/Controllers/ManageController.cs @@ -346,7 +346,7 @@ namespace IdentitySample { foreach (var error in result.Errors) { - ModelState.AddModelError("", error); + ModelState.AddModelError("", error.Description); } } diff --git a/src/Microsoft.AspNet.Identity/IdentityBuilder.cs b/src/Microsoft.AspNet.Identity/IdentityBuilder.cs index 71b2011cdb..4f78d0731d 100644 --- a/src/Microsoft.AspNet.Identity/IdentityBuilder.cs +++ b/src/Microsoft.AspNet.Identity/IdentityBuilder.cs @@ -35,6 +35,12 @@ namespace Microsoft.AspNet.Identity return AddScoped(typeof(IRoleValidator<>).MakeGenericType(RoleType), typeof(T)); } + public IdentityBuilder AddErrorDescriber() where TDescriber : IdentityErrorDescriber + { + Services.AddScoped(); + return this; + } + public IdentityBuilder AddPasswordValidator() where T : class { return AddScoped(typeof(IPasswordValidator<>).MakeGenericType(UserType), typeof(T)); diff --git a/src/Microsoft.AspNet.Identity/SignInStatus.cs b/src/Microsoft.AspNet.Identity/IdentityError.cs similarity index 64% rename from src/Microsoft.AspNet.Identity/SignInStatus.cs rename to src/Microsoft.AspNet.Identity/IdentityError.cs index 3247a7dd39..0db1958a8f 100644 --- a/src/Microsoft.AspNet.Identity/SignInStatus.cs +++ b/src/Microsoft.AspNet.Identity/IdentityError.cs @@ -3,12 +3,9 @@ namespace Microsoft.AspNet.Identity { - public enum SignInStatus + public class IdentityError { - Success, - LockedOut, - RequiresVerification, - NotAllowed, - Failure + public string Code { get; set; } + public string Description { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/IdentityErrorDescriber.cs b/src/Microsoft.AspNet.Identity/IdentityErrorDescriber.cs new file mode 100644 index 0000000000..05fcef7fe0 --- /dev/null +++ b/src/Microsoft.AspNet.Identity/IdentityErrorDescriber.cs @@ -0,0 +1,181 @@ +// 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. + +namespace Microsoft.AspNet.Identity +{ + public class IdentityErrorDescriber + { + public static IdentityErrorDescriber Default = new IdentityErrorDescriber(); + + public virtual IdentityError DefaultError() + { + return new IdentityError + { + Code = nameof(DefaultError), + Description = Resources.DefaultError + }; + } + + public virtual IdentityError PasswordMismatch() + { + return new IdentityError + { + Code = nameof(PasswordMismatch), + Description = Resources.PasswordMismatch + }; + } + + public virtual IdentityError InvalidToken() + { + return new IdentityError + { + Code = nameof(InvalidToken), + Description = Resources.InvalidToken + }; + } + + public virtual IdentityError LoginAlreadyAssociated() + { + return new IdentityError + { + Code = nameof(LoginAlreadyAssociated), + Description = Resources.LoginAlreadyAssociated + }; + } + + public virtual IdentityError InvalidUserName(string name) + { + return new IdentityError + { + Code = nameof(InvalidUserName), + Description = Resources.FormatInvalidUserName(name) + }; + } + + public virtual IdentityError InvalidEmail(string email) + { + return new IdentityError + { + Code = nameof(InvalidEmail), + Description = Resources.FormatInvalidEmail(email) + }; + } + + public virtual IdentityError DuplicateUserName(string name) + { + return new IdentityError + { + Code = nameof(DuplicateUserName), + Description = Resources.FormatDuplicateUserName(name) + }; + } + + public virtual IdentityError DuplicateEmail(string email) + { + return new IdentityError + { + Code = nameof(DuplicateEmail), + Description = Resources.FormatDuplicateEmail(email) + }; + } + + public virtual IdentityError InvalidRoleName(string name) + { + return new IdentityError + { + Code = nameof(InvalidRoleName), + Description = Resources.FormatInvalidRoleName(name) + }; + } + + public virtual IdentityError DuplicateRoleName(string name) + { + return new IdentityError + { + Code = nameof(DuplicateRoleName), + Description = Resources.FormatDuplicateRoleName(name) + }; + } + + public virtual IdentityError UserAlreadyHasPassword() + { + return new IdentityError + { + Code = nameof(UserAlreadyHasPassword), + Description = Resources.UserAlreadyHasPassword + }; + } + + public virtual IdentityError UserLockoutNotEnabled() + { + return new IdentityError + { + Code = nameof(UserLockoutNotEnabled), + Description = Resources.UserLockoutNotEnabled + }; + } + + public virtual IdentityError UserAlreadyInRole(string role) + { + return new IdentityError + { + Code = nameof(UserAlreadyInRole), + Description = Resources.FormatUserAlreadyInRole(role) + }; + } + + public virtual IdentityError UserNotInRole(string role) + { + return new IdentityError + { + Code = nameof(UserNotInRole), + Description = Resources.FormatUserNotInRole(role) + }; + } + + public virtual IdentityError PasswordTooShort(int length) + { + return new IdentityError + { + Code = nameof(PasswordTooShort), + Description = Resources.FormatPasswordTooShort(length) + }; + } + + public virtual IdentityError PasswordRequiresNonLetterAndDigit() + { + return new IdentityError + { + Code = nameof(PasswordRequiresNonLetterAndDigit), + Description = Resources.PasswordRequiresNonLetterAndDigit + }; + } + + public virtual IdentityError PasswordRequiresDigit() + { + return new IdentityError + { + Code = nameof(PasswordRequiresDigit), + Description = Resources.PasswordRequiresDigit + }; + } + + public virtual IdentityError PasswordRequiresLower() + { + return new IdentityError + { + Code = nameof(PasswordRequiresLower), + Description = Resources.PasswordRequiresLower + }; + } + + public virtual IdentityError PasswordRequiresUpper() + { + return new IdentityError + { + Code = nameof(PasswordRequiresUpper), + Description = Resources.PasswordRequiresUpper + }; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/IdentityResult.cs b/src/Microsoft.AspNet.Identity/IdentityResult.cs index dbe60c4756..dd3fe07de6 100644 --- a/src/Microsoft.AspNet.Identity/IdentityResult.cs +++ b/src/Microsoft.AspNet.Identity/IdentityResult.cs @@ -11,45 +11,19 @@ namespace Microsoft.AspNet.Identity /// public class IdentityResult { - private static readonly IdentityResult _success = new IdentityResult(true); + private static readonly IdentityResult _success = new IdentityResult { Succeeded = true }; - /// - /// Failure constructor that takes error messages - /// - /// - public IdentityResult(params string[] errors) : this((IEnumerable)errors) - { - } - - /// - /// Failure constructor that takes error messages - /// - /// - public IdentityResult(IEnumerable errors) - { - if (errors == null || !errors.Any()) - { - errors = new[] { Resources.DefaultError }; - } - Succeeded = false; - Errors = errors; - } - - protected IdentityResult(bool success) - { - Succeeded = success; - Errors = new string[0]; - } + private List _errors = new List(); /// /// True if the operation was successful /// - public bool Succeeded { get; private set; } + public bool Succeeded { get; protected set; } /// /// List of errors /// - public IEnumerable Errors { get; private set; } + public IEnumerable Errors { get { return _errors; } } /// /// Static success result @@ -65,9 +39,14 @@ namespace Microsoft.AspNet.Identity /// /// /// - public static IdentityResult Failed(params string[] errors) + public static IdentityResult Failed(params IdentityError[] errors) { - return new IdentityResult(errors); + var result = new IdentityResult { Succeeded = false }; + if (errors != null) + { + result._errors.AddRange(errors); + } + return result; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/IdentityServiceCollectionExtensions.cs b/src/Microsoft.AspNet.Identity/IdentityServiceCollectionExtensions.cs index 86fd009b9e..33b4d5e8f6 100644 --- a/src/Microsoft.AspNet.Identity/IdentityServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNet.Identity/IdentityServiceCollectionExtensions.cs @@ -59,6 +59,8 @@ namespace Microsoft.Framework.DependencyInjection services.TryAdd(describe.Transient, PasswordHasher>()); services.TryAdd(describe.Transient()); services.TryAdd(describe.Transient, RoleValidator>()); + // No interface for the error describer so we can add errors without rev'ing the interface + services.TryAdd(describe.Transient()); services.TryAdd(describe.Scoped>()); services.TryAdd(describe.Scoped, ClaimsIdentityFactory>()); services.TryAdd(describe.Scoped, UserManager>()); diff --git a/src/Microsoft.AspNet.Identity/PasswordHasher.cs b/src/Microsoft.AspNet.Identity/PasswordHasher.cs index 09557ae983..611ce4a04f 100644 --- a/src/Microsoft.AspNet.Identity/PasswordHasher.cs +++ b/src/Microsoft.AspNet.Identity/PasswordHasher.cs @@ -37,14 +37,11 @@ namespace Microsoft.AspNet.Identity /// Constructs a PasswordHasher using the specified options /// /// - public PasswordHasher(IOptions options) + public PasswordHasher(IOptions optionsAccessor = null) { - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } + var options = optionsAccessor?.Options ?? new PasswordHasherOptions(); - _compatibilityMode = options.Options.CompatibilityMode; + _compatibilityMode = options.CompatibilityMode; switch (_compatibilityMode) { case PasswordHasherCompatibilityMode.IdentityV2: @@ -52,7 +49,7 @@ namespace Microsoft.AspNet.Identity break; case PasswordHasherCompatibilityMode.IdentityV3: - _iterCount = options.Options.IterationCount; + _iterCount = options.IterationCount; if (_iterCount < 1) { throw new InvalidOperationException(Resources.InvalidPasswordHasherIterationCount); @@ -63,7 +60,7 @@ namespace Microsoft.AspNet.Identity throw new InvalidOperationException(Resources.InvalidPasswordHasherCompatibilityMode); } - _rng = options.Options.Rng; + _rng = options.Rng; } // Compares two byte arrays for equality. The method is specifically written so that the loop is not optimized. diff --git a/src/Microsoft.AspNet.Identity/PasswordValidator.cs b/src/Microsoft.AspNet.Identity/PasswordValidator.cs index 2a8bcf730f..1726e26694 100644 --- a/src/Microsoft.AspNet.Identity/PasswordValidator.cs +++ b/src/Microsoft.AspNet.Identity/PasswordValidator.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -15,6 +14,13 @@ namespace Microsoft.AspNet.Identity /// public class PasswordValidator : IPasswordValidator where TUser : class { + public PasswordValidator(IdentityErrorDescriber errors = null) + { + Describer = errors ?? new IdentityErrorDescriber(); + } + + public IdentityErrorDescriber Describer { get; private set; } + /// /// Ensures that the password is of the required length and meets the configured requirements /// @@ -33,33 +39,32 @@ namespace Microsoft.AspNet.Identity { throw new ArgumentNullException("manager"); } - var errors = new List(); + var errors = new List(); var options = manager.Options.Password; if (string.IsNullOrWhiteSpace(password) || password.Length < options.RequiredLength) { - errors.Add(String.Format(CultureInfo.CurrentCulture, Resources.PasswordTooShort, - options.RequiredLength)); + errors.Add(Describer.PasswordTooShort(options.RequiredLength)); } if (options.RequireNonLetterOrDigit && password.All(IsLetterOrDigit)) { - errors.Add(Resources.PasswordRequireNonLetterOrDigit); + errors.Add(Describer.PasswordRequiresNonLetterAndDigit()); } if (options.RequireDigit && !password.Any(IsDigit)) { - errors.Add(Resources.PasswordRequireDigit); + errors.Add(Describer.PasswordRequiresDigit()); } if (options.RequireLowercase && !password.Any(IsLower)) { - errors.Add(Resources.PasswordRequireLower); + errors.Add(Describer.PasswordRequiresLower()); } if (options.RequireUppercase && !password.Any(IsUpper)) { - errors.Add(Resources.PasswordRequireUpper); + errors.Add(Describer.PasswordRequiresUpper()); } return Task.FromResult(errors.Count == 0 ? IdentityResult.Success - : IdentityResult.Failed(String.Join(" ", errors))); + : IdentityResult.Failed(errors.ToArray())); } /// diff --git a/src/Microsoft.AspNet.Identity/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Identity/Properties/Resources.Designer.cs index 9c91fd4bd5..8caeda20b2 100644 --- a/src/Microsoft.AspNet.Identity/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Identity/Properties/Resources.Designer.cs @@ -139,35 +139,35 @@ namespace Microsoft.AspNet.Identity } /// - /// Name {0} is already taken. + /// Role Name '{0}' is already taken. /// - internal static string DuplicateName + internal static string DuplicateRoleName { - get { return GetString("DuplicateName"); } + get { return GetString("DuplicateRoleName"); } } /// - /// Name {0} is already taken. + /// Role Name '{0}' is already taken. /// - internal static string FormatDuplicateName(object p0) + internal static string FormatDuplicateRoleName(object p0) { - return string.Format(CultureInfo.CurrentCulture, GetString("DuplicateName"), p0); + return string.Format(CultureInfo.CurrentCulture, GetString("DuplicateRoleName"), p0); } /// - /// A user with that external login already exists. + /// UserName '{0}' is already taken. /// - internal static string ExternalLoginExists + internal static string DuplicateUserName { - get { return GetString("ExternalLoginExists"); } + get { return GetString("DuplicateUserName"); } } /// - /// A user with that external login already exists. + /// UserName '{0}' is already taken. /// - internal static string FormatExternalLoginExists() + internal static string FormatDuplicateUserName(object p0) { - return GetString("ExternalLoginExists"); + return string.Format(CultureInfo.CurrentCulture, GetString("DuplicateUserName"), p0); } /// @@ -218,6 +218,22 @@ namespace Microsoft.AspNet.Identity return GetString("InvalidPasswordHasherIterationCount"); } + /// + /// Role name '{0}' is invalid. + /// + internal static string InvalidRoleName + { + get { return GetString("InvalidRoleName"); } + } + + /// + /// Role name '{0}' is invalid. + /// + internal static string FormatInvalidRoleName(object p0) + { + return string.Format(CultureInfo.CurrentCulture, GetString("InvalidRoleName"), p0); + } + /// /// Invalid token. /// @@ -235,7 +251,7 @@ namespace Microsoft.AspNet.Identity } /// - /// User name {0} is invalid, can only contain letters or digits. + /// User name '{0}' is invalid, can only contain letters or digits. /// internal static string InvalidUserName { @@ -243,7 +259,7 @@ namespace Microsoft.AspNet.Identity } /// - /// User name {0} is invalid, can only contain letters or digits. + /// User name '{0}' is invalid, can only contain letters or digits. /// internal static string FormatInvalidUserName(object p0) { @@ -251,19 +267,19 @@ namespace Microsoft.AspNet.Identity } /// - /// Lockout is not enabled for this user. + /// A user with this login already exists. /// - internal static string LockoutNotEnabled + internal static string LoginAlreadyAssociated { - get { return GetString("LockoutNotEnabled"); } + get { return GetString("LoginAlreadyAssociated"); } } /// - /// Lockout is not enabled for this user. + /// A user with this login already exists. /// - internal static string FormatLockoutNotEnabled() + internal static string FormatLoginAlreadyAssociated() { - return GetString("LockoutNotEnabled"); + return GetString("LoginAlreadyAssociated"); } /// @@ -317,65 +333,65 @@ namespace Microsoft.AspNet.Identity /// /// Passwords must have at least one digit ('0'-'9'). /// - internal static string PasswordRequireDigit + internal static string PasswordRequiresDigit { - get { return GetString("PasswordRequireDigit"); } + get { return GetString("PasswordRequiresDigit"); } } /// /// Passwords must have at least one digit ('0'-'9'). /// - internal static string FormatPasswordRequireDigit() + internal static string FormatPasswordRequiresDigit() { - return GetString("PasswordRequireDigit"); + return GetString("PasswordRequiresDigit"); } /// /// Passwords must have at least one lowercase ('a'-'z'). /// - internal static string PasswordRequireLower + internal static string PasswordRequiresLower { - get { return GetString("PasswordRequireLower"); } + get { return GetString("PasswordRequiresLower"); } } /// /// Passwords must have at least one lowercase ('a'-'z'). /// - internal static string FormatPasswordRequireLower() + internal static string FormatPasswordRequiresLower() { - return GetString("PasswordRequireLower"); + return GetString("PasswordRequiresLower"); } /// /// Passwords must have at least one non letter and non digit character. /// - internal static string PasswordRequireNonLetterOrDigit + internal static string PasswordRequiresNonLetterAndDigit { - get { return GetString("PasswordRequireNonLetterOrDigit"); } + get { return GetString("PasswordRequiresNonLetterAndDigit"); } } /// /// Passwords must have at least one non letter and non digit character. /// - internal static string FormatPasswordRequireNonLetterOrDigit() + internal static string FormatPasswordRequiresNonLetterAndDigit() { - return GetString("PasswordRequireNonLetterOrDigit"); + return GetString("PasswordRequiresNonLetterAndDigit"); } /// /// Passwords must have at least one uppercase ('A'-'Z'). /// - internal static string PasswordRequireUpper + internal static string PasswordRequiresUpper { - get { return GetString("PasswordRequireUpper"); } + get { return GetString("PasswordRequiresUpper"); } } /// /// Passwords must have at least one uppercase ('A'-'Z'). /// - internal static string FormatPasswordRequireUpper() + internal static string FormatPasswordRequiresUpper() { - return GetString("PasswordRequireUpper"); + return GetString("PasswordRequiresUpper"); } /// @@ -651,7 +667,7 @@ namespace Microsoft.AspNet.Identity } /// - /// User already in role. + /// User already in role '{0}'. /// internal static string UserAlreadyInRole { @@ -659,11 +675,43 @@ namespace Microsoft.AspNet.Identity } /// - /// User already in role. + /// User already in role '{0}'. /// - internal static string FormatUserAlreadyInRole() + internal static string FormatUserAlreadyInRole(object p0) { - return GetString("UserAlreadyInRole"); + return string.Format(CultureInfo.CurrentCulture, GetString("UserAlreadyInRole"), p0); + } + + /// + /// User is locked out. + /// + internal static string UserLockedOut + { + get { return GetString("UserLockedOut"); } + } + + /// + /// User is locked out. + /// + internal static string FormatUserLockedOut() + { + return GetString("UserLockedOut"); + } + + /// + /// Lockout is not enabled for this user. + /// + internal static string UserLockoutNotEnabled + { + get { return GetString("UserLockoutNotEnabled"); } + } + + /// + /// Lockout is not enabled for this user. + /// + internal static string FormatUserLockoutNotEnabled() + { + return GetString("UserLockoutNotEnabled"); } /// @@ -683,7 +731,7 @@ namespace Microsoft.AspNet.Identity } /// - /// User is not in role. + /// User is not in role '{0}'. /// internal static string UserNotInRole { @@ -691,11 +739,11 @@ namespace Microsoft.AspNet.Identity } /// - /// User is not in role. + /// User is not in role '{0}'. /// - internal static string FormatUserNotInRole() + internal static string FormatUserNotInRole(object p0) { - return GetString("UserNotInRole"); + return string.Format(CultureInfo.CurrentCulture, GetString("UserNotInRole"), p0); } private static string GetString(string name, params string[] formatterNames) diff --git a/src/Microsoft.AspNet.Identity/Resources.resx b/src/Microsoft.AspNet.Identity/Resources.resx index 674ae53538..79f668637f 100644 --- a/src/Microsoft.AspNet.Identity/Resources.resx +++ b/src/Microsoft.AspNet.Identity/Resources.resx @@ -149,13 +149,13 @@ Email '{0}' is already taken. error for duplicate emails - - Name {0} is already taken. + + Role Name '{0}' is already taken. error for duplicate usernames - - A user with that external login already exists. - Error when a login already linked + + UserName '{0}' is already taken. + error for duplicate usernames Email '{0}' is invalid. @@ -169,17 +169,21 @@ The iteration count must be a positive integer. Error when the iteration count is < 1. + + Role name '{0}' is invalid. + error for invalid role names + Invalid token. Error when a token is not recognized - User name {0} is invalid, can only contain letters or digits. + User name '{0}' is invalid, can only contain letters or digits. usernames can only contain letters or digits - - Lockout is not enabled for this user. - error when lockout is not enabled + + A user with this login already exists. + Error when a login already linked No IUserMessageProvider named '{0}' is registered. @@ -193,19 +197,19 @@ Incorrect password. Error when a password doesn't match - + Passwords must have at least one digit ('0'-'9'). Error when passwords do not have a digit - + Passwords must have at least one lowercase ('a'-'z'). Error when passwords do not have a lowercase letter - + Passwords must have at least one non letter and non digit character. Error when password does not have enough letter or digit characters - + Passwords must have at least one uppercase ('A'-'Z'). Error when passwords do not have an uppercase letter @@ -213,10 +217,6 @@ Passwords must be at least {0} characters. Error message for passwords that are too short - - {0} cannot be null or empty. - error for empty or null usernames - Role {0} does not exist. error when a role does not exist @@ -278,15 +278,23 @@ error when AddPasswordAsync called when a user already has a password - User already in role. + User already in role '{0}'. Error when a user is already in a role + + User is locked out. + Error when a user is locked out + + + Lockout is not enabled for this user. + error when lockout is not enabled + User {0} does not exist. error when a user does not exist - User is not in role. + User is not in role '{0}'. Error when a user is not in the role \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/RoleManager.cs b/src/Microsoft.AspNet.Identity/RoleManager.cs index 6b30e437fc..3189f253a4 100644 --- a/src/Microsoft.AspNet.Identity/RoleManager.cs +++ b/src/Microsoft.AspNet.Identity/RoleManager.cs @@ -24,13 +24,16 @@ namespace Microsoft.AspNet.Identity /// /// The IRoleStore commits changes via the UpdateAsync/CreateAsync methods /// - public RoleManager(IRoleStore store, IEnumerable> roleValidators) + public RoleManager(IRoleStore store, + IEnumerable> roleValidators = null, + IdentityErrorDescriber errors = null) { if (store == null) { throw new ArgumentNullException("store"); } Store = store; + ErrorDescriber = errors ?? new IdentityErrorDescriber(); if (roleValidators != null) { @@ -51,6 +54,11 @@ namespace Microsoft.AspNet.Identity /// public IList> RoleValidators { get; } = new List>(); + /// + /// Used to generate public API error messages + /// + public IdentityErrorDescriber ErrorDescriber { get; set; } + /// /// Returns an IQueryable of roles if the store is an IQueryableRoleStore /// @@ -102,7 +110,7 @@ namespace Microsoft.AspNet.Identity private async Task ValidateRoleInternal(TRole role, CancellationToken cancellationToken) { - var errors = new List(); + var errors = new List(); foreach (var v in RoleValidators) { var result = await v.ValidateAsync(this, role, cancellationToken); diff --git a/src/Microsoft.AspNet.Identity/RoleValidator.cs b/src/Microsoft.AspNet.Identity/RoleValidator.cs index 244b7136f0..fc20865ae9 100644 --- a/src/Microsoft.AspNet.Identity/RoleValidator.cs +++ b/src/Microsoft.AspNet.Identity/RoleValidator.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Threading; using System.Threading.Tasks; @@ -15,6 +14,13 @@ namespace Microsoft.AspNet.Identity /// public class RoleValidator : IRoleValidator where TRole : class { + public RoleValidator(IdentityErrorDescriber errors = null) + { + Describer = errors ?? new IdentityErrorDescriber(); + } + + private IdentityErrorDescriber Describer { get; set; } + /// /// Validates a role before saving /// @@ -33,7 +39,7 @@ namespace Microsoft.AspNet.Identity { throw new ArgumentNullException("role"); } - var errors = new List(); + var errors = new List(); await ValidateRoleName(manager, role, errors); if (errors.Count > 0) { @@ -42,13 +48,13 @@ namespace Microsoft.AspNet.Identity return IdentityResult.Success; } - private static async Task ValidateRoleName(RoleManager manager, TRole role, - ICollection errors) + private async Task ValidateRoleName(RoleManager manager, TRole role, + ICollection errors) { var roleName = await manager.GetRoleNameAsync(role); if (string.IsNullOrWhiteSpace(roleName)) { - errors.Add(String.Format(CultureInfo.CurrentCulture, Resources.PropertyTooShort, "Name")); + errors.Add(Describer.InvalidRoleName(roleName)); } else { @@ -56,7 +62,7 @@ namespace Microsoft.AspNet.Identity if (owner != null && !string.Equals(await manager.GetRoleIdAsync(owner), await manager.GetRoleIdAsync(role))) { - errors.Add(String.Format(CultureInfo.CurrentCulture, Resources.DuplicateName, roleName)); + errors.Add(Describer.DuplicateRoleName(roleName)); } } } diff --git a/src/Microsoft.AspNet.Identity/SignInManager.cs b/src/Microsoft.AspNet.Identity/SignInManager.cs index 846fd81540..95ca0c1851 100644 --- a/src/Microsoft.AspNet.Identity/SignInManager.cs +++ b/src/Microsoft.AspNet.Identity/SignInManager.cs @@ -21,8 +21,10 @@ namespace Microsoft.AspNet.Identity /// public class SignInManager where TUser : class { - public SignInManager(UserManager userManager, IContextAccessor contextAccessor, - IClaimsIdentityFactory claimsFactory, IOptions optionsAccessor) + public SignInManager(UserManager userManager, + IContextAccessor contextAccessor, + IClaimsIdentityFactory claimsFactory, + IOptions optionsAccessor = null) { if (userManager == null) { @@ -36,14 +38,10 @@ namespace Microsoft.AspNet.Identity { throw new ArgumentNullException(nameof(claimsFactory)); } - if (optionsAccessor == null || optionsAccessor.Options == null) - { - throw new ArgumentNullException(nameof(optionsAccessor)); - } UserManager = userManager; Context = contextAccessor.Value; ClaimsFactory = claimsFactory; - Options = optionsAccessor.Options; + Options = optionsAccessor?.Options ?? new IdentityOptions(); } public UserManager UserManager { get; private set; } @@ -96,15 +94,15 @@ namespace Microsoft.AspNet.Identity return UserManager.SupportsUserLockout && await UserManager.IsLockedOutAsync(user, token); } - private async Task PreSignInCheck(TUser user, CancellationToken token) + private async Task PreSignInCheck(TUser user, CancellationToken token) { if (!await CanSignInAsync(user, token)) { - return SignInStatus.NotAllowed; + return SignInResult.NotAllowed; } if (await IsLockedOut(user, token)) { - return SignInStatus.LockedOut; + return SignInResult.LockedOut; } return null; } @@ -141,13 +139,21 @@ namespace Microsoft.AspNet.Identity return null; } - public virtual async Task PasswordSignInAsync(TUser user, string password, + public virtual async Task PasswordSignInAsync(TUser user, string password, bool isPersistent, bool shouldLockout, CancellationToken cancellationToken = default(CancellationToken)) { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } var error = await PreSignInCheck(user, cancellationToken); if (error != null) { - return error.Value; + return error; + } + if (await IsLockedOut(user, cancellationToken)) + { + return SignInResult.LockedOut; } if (await UserManager.CheckPasswordAsync(user, password, cancellationToken)) { @@ -160,19 +166,19 @@ namespace Microsoft.AspNet.Identity await UserManager.AccessFailedAsync(user, cancellationToken); if (await UserManager.IsLockedOutAsync(user, cancellationToken)) { - return SignInStatus.LockedOut; + return SignInResult.LockedOut; } } - return SignInStatus.Failure; + return SignInResult.Failed; } - public virtual async Task PasswordSignInAsync(string userName, string password, + public virtual async Task PasswordSignInAsync(string userName, string password, bool isPersistent, bool shouldLockout, CancellationToken cancellationToken = default(CancellationToken)) { var user = await UserManager.FindByNameAsync(userName, cancellationToken); if (user == null) { - return SignInStatus.Failure; + return SignInResult.Failed; } return await PasswordSignInAsync(user, password, isPersistent, shouldLockout, cancellationToken); } @@ -207,7 +213,6 @@ namespace Microsoft.AspNet.Identity return false; } var token = await UserManager.GenerateTwoFactorTokenAsync(user, provider, cancellationToken); - // See IdentityConfig.cs to plug in Email/SMS services to actually send the code await UserManager.NotifyTwoFactorTokenAsync(user, provider, token, cancellationToken); return true; } @@ -236,23 +241,23 @@ namespace Microsoft.AspNet.Identity return Task.FromResult(0); } - public virtual async Task TwoFactorSignInAsync(string provider, string code, bool isPersistent, + public virtual async Task TwoFactorSignInAsync(string provider, string code, bool isPersistent, bool rememberClient, CancellationToken cancellationToken = default(CancellationToken)) { var twoFactorInfo = await RetrieveTwoFactorInfoAsync(cancellationToken); if (twoFactorInfo == null || twoFactorInfo.UserId == null) { - return SignInStatus.Failure; + return SignInResult.Failed; } var user = await UserManager.FindByIdAsync(twoFactorInfo.UserId, cancellationToken); if (user == null) { - return SignInStatus.Failure; + return SignInResult.Failed; } var error = await PreSignInCheck(user, cancellationToken); if (error != null) { - return error.Value; + return error; } if (await UserManager.VerifyTwoFactorTokenAsync(user, provider, code, cancellationToken)) { @@ -268,11 +273,13 @@ namespace Microsoft.AspNet.Identity { await RememberTwoFactorClientAsync(user, cancellationToken); } - return SignInStatus.Success; + await UserManager.ResetAccessFailedCountAsync(user, cancellationToken); + await SignInAsync(user, isPersistent); + return SignInResult.Success; } // If the token is incorrect, record the failure which also may cause the user to be locked out await UserManager.AccessFailedAsync(user, cancellationToken); - return SignInStatus.Failure; + return SignInResult.Failed; } /// @@ -292,18 +299,18 @@ namespace Microsoft.AspNet.Identity return await UserManager.FindByIdAsync(info.UserId, cancellationToken); } - public virtual async Task ExternalLoginSignInAsync(string loginProvider, string providerKey, bool isPersistent, + public async Task ExternalLoginSignInAsync(string loginProvider, string providerKey, bool isPersistent, CancellationToken cancellationToken = default(CancellationToken)) { var user = await UserManager.FindByLoginAsync(loginProvider, providerKey, cancellationToken); if (user == null) { - return SignInStatus.Failure; + return SignInResult.Failed; } var error = await PreSignInCheck(user, cancellationToken); if (error != null) { - return error.Value; + return error; } return await SignInOrTwoFactorAsync(user, isPersistent, cancellationToken, loginProvider); } @@ -358,7 +365,7 @@ namespace Microsoft.AspNet.Identity return properties; } - private async Task SignInOrTwoFactorAsync(TUser user, bool isPersistent, + private async Task SignInOrTwoFactorAsync(TUser user, bool isPersistent, CancellationToken cancellationToken, string loginProvider = null) { if (UserManager.SupportsUserTwoFactor && @@ -370,7 +377,7 @@ namespace Microsoft.AspNet.Identity // Store the userId for use after two factor check var userId = await UserManager.GetUserIdAsync(user, cancellationToken); Context.Response.SignIn(StoreTwoFactorInfo(userId, loginProvider)); - return SignInStatus.RequiresVerification; + return SignInResult.TwoFactorRequired; } } // Cleanup external cookie @@ -379,7 +386,7 @@ namespace Microsoft.AspNet.Identity Context.Response.SignOut(IdentityOptions.ExternalCookieAuthenticationType); } await SignInAsync(user, isPersistent, loginProvider, cancellationToken); - return SignInStatus.Success; + return SignInResult.Success; } private async Task RetrieveTwoFactorInfoAsync(CancellationToken cancellationToken) diff --git a/src/Microsoft.AspNet.Identity/SignInResult.cs b/src/Microsoft.AspNet.Identity/SignInResult.cs new file mode 100644 index 0000000000..e125690fe7 --- /dev/null +++ b/src/Microsoft.AspNet.Identity/SignInResult.cs @@ -0,0 +1,84 @@ +// 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. + +namespace Microsoft.AspNet.Identity +{ + /// + /// Represents the result of an sign in operation + /// + public class SignInResult + { + private static readonly SignInResult _success = new SignInResult { Succeeded = true }; + private static readonly SignInResult _failed = new SignInResult(); + private static readonly SignInResult _lockedOut = new SignInResult { IsLockedOut = true }; + private static readonly SignInResult _notAllowed = new SignInResult { IsNotAllowed = true }; + private static readonly SignInResult _twoFactorRequired = new SignInResult { RequiresTwoFactor = true }; + + /// + /// True if the operation was successful + /// + public bool Succeeded { get; protected set; } + + /// + /// True if the user is locked out + /// + public bool IsLockedOut { get; protected set; } + + /// + /// True if the user is not allowed to sign in + /// + public bool IsNotAllowed { get; protected set; } + + /// + /// True if the sign in requires two factor + /// + public bool RequiresTwoFactor { get; protected set; } + + /// + /// Static success result + /// + /// + public static SignInResult Success + { + get { return _success; } + } + + /// + /// Static failure result + /// + /// + public static SignInResult Failed + { + get { return _failed; } + } + + /// + /// Static locked out result + /// + /// + public static SignInResult LockedOut + { + get { return _lockedOut; } + } + + /// + /// Static not allowed result + /// + /// + public static SignInResult NotAllowed + { + get { return _notAllowed; } + } + + /// + /// Static two factor required result + /// + /// + public static SignInResult TwoFactorRequired + { + get { return _twoFactorRequired; } + } + + + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/UserManager.cs b/src/Microsoft.AspNet.Identity/UserManager.cs index 787f91ebeb..6caed539c2 100644 --- a/src/Microsoft.AspNet.Identity/UserManager.cs +++ b/src/Microsoft.AspNet.Identity/UserManager.cs @@ -39,30 +39,24 @@ namespace Microsoft.AspNet.Identity /// /// public UserManager(IUserStore store, - IOptions optionsAccessor, - IPasswordHasher passwordHasher, - IEnumerable> userValidators, - IEnumerable> passwordValidators, - IUserNameNormalizer userNameNormalizer, - IEnumerable> tokenProviders, - IEnumerable msgProviders) + IOptions optionsAccessor = null, + IPasswordHasher passwordHasher = null, + IEnumerable> userValidators = null, + IEnumerable> passwordValidators = null, + IUserNameNormalizer userNameNormalizer = null, + IdentityErrorDescriber errors = null, + IEnumerable> tokenProviders = null, + IEnumerable msgProviders = null) { if (store == null) { throw new ArgumentNullException(nameof(store)); } - if (optionsAccessor == null || optionsAccessor.Options == null) - { - throw new ArgumentNullException(nameof(optionsAccessor)); - } - if (passwordHasher == null) - { - throw new ArgumentNullException(nameof(passwordHasher)); - } Store = store; - Options = optionsAccessor.Options; - PasswordHasher = passwordHasher; - UserNameNormalizer = userNameNormalizer; + Options = optionsAccessor?.Options ?? new IdentityOptions(); + PasswordHasher = passwordHasher ?? new PasswordHasher(); + UserNameNormalizer = userNameNormalizer ?? new UpperInvariantUserNameNormalizer(); + ErrorDescriber = errors ?? new IdentityErrorDescriber(); if (userValidators != null) { foreach (var v in userValidators) @@ -136,6 +130,11 @@ namespace Microsoft.AspNet.Identity /// public IUserNameNormalizer UserNameNormalizer { get; set; } + /// + /// Used to generate public API error messages + /// + public IdentityErrorDescriber ErrorDescriber { get; set; } + public IdentityOptions Options { get @@ -301,7 +300,7 @@ namespace Microsoft.AspNet.Identity private async Task ValidateUserInternal(TUser user, CancellationToken cancellationToken) { - var errors = new List(); + var errors = new List(); foreach (var v in UserValidators) { var result = await v.ValidateAsync(this, user, cancellationToken); @@ -315,7 +314,7 @@ namespace Microsoft.AspNet.Identity private async Task ValidatePasswordInternal(TUser user, string password, CancellationToken cancellationToken) { - var errors = new List(); + var errors = new List(); foreach (var v in PasswordValidators) { var result = await v.ValidateAsync(this, user, password, cancellationToken); @@ -598,7 +597,7 @@ namespace Microsoft.AspNet.Identity var hash = await passwordStore.GetPasswordHashAsync(user, cancellationToken); if (hash != null) { - return new IdentityResult(Resources.UserAlreadyHasPassword); + return IdentityResult.Failed(ErrorDescriber.UserAlreadyHasPassword()); } var result = await UpdatePasswordInternal(passwordStore, user, password, cancellationToken); if (!result.Succeeded) @@ -634,7 +633,7 @@ namespace Microsoft.AspNet.Identity } return await UpdateAsync(user, cancellationToken); } - return IdentityResult.Failed(Resources.PasswordMismatch); + return IdentityResult.Failed(ErrorDescriber.PasswordMismatch()); } /// @@ -766,7 +765,7 @@ namespace Microsoft.AspNet.Identity // Make sure the token is valid and the stamp matches if (!await VerifyUserTokenAsync(user, Options.PasswordResetTokenProvider, "ResetPassword", token, cancellationToken)) { - return IdentityResult.Failed(Resources.InvalidToken); + return IdentityResult.Failed(ErrorDescriber.InvalidToken()); } var passwordStore = GetPasswordStore(); var result = await UpdatePasswordInternal(passwordStore, user, newPassword, cancellationToken); @@ -877,7 +876,7 @@ namespace Microsoft.AspNet.Identity var existingUser = await FindByLoginAsync(login.LoginProvider, login.ProviderKey, cancellationToken); if (existingUser != null) { - return IdentityResult.Failed(Resources.ExternalLoginExists); + return IdentityResult.Failed(ErrorDescriber.LoginAlreadyAssociated()); } await loginStore.AddLoginAsync(user, login, cancellationToken); return await UpdateAsync(user, cancellationToken); @@ -1082,7 +1081,7 @@ namespace Microsoft.AspNet.Identity var userRoles = await userRoleStore.GetRolesAsync(user, cancellationToken); if (userRoles.Contains(role)) { - return new IdentityResult(Resources.UserAlreadyInRole); + return IdentityResult.Failed(ErrorDescriber.UserAlreadyInRole(role)); } await userRoleStore.AddToRoleAsync(user, role, cancellationToken); return await UpdateAsync(user, cancellationToken); @@ -1113,7 +1112,7 @@ namespace Microsoft.AspNet.Identity { if (userRoles.Contains(role)) { - return new IdentityResult(Resources.UserAlreadyInRole); + return IdentityResult.Failed(ErrorDescriber.UserAlreadyInRole(role)); } await userRoleStore.AddToRoleAsync(user, role, cancellationToken); } @@ -1138,7 +1137,7 @@ namespace Microsoft.AspNet.Identity } if (!await userRoleStore.IsInRoleAsync(user, role, cancellationToken)) { - return new IdentityResult(Resources.UserNotInRole); + return IdentityResult.Failed(ErrorDescriber.UserNotInRole(role)); } await userRoleStore.RemoveFromRoleAsync(user, role, cancellationToken); return await UpdateAsync(user, cancellationToken); @@ -1168,7 +1167,7 @@ namespace Microsoft.AspNet.Identity { if (!await userRoleStore.IsInRoleAsync(user, role, cancellationToken)) { - return new IdentityResult(Resources.UserNotInRole); + return IdentityResult.Failed(ErrorDescriber.UserNotInRole(role)); } await userRoleStore.RemoveFromRoleAsync(user, role, cancellationToken); } @@ -1313,7 +1312,7 @@ namespace Microsoft.AspNet.Identity } if (!await VerifyUserTokenAsync(user, Options.EmailConfirmationTokenProvider, "Confirmation", token, cancellationToken)) { - return IdentityResult.Failed(Resources.InvalidToken); + return IdentityResult.Failed(ErrorDescriber.InvalidToken()); } await store.SetEmailConfirmedAsync(user, true, cancellationToken); return await UpdateAsync(user, cancellationToken); @@ -1374,7 +1373,7 @@ namespace Microsoft.AspNet.Identity // Make sure the token is valid and the stamp matches if (!await VerifyUserTokenAsync(user, Options.ChangeEmailTokenProvider, GetChangeEmailPurpose(newEmail), token, cancellationToken)) { - return IdentityResult.Failed(Resources.InvalidToken); + return IdentityResult.Failed(ErrorDescriber.InvalidToken()); } var store = GetEmailStore(); await store.SetEmailAsync(user, newEmail, cancellationToken); @@ -1453,7 +1452,7 @@ namespace Microsoft.AspNet.Identity } if (!await VerifyChangePhoneNumberTokenAsync(user, token, phoneNumber, cancellationToken)) { - return IdentityResult.Failed(Resources.InvalidToken); + return IdentityResult.Failed(ErrorDescriber.InvalidToken()); } await store.SetPhoneNumberAsync(user, phoneNumber, cancellationToken); await store.SetPhoneNumberConfirmedAsync(user, true, cancellationToken); @@ -1890,7 +1889,7 @@ namespace Microsoft.AspNet.Identity } if (!await store.GetLockoutEnabledAsync(user, cancellationToken).ConfigureAwait((false))) { - return IdentityResult.Failed(Resources.LockoutNotEnabled); + return IdentityResult.Failed(ErrorDescriber.UserLockoutNotEnabled()); } await store.SetLockoutEndDateAsync(user, lockoutEnd, cancellationToken); return await UpdateAsync(user, cancellationToken); diff --git a/src/Microsoft.AspNet.Identity/UserValidator.cs b/src/Microsoft.AspNet.Identity/UserValidator.cs index 749f3a9f3f..06ab306683 100644 --- a/src/Microsoft.AspNet.Identity/UserValidator.cs +++ b/src/Microsoft.AspNet.Identity/UserValidator.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Globalization; #if ASPNET50 using System.Net.Mail; #endif @@ -19,6 +18,13 @@ namespace Microsoft.AspNet.Identity /// public class UserValidator : IUserValidator where TUser : class { + public UserValidator(IdentityErrorDescriber errors = null) + { + Describer = errors ?? new IdentityErrorDescriber(); + } + + public IdentityErrorDescriber Describer { get; private set; } + /// /// Validates a user before saving /// @@ -37,7 +43,7 @@ namespace Microsoft.AspNet.Identity { throw new ArgumentNullException("user"); } - var errors = new List(); + var errors = new List(); await ValidateUserName(manager, user, errors); if (manager.Options.User.RequireUniqueEmail) { @@ -46,16 +52,16 @@ namespace Microsoft.AspNet.Identity return errors.Count > 0 ? IdentityResult.Failed(errors.ToArray()) : IdentityResult.Success; } - private async Task ValidateUserName(UserManager manager, TUser user, ICollection errors) + private async Task ValidateUserName(UserManager manager, TUser user, ICollection errors) { var userName = await manager.GetUserNameAsync(user); if (string.IsNullOrWhiteSpace(userName)) { - errors.Add(String.Format(CultureInfo.CurrentCulture, Resources.PropertyTooShort, "UserName")); + errors.Add(Describer.InvalidUserName(userName)); } else if (manager.Options.User.UserNameValidationRegex != null && !Regex.IsMatch(userName, manager.Options.User.UserNameValidationRegex)) { - errors.Add(String.Format(CultureInfo.CurrentCulture, Resources.InvalidUserName, userName)); + errors.Add(Describer.InvalidUserName(userName)); } else { @@ -63,18 +69,18 @@ namespace Microsoft.AspNet.Identity if (owner != null && !string.Equals(await manager.GetUserIdAsync(owner), await manager.GetUserIdAsync(user))) { - errors.Add(String.Format(CultureInfo.CurrentCulture, Resources.DuplicateName, userName)); + errors.Add(Describer.DuplicateUserName(userName)); } } } // make sure email is not empty, valid, and unique - private static async Task ValidateEmail(UserManager manager, TUser user, List errors) + private async Task ValidateEmail(UserManager manager, TUser user, List errors) { var email = await manager.GetEmailAsync(user); if (string.IsNullOrWhiteSpace(email)) { - errors.Add(String.Format(CultureInfo.CurrentCulture, Resources.PropertyTooShort, "Email")); + errors.Add(Describer.InvalidEmail(email)); return; } #if ASPNET50 @@ -84,7 +90,7 @@ namespace Microsoft.AspNet.Identity } catch (FormatException) { - errors.Add(String.Format(CultureInfo.CurrentCulture, Resources.InvalidEmail, email)); + errors.Add(Describer.InvalidEmail(email)); return; } #endif @@ -92,7 +98,7 @@ namespace Microsoft.AspNet.Identity if (owner != null && !string.Equals(await manager.GetUserIdAsync(owner), await manager.GetUserIdAsync(user))) { - errors.Add(String.Format(CultureInfo.CurrentCulture, Resources.DuplicateEmail, email)); + errors.Add(Describer.DuplicateEmail(email)); } } } diff --git a/test/Microsoft.AspNet.Identity.InMemory.Test/HttpSignInTest.cs b/test/Microsoft.AspNet.Identity.InMemory.Test/HttpSignInTest.cs index ade7e69e73..f9f871fdc8 100644 --- a/test/Microsoft.AspNet.Identity.InMemory.Test/HttpSignInTest.cs +++ b/test/Microsoft.AspNet.Identity.InMemory.Test/HttpSignInTest.cs @@ -50,10 +50,10 @@ namespace Microsoft.AspNet.Identity.InMemory.Test var signInManager = app.ApplicationServices.GetRequiredService>(); IdentityResultAssert.IsSuccess(await userManager.CreateAsync(user, password)); - var result = await signInManager.PasswordSignInAsync(user.UserName, password, isPersistent, false); + var result = await signInManager.PasswordSignInAsync(user, password, isPersistent, false); // Assert - Assert.Equal(SignInStatus.Success, result); + Assert.True(result.Succeeded); context.VerifyAll(); response.VerifyAll(); contextAccessor.VerifyAll(); diff --git a/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs b/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs index 7843b95019..1d0b2fcc90 100644 --- a/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs @@ -226,18 +226,7 @@ namespace Microsoft.AspNet.Identity.Test private class MyUserManager : UserManager { - public MyUserManager(IUserStore store, - IOptions optionsAccessor, - IPasswordHasher passwordHasher, - IEnumerable> userValidators, - IEnumerable> passwordValidators, - IUserNameNormalizer userNameNormalizer, - IEnumerable> tokenProviders, - IEnumerable msgProviders) : - base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, userNameNormalizer, tokenProviders, msgProviders) - { - - } + public MyUserManager(IUserStore store) : base(store) { } } private class MyRoleManager : RoleManager diff --git a/test/Microsoft.AspNet.Identity.Test/IdentityResultTest.cs b/test/Microsoft.AspNet.Identity.Test/IdentityResultTest.cs index 0fd8e3bddd..7d24b52bce 100644 --- a/test/Microsoft.AspNet.Identity.Test/IdentityResultTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/IdentityResultTest.cs @@ -13,17 +13,15 @@ namespace Microsoft.AspNet.Identity.Test { var result = new IdentityResult(); Assert.False(result.Succeeded); - Assert.Equal(1, result.Errors.Count()); - Assert.Equal("An unknown failure has occured.", result.Errors.First()); + Assert.Equal(0, result.Errors.Count()); } [Fact] - public void NullErrorListUsesDefaultError() + public void NullFailedUsesEmptyErrors() { - var result = new IdentityResult(null); + var result = IdentityResult.Failed(); Assert.False(result.Succeeded); - Assert.Equal(1, result.Errors.Count()); - Assert.Equal("An unknown failure has occured.", result.Errors.First()); + Assert.Equal(0, result.Errors.Count()); } } } \ 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 220fb2125b..f8c37de273 100644 --- a/test/Microsoft.AspNet.Identity.Test/PasswordValidatorTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/PasswordValidatorTest.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Xunit; @@ -133,13 +134,18 @@ namespace Microsoft.AspNet.Identity.Test { errors.Add(upperError); } + var result = await valid.ValidateAsync(manager, null, input); if (errors.Count == 0) { - IdentityResultAssert.IsSuccess(await valid.ValidateAsync(manager, null, input)); + IdentityResultAssert.IsSuccess(result); } else { - IdentityResultAssert.IsFailure(await valid.ValidateAsync(manager, null, input), string.Join(" ", errors)); + IdentityResultAssert.IsFailure(result); + foreach (var error in errors) + { + Assert.True(result.Errors.Any(e => e.Description == error)); + } } } } diff --git a/test/Microsoft.AspNet.Identity.Test/RoleValidatorTest.cs b/test/Microsoft.AspNet.Identity.Test/RoleValidatorTest.cs index c2f47f6870..75a84c24cb 100644 --- a/test/Microsoft.AspNet.Identity.Test/RoleValidatorTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/RoleValidatorTest.cs @@ -36,7 +36,7 @@ namespace Microsoft.AspNet.Identity.Test var result = await validator.ValidateAsync(manager, user); // Assert - IdentityResultAssert.IsFailure(result, "Name cannot be null or empty."); + IdentityResultAssert.IsFailure(result, IdentityErrorDescriber.Default.InvalidRoleName(input)); } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.Test/SignInManagerTest.cs b/test/Microsoft.AspNet.Identity.Test/SignInManagerTest.cs index 629311bf23..60876733cc 100644 --- a/test/Microsoft.AspNet.Identity.Test/SignInManagerTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/SignInManagerTest.cs @@ -1,17 +1,17 @@ // 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 Microsoft.AspNet.Http; -using Microsoft.AspNet.Http.Security; -using Microsoft.Framework.DependencyInjection; -using Microsoft.Framework.OptionsModel; -using Moq; using System; using System.Collections.Generic; using System.Security.Claims; using System.Security.Principal; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.Http.Security; +using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.OptionsModel; +using Moq; using Xunit; namespace Microsoft.AspNet.Identity.Test @@ -75,8 +75,6 @@ namespace Microsoft.AspNet.Identity.Test var context = new Mock(); contextAccessor.Setup(a => a.Value).Returns(context.Object); Assert.Throws("claimsFactory", () => new SignInManager(userManager, contextAccessor.Object, null, null)); - var claimsFactory = new Mock>().Object; - Assert.Throws("optionsAccessor", () => new SignInManager(userManager, contextAccessor.Object, claimsFactory, null)); } //TODO: Mock fails in K (this works fine in net45) @@ -132,7 +130,8 @@ namespace Microsoft.AspNet.Identity.Test var result = await helper.PasswordSignInAsync(user.UserName, "bogus", false, false); // Assert - Assert.Equal(SignInStatus.LockedOut, result); + Assert.False(result.Succeeded); + Assert.True(result.IsLockedOut); manager.VerifyAll(); } @@ -167,7 +166,7 @@ namespace Microsoft.AspNet.Identity.Test var result = await helper.PasswordSignInAsync(user.UserName, "password", isPersistent, false); // Assert - Assert.Equal(SignInStatus.Success, result); + Assert.True(result.Succeeded); manager.VerifyAll(); context.VerifyAll(); response.VerifyAll(); @@ -203,7 +202,7 @@ namespace Microsoft.AspNet.Identity.Test var result = await helper.PasswordSignInAsync(user.UserName, "password", false, false); // Assert - Assert.Equal(SignInStatus.Success, result); + Assert.True(result.Succeeded); manager.VerifyAll(); context.VerifyAll(); response.VerifyAll(); @@ -251,7 +250,8 @@ namespace Microsoft.AspNet.Identity.Test var result = await helper.PasswordSignInAsync(user.UserName, "password", false, false); // Assert - Assert.Equal(SignInStatus.RequiresVerification, result); + Assert.False(result.Succeeded); + Assert.True(result.RequiresTwoFactor); manager.VerifyAll(); context.VerifyAll(); response.VerifyAll(); @@ -297,7 +297,7 @@ namespace Microsoft.AspNet.Identity.Test var result = await helper.ExternalLoginSignInAsync(loginProvider, providerKey, isPersistent); // Assert - Assert.Equal(SignInStatus.Success, result); + Assert.True(result.Succeeded); manager.VerifyAll(); context.VerifyAll(); response.VerifyAll(); @@ -382,7 +382,7 @@ namespace Microsoft.AspNet.Identity.Test var result = await helper.TwoFactorSignInAsync(provider, code, isPersistent, rememberClient); // Assert - Assert.Equal(SignInStatus.Success, result); + Assert.True(result.Succeeded); manager.VerifyAll(); context.VerifyAll(); response.VerifyAll(); @@ -466,7 +466,7 @@ namespace Microsoft.AspNet.Identity.Test var result = await helper.PasswordSignInAsync(user.UserName, "password", isPersistent, false); // Assert - Assert.Equal(SignInStatus.Success, result); + Assert.True(result.Succeeded); manager.VerifyAll(); context.VerifyAll(); response.VerifyAll(); @@ -530,7 +530,7 @@ namespace Microsoft.AspNet.Identity.Test var result = await helper.PasswordSignInAsync(user.UserName, "bogus", false, false); // Assert - Assert.Equal(SignInStatus.Failure, result); + Assert.False(result.Succeeded); manager.VerifyAll(); context.VerifyAll(); contextAccessor.VerifyAll(); @@ -556,7 +556,7 @@ namespace Microsoft.AspNet.Identity.Test var result = await helper.PasswordSignInAsync("bogus", "bogus", false, false); // Assert - Assert.Equal(SignInStatus.Failure, result); + Assert.False(result.Succeeded); manager.VerifyAll(); context.VerifyAll(); contextAccessor.VerifyAll(); @@ -592,7 +592,8 @@ namespace Microsoft.AspNet.Identity.Test var result = await helper.PasswordSignInAsync(user.UserName, "bogus", false, true); // Assert - Assert.Equal(SignInStatus.LockedOut, result); + Assert.False(result.Succeeded); + Assert.True(result.IsLockedOut); manager.VerifyAll(); } @@ -631,7 +632,9 @@ namespace Microsoft.AspNet.Identity.Test var result = await helper.PasswordSignInAsync(user, "password", false, false); // Assert - Assert.Equal(confirmed ? SignInStatus.Success : SignInStatus.NotAllowed, result); + + Assert.Equal(confirmed, result.Succeeded); + Assert.NotEqual(confirmed, result.IsNotAllowed); manager.VerifyAll(); context.VerifyAll(); response.VerifyAll(); @@ -670,7 +673,8 @@ namespace Microsoft.AspNet.Identity.Test var result = await helper.PasswordSignInAsync(user, "password", false, false); // Assert - Assert.Equal(confirmed ? SignInStatus.Success : SignInStatus.NotAllowed, result); + Assert.Equal(confirmed, result.Succeeded); + Assert.NotEqual(confirmed, result.IsNotAllowed); manager.VerifyAll(); context.VerifyAll(); response.VerifyAll(); diff --git a/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs b/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs index de496f8ff7..21cdf3d03d 100644 --- a/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs @@ -1,16 +1,15 @@ // 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 Microsoft.Framework.DependencyInjection; -using Microsoft.Framework.DependencyInjection.Fallback; -using Microsoft.Framework.OptionsModel; -using Moq; using System; using System.Collections.Generic; using System.Linq; using System.Security.Claims; using System.Threading; using System.Threading.Tasks; +using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.DependencyInjection.Fallback; +using Moq; using Xunit; namespace Microsoft.AspNet.Identity.Test @@ -21,10 +20,7 @@ namespace Microsoft.AspNet.Identity.Test { public IUserStore StorePublic { get { return Store; } } - public TestManager(IUserStore store, IOptions optionsAccessor, - IPasswordHasher passwordHasher, IEnumerable> userValidator, - IEnumerable> passwordValidator) - : base(store, optionsAccessor, passwordHasher, userValidator, passwordValidator, null, null, null) { } + public TestManager(IUserStore store) : base(store) { } } [Fact] @@ -36,8 +32,6 @@ namespace Microsoft.AspNet.Identity.Test services.AddIdentity(); var manager = services.BuildServiceProvider().GetRequiredService(); Assert.NotNull(manager.PasswordHasher); - Assert.Equal(1, manager.PasswordValidators.Count); - Assert.Equal(1, manager.UserValidators.Count); Assert.NotNull(manager.StorePublic); Assert.NotNull(manager.Options); } @@ -66,7 +60,7 @@ namespace Microsoft.AspNet.Identity.Test var store = new Mock>(); var user = new TestUser { UserName = "Foo" }; store.Setup(s => s.DeleteAsync(user, CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable(); - var userManager = MockHelpers.TestUserManager(store.Object); + var userManager = MockHelpers.TestUserManager(store.Object); // Act var result = await userManager.DeleteAsync(user); @@ -100,7 +94,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 userManager = MockHelpers.TestUserManager(store.Object); + var userManager = MockHelpers.TestUserManager(store.Object); // Act var result = await userManager.SetUserNameAsync(user, "foo"); @@ -207,7 +201,7 @@ namespace Microsoft.AspNet.Identity.Test var result = await userManager.AddToRolesAsync(user, roles); // Assert - IdentityResultAssert.IsFailure(result, "User already in role."); + IdentityResultAssert.IsFailure(result, IdentityErrorDescriber.Default.UserAlreadyInRole("B")); store.VerifyAll(); } @@ -269,7 +263,7 @@ namespace Microsoft.AspNet.Identity.Test var result = await userManager.RemoveFromRolesAsync(user, roles); // Assert - IdentityResultAssert.IsFailure(result, "User is not in role."); + IdentityResultAssert.IsFailure(result, IdentityErrorDescriber.Default.UserNotInRole("B")); store.VerifyAll(); } @@ -284,7 +278,7 @@ namespace Microsoft.AspNet.Identity.Test .Returns(Task.FromResult(0)) .Verifiable(); store.Setup(s => s.UpdateAsync(user, CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable(); - var userManager = MockHelpers.TestUserManager(store.Object); + var userManager = MockHelpers.TestUserManager(store.Object); // Act var result = await userManager.AddClaimsAsync(user, claims); @@ -305,7 +299,7 @@ namespace Microsoft.AspNet.Identity.Test .Returns(Task.FromResult(0)) .Verifiable(); store.Setup(s => s.UpdateAsync(user, CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable(); - var userManager = MockHelpers.TestUserManager(store.Object); + var userManager = MockHelpers.TestUserManager(store.Object); // Act var result = await userManager.AddClaimAsync(user, claim); @@ -326,7 +320,7 @@ namespace Microsoft.AspNet.Identity.Test store.Setup(s => s.ReplaceClaimAsync(user, It.IsAny(), It.IsAny(), CancellationToken.None)) .Returns(Task.FromResult(0)) .Verifiable(); - var userManager = MockHelpers.TestUserManager(store.Object); + var userManager = MockHelpers.TestUserManager(store.Object); // Act var result = await userManager.ReplaceClaimAsync(user, claim, newClaim); @@ -533,17 +527,11 @@ namespace Microsoft.AspNet.Identity.Test public async Task ManagerPublicNullChecks() { var store = new NotImplementedStore(); - var optionsAccessor = new OptionsManager(null); - var passwordHasher = new PasswordHasher(new PasswordHasherOptionsAccessor()); Assert.Throws("store", - () => new UserManager(null, null, null, null, null, null, null, null)); - Assert.Throws("optionsAccessor", - () => new UserManager(store, null, null, null, null, null, null, null)); - Assert.Throws("passwordHasher", - () => new UserManager(store, optionsAccessor, null, null, null, null, null, null)); + () => new UserManager(null)); - var manager = new UserManager(store, optionsAccessor, passwordHasher, null, null, null, null, null); + var manager = new UserManager(store); Assert.Throws("value", () => manager.PasswordHasher = null); Assert.Throws("value", () => manager.Options = null); @@ -714,7 +702,7 @@ namespace Microsoft.AspNet.Identity.Test private class BadPasswordValidator : IPasswordValidator where TUser : class { - public const string ErrorMessage = "I'm Bad."; + public static readonly IdentityError ErrorMessage = new IdentityError { Description = "I'm Bad." }; public Task ValidateAsync(UserManager manager, TUser user, string password, CancellationToken cancellationToken = default(CancellationToken)) { @@ -1239,5 +1227,51 @@ namespace Microsoft.AspNet.Identity.Test throw new NotImplementedException(); } } + + [Fact] + public async Task CanCustomizeUserValidatorErrors() + { + var services = new ServiceCollection(); + var store = new Mock>(); + var describer = new TestErrorDescriber(); + services.AddInstance(describer) + .AddInstance>(store.Object) + .AddIdentity(); + + var manager = services.BuildServiceProvider().GetRequiredService>(); + + manager.Options.User.RequireUniqueEmail = true; + var user = new TestUser() { UserName = "dupeEmail", Email = "dupe@email.com" }; + var user2 = new TestUser() { UserName = "dupeEmail2", Email = "dupe@email.com" }; + store.Setup(s => s.FindByEmailAsync(user.Email, CancellationToken.None)) + .Returns(Task.FromResult(user2)) + .Verifiable(); + store.Setup(s => s.GetUserIdAsync(user2, CancellationToken.None)) + .Returns(Task.FromResult(user2.Id)) + .Verifiable(); + store.Setup(s => s.GetUserNameAsync(user, CancellationToken.None)) + .Returns(Task.FromResult(user.UserName)) + .Verifiable(); + store.Setup(s => s.GetEmailAsync(user, CancellationToken.None)) + .Returns(Task.FromResult(user.Email)) + .Verifiable(); + + Assert.Same(describer, manager.ErrorDescriber); + IdentityResultAssert.IsFailure(await manager.CreateAsync(user), describer.DuplicateEmail(user.Email)); + + store.VerifyAll(); + } + + public class TestErrorDescriber : IdentityErrorDescriber + { + public static string Code = "Error"; + public static string FormatError = "FormatError {0}"; + + public override IdentityError DuplicateEmail(string email) + { + return new IdentityError { Code = Code, Description = string.Format(FormatError, email) }; + } + } + } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.Test/UserValidatorTest.cs b/test/Microsoft.AspNet.Identity.Test/UserValidatorTest.cs index 793dd36598..093ecb5131 100644 --- a/test/Microsoft.AspNet.Identity.Test/UserValidatorTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/UserValidatorTest.cs @@ -36,7 +36,7 @@ namespace Microsoft.AspNet.Identity.Test var result = await validator.ValidateAsync(manager, user); // Assert - IdentityResultAssert.IsFailure(result, "UserName cannot be null or empty."); + IdentityResultAssert.IsFailure(result, IdentityErrorDescriber.Default.InvalidUserName(input)); } [Theory] diff --git a/test/Shared/IdentityResultAssert.cs b/test/Shared/IdentityResultAssert.cs index eb92499bf1..617e5c0c6b 100644 --- a/test/Shared/IdentityResultAssert.cs +++ b/test/Shared/IdentityResultAssert.cs @@ -24,7 +24,16 @@ namespace Microsoft.AspNet.Identity.Test { Assert.NotNull(result); Assert.False(result.Succeeded); - Assert.Equal(error, result.Errors.First()); + Assert.Equal(error, result.Errors.First().Description); } + + public static void IsFailure(IdentityResult result, IdentityError error) + { + Assert.NotNull(result); + Assert.False(result.Succeeded); + Assert.Equal(error.Description, result.Errors.First().Description); + Assert.Equal(error.Code, result.Errors.First().Code); + } + } } \ No newline at end of file diff --git a/test/Shared/MockHelpers.cs b/test/Shared/MockHelpers.cs index 87d113c96a..4a73bba084 100644 --- a/test/Shared/MockHelpers.cs +++ b/test/Shared/MockHelpers.cs @@ -14,20 +14,10 @@ namespace Microsoft.AspNet.Identity.Test 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()), - userValidators, - pwdValidators, - new UpperInvariantUserNameNormalizer(), - new List>(), - new List()); + var mgr = new Mock>(store.Object, null, null, null, null, null, null, null, null); + mgr.Object.UserValidators.Add(new UserValidator()); + mgr.Object.PasswordValidators.Add(new PasswordValidator()); + return mgr; } public static Mock> MockRoleManager() where TRole : class @@ -35,7 +25,7 @@ namespace Microsoft.AspNet.Identity.Test var store = new Mock>(); var roles = new List>(); roles.Add(new RoleValidator()); - return new Mock>(store.Object, roles); + return new Mock>(store.Object, roles, null); } public static UserManager TestUserManager() where TUser : class @@ -45,10 +35,8 @@ namespace Microsoft.AspNet.Identity.Test public static UserManager TestUserManager(IUserStore store) where TUser : class { - var options = new OptionsManager(null); - var validator = new Mock>(); - var userManager = new UserManager(store, options, new PasswordHasher(new PasswordHasherOptionsAccessor()), - null, null, new UpperInvariantUserNameNormalizer(), null, null); + var validator = new Mock>(); + var userManager = new UserManager(store); userManager.UserValidators.Add(validator.Object); userManager.PasswordValidators.Add(new PasswordValidator()); validator.Setup(v => v.ValidateAsync(userManager, It.IsAny(), CancellationToken.None)) diff --git a/test/Shared/TestUser.cs b/test/Shared/TestUser.cs index 19b7e391f4..40436a8361 100644 --- a/test/Shared/TestUser.cs +++ b/test/Shared/TestUser.cs @@ -14,5 +14,6 @@ namespace Microsoft.AspNet.Identity.Test public string Id { get; private set; } public string UserName { get; set; } + public string Email { get; set; } } } \ No newline at end of file diff --git a/test/Shared/UserManagerTestBase.cs b/test/Shared/UserManagerTestBase.cs index 8997be452f..3b2dd143f6 100644 --- a/test/Shared/UserManagerTestBase.cs +++ b/test/Shared/UserManagerTestBase.cs @@ -182,7 +182,7 @@ namespace Microsoft.AspNet.Identity.Test var manager = CreateManager(); var user = CreateTestUser(); manager.Options.User.RequireUniqueEmail = true; - IdentityResultAssert.IsFailure(await manager.CreateAsync(user), "Email cannot be null or empty."); + IdentityResultAssert.IsFailure(await manager.CreateAsync(user), IdentityErrorDescriber.Default.InvalidEmail(email)); } #if ASPNET50 @@ -194,7 +194,7 @@ namespace Microsoft.AspNet.Identity.Test 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."); + IdentityResultAssert.IsFailure(await manager.CreateAsync(user), IdentityErrorDescriber.Default.InvalidEmail(email)); } #endif @@ -414,7 +414,7 @@ namespace Microsoft.AspNet.Identity.Test var user = CreateTestUser(); var user2 = new TUser() { UserName = user.UserName }; IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - IdentityResultAssert.IsFailure(await manager.CreateAsync(user2), "Name "+user.UserName+" is already taken."); + IdentityResultAssert.IsFailure(await manager.CreateAsync(user2), IdentityErrorDescriber.Default.DuplicateUserName(user.UserName)); } [Fact] @@ -440,7 +440,7 @@ namespace Microsoft.AspNet.Identity.Test user.Email = email; user2.Email = email; IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - IdentityResultAssert.IsFailure(await manager.CreateAsync(user2), "Email '"+email+"' is already taken."); + IdentityResultAssert.IsFailure(await manager.CreateAsync(user2), IdentityErrorDescriber.Default.DuplicateEmail(user.Email)); } [Fact] @@ -465,7 +465,7 @@ namespace Microsoft.AspNet.Identity.Test 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."); + IdentityResultAssert.IsFailure(result, IdentityErrorDescriber.Default.LoginAlreadyAssociated()); } // Email tests @@ -842,7 +842,7 @@ namespace Microsoft.AspNet.Identity.Test private class AlwaysBadValidator : IUserValidator, IRoleValidator, IPasswordValidator { - public const string ErrorMessage = "I'm Bad."; + public static readonly IdentityError ErrorMessage = new IdentityError { Description = "I'm Bad." }; public Task ValidateAsync(UserManager manager, TUser user, string password, CancellationToken cancellationToken = default(CancellationToken)) { @@ -1142,7 +1142,7 @@ namespace Microsoft.AspNet.Identity.Test 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."); + IdentityResultAssert.IsFailure(result, IdentityErrorDescriber.Default.UserNotInRole(role.Name)); } [Fact] @@ -1157,7 +1157,7 @@ namespace Microsoft.AspNet.Identity.Test 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."); + IdentityResultAssert.IsFailure(await userMgr.AddToRoleAsync(user, role.Name), IdentityErrorDescriber.Default.UserAlreadyInRole(role.Name)); } [Fact]