Introduce SignInResult/IdentityError/Describer

Follows Resource pattern (IdentityErrorDescriber.StringName, or
FormatStringName(arg1, arg2)
Also cleaned up optional services, by allowing null in constructor
SignInStatus -> SignInFailure and introduced SignInResult to make
SignInManager APIs consistent with IdentityResult (but no strings needed
for SignIn)
Fixes: #86, #176, #287 and #177
This commit is contained in:
Hao Kung 2014-12-30 14:51:52 -08:00
parent 625b270924
commit c9d27e27e6
29 changed files with 708 additions and 337 deletions

View File

@ -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);
}
}

View File

@ -346,7 +346,7 @@ namespace IdentitySample
{
foreach (var error in result.Errors)
{
ModelState.AddModelError("", error);
ModelState.AddModelError("", error.Description);
}
}

View File

@ -35,6 +35,12 @@ namespace Microsoft.AspNet.Identity
return AddScoped(typeof(IRoleValidator<>).MakeGenericType(RoleType), typeof(T));
}
public IdentityBuilder AddErrorDescriber<TDescriber>() where TDescriber : IdentityErrorDescriber
{
Services.AddScoped<IdentityErrorDescriber, TDescriber>();
return this;
}
public IdentityBuilder AddPasswordValidator<T>() where T : class
{
return AddScoped(typeof(IPasswordValidator<>).MakeGenericType(UserType), typeof(T));

View File

@ -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; }
}
}

View File

@ -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
};
}
}
}

View File

@ -11,45 +11,19 @@ namespace Microsoft.AspNet.Identity
/// </summary>
public class IdentityResult
{
private static readonly IdentityResult _success = new IdentityResult(true);
private static readonly IdentityResult _success = new IdentityResult { Succeeded = true };
/// <summary>
/// Failure constructor that takes error messages
/// </summary>
/// <param name="errors"></param>
public IdentityResult(params string[] errors) : this((IEnumerable<string>)errors)
{
}
/// <summary>
/// Failure constructor that takes error messages
/// </summary>
/// <param name="errors"></param>
public IdentityResult(IEnumerable<string> 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<IdentityError> _errors = new List<IdentityError>();
/// <summary>
/// True if the operation was successful
/// </summary>
public bool Succeeded { get; private set; }
public bool Succeeded { get; protected set; }
/// <summary>
/// List of errors
/// </summary>
public IEnumerable<string> Errors { get; private set; }
public IEnumerable<IdentityError> Errors { get { return _errors; } }
/// <summary>
/// Static success result
@ -65,9 +39,14 @@ namespace Microsoft.AspNet.Identity
/// </summary>
/// <param name="errors"></param>
/// <returns></returns>
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;
}
}
}

View File

@ -59,6 +59,8 @@ namespace Microsoft.Framework.DependencyInjection
services.TryAdd(describe.Transient<IPasswordHasher<TUser>, PasswordHasher<TUser>>());
services.TryAdd(describe.Transient<IUserNameNormalizer, UpperInvariantUserNameNormalizer>());
services.TryAdd(describe.Transient<IRoleValidator<TRole>, RoleValidator<TRole>>());
// No interface for the error describer so we can add errors without rev'ing the interface
services.TryAdd(describe.Transient<IdentityErrorDescriber, IdentityErrorDescriber>());
services.TryAdd(describe.Scoped<ISecurityStampValidator, SecurityStampValidator<TUser>>());
services.TryAdd(describe.Scoped<IClaimsIdentityFactory<TUser>, ClaimsIdentityFactory<TUser, TRole>>());
services.TryAdd(describe.Scoped<UserManager<TUser>, UserManager<TUser>>());

View File

@ -37,14 +37,11 @@ namespace Microsoft.AspNet.Identity
/// Constructs a PasswordHasher using the specified options
/// </summary>
/// <param name="options"></param>
public PasswordHasher(IOptions<PasswordHasherOptions> options)
public PasswordHasher(IOptions<PasswordHasherOptions> 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.

View File

@ -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
/// </summary>
public class PasswordValidator<TUser> : IPasswordValidator<TUser> where TUser : class
{
public PasswordValidator(IdentityErrorDescriber errors = null)
{
Describer = errors ?? new IdentityErrorDescriber();
}
public IdentityErrorDescriber Describer { get; private set; }
/// <summary>
/// Ensures that the password is of the required length and meets the configured requirements
/// </summary>
@ -33,33 +39,32 @@ namespace Microsoft.AspNet.Identity
{
throw new ArgumentNullException("manager");
}
var errors = new List<string>();
var errors = new List<IdentityError>();
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()));
}
/// <summary>

View File

@ -139,35 +139,35 @@ namespace Microsoft.AspNet.Identity
}
/// <summary>
/// Name {0} is already taken.
/// Role Name '{0}' is already taken.
/// </summary>
internal static string DuplicateName
internal static string DuplicateRoleName
{
get { return GetString("DuplicateName"); }
get { return GetString("DuplicateRoleName"); }
}
/// <summary>
/// Name {0} is already taken.
/// Role Name '{0}' is already taken.
/// </summary>
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);
}
/// <summary>
/// A user with that external login already exists.
/// UserName '{0}' is already taken.
/// </summary>
internal static string ExternalLoginExists
internal static string DuplicateUserName
{
get { return GetString("ExternalLoginExists"); }
get { return GetString("DuplicateUserName"); }
}
/// <summary>
/// A user with that external login already exists.
/// UserName '{0}' is already taken.
/// </summary>
internal static string FormatExternalLoginExists()
internal static string FormatDuplicateUserName(object p0)
{
return GetString("ExternalLoginExists");
return string.Format(CultureInfo.CurrentCulture, GetString("DuplicateUserName"), p0);
}
/// <summary>
@ -218,6 +218,22 @@ namespace Microsoft.AspNet.Identity
return GetString("InvalidPasswordHasherIterationCount");
}
/// <summary>
/// Role name '{0}' is invalid.
/// </summary>
internal static string InvalidRoleName
{
get { return GetString("InvalidRoleName"); }
}
/// <summary>
/// Role name '{0}' is invalid.
/// </summary>
internal static string FormatInvalidRoleName(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("InvalidRoleName"), p0);
}
/// <summary>
/// Invalid token.
/// </summary>
@ -235,7 +251,7 @@ namespace Microsoft.AspNet.Identity
}
/// <summary>
/// User name {0} is invalid, can only contain letters or digits.
/// User name '{0}' is invalid, can only contain letters or digits.
/// </summary>
internal static string InvalidUserName
{
@ -243,7 +259,7 @@ namespace Microsoft.AspNet.Identity
}
/// <summary>
/// User name {0} is invalid, can only contain letters or digits.
/// User name '{0}' is invalid, can only contain letters or digits.
/// </summary>
internal static string FormatInvalidUserName(object p0)
{
@ -251,19 +267,19 @@ namespace Microsoft.AspNet.Identity
}
/// <summary>
/// Lockout is not enabled for this user.
/// A user with this login already exists.
/// </summary>
internal static string LockoutNotEnabled
internal static string LoginAlreadyAssociated
{
get { return GetString("LockoutNotEnabled"); }
get { return GetString("LoginAlreadyAssociated"); }
}
/// <summary>
/// Lockout is not enabled for this user.
/// A user with this login already exists.
/// </summary>
internal static string FormatLockoutNotEnabled()
internal static string FormatLoginAlreadyAssociated()
{
return GetString("LockoutNotEnabled");
return GetString("LoginAlreadyAssociated");
}
/// <summary>
@ -317,65 +333,65 @@ namespace Microsoft.AspNet.Identity
/// <summary>
/// Passwords must have at least one digit ('0'-'9').
/// </summary>
internal static string PasswordRequireDigit
internal static string PasswordRequiresDigit
{
get { return GetString("PasswordRequireDigit"); }
get { return GetString("PasswordRequiresDigit"); }
}
/// <summary>
/// Passwords must have at least one digit ('0'-'9').
/// </summary>
internal static string FormatPasswordRequireDigit()
internal static string FormatPasswordRequiresDigit()
{
return GetString("PasswordRequireDigit");
return GetString("PasswordRequiresDigit");
}
/// <summary>
/// Passwords must have at least one lowercase ('a'-'z').
/// </summary>
internal static string PasswordRequireLower
internal static string PasswordRequiresLower
{
get { return GetString("PasswordRequireLower"); }
get { return GetString("PasswordRequiresLower"); }
}
/// <summary>
/// Passwords must have at least one lowercase ('a'-'z').
/// </summary>
internal static string FormatPasswordRequireLower()
internal static string FormatPasswordRequiresLower()
{
return GetString("PasswordRequireLower");
return GetString("PasswordRequiresLower");
}
/// <summary>
/// Passwords must have at least one non letter and non digit character.
/// </summary>
internal static string PasswordRequireNonLetterOrDigit
internal static string PasswordRequiresNonLetterAndDigit
{
get { return GetString("PasswordRequireNonLetterOrDigit"); }
get { return GetString("PasswordRequiresNonLetterAndDigit"); }
}
/// <summary>
/// Passwords must have at least one non letter and non digit character.
/// </summary>
internal static string FormatPasswordRequireNonLetterOrDigit()
internal static string FormatPasswordRequiresNonLetterAndDigit()
{
return GetString("PasswordRequireNonLetterOrDigit");
return GetString("PasswordRequiresNonLetterAndDigit");
}
/// <summary>
/// Passwords must have at least one uppercase ('A'-'Z').
/// </summary>
internal static string PasswordRequireUpper
internal static string PasswordRequiresUpper
{
get { return GetString("PasswordRequireUpper"); }
get { return GetString("PasswordRequiresUpper"); }
}
/// <summary>
/// Passwords must have at least one uppercase ('A'-'Z').
/// </summary>
internal static string FormatPasswordRequireUpper()
internal static string FormatPasswordRequiresUpper()
{
return GetString("PasswordRequireUpper");
return GetString("PasswordRequiresUpper");
}
/// <summary>
@ -651,7 +667,7 @@ namespace Microsoft.AspNet.Identity
}
/// <summary>
/// User already in role.
/// User already in role '{0}'.
/// </summary>
internal static string UserAlreadyInRole
{
@ -659,11 +675,43 @@ namespace Microsoft.AspNet.Identity
}
/// <summary>
/// User already in role.
/// User already in role '{0}'.
/// </summary>
internal static string FormatUserAlreadyInRole()
internal static string FormatUserAlreadyInRole(object p0)
{
return GetString("UserAlreadyInRole");
return string.Format(CultureInfo.CurrentCulture, GetString("UserAlreadyInRole"), p0);
}
/// <summary>
/// User is locked out.
/// </summary>
internal static string UserLockedOut
{
get { return GetString("UserLockedOut"); }
}
/// <summary>
/// User is locked out.
/// </summary>
internal static string FormatUserLockedOut()
{
return GetString("UserLockedOut");
}
/// <summary>
/// Lockout is not enabled for this user.
/// </summary>
internal static string UserLockoutNotEnabled
{
get { return GetString("UserLockoutNotEnabled"); }
}
/// <summary>
/// Lockout is not enabled for this user.
/// </summary>
internal static string FormatUserLockoutNotEnabled()
{
return GetString("UserLockoutNotEnabled");
}
/// <summary>
@ -683,7 +731,7 @@ namespace Microsoft.AspNet.Identity
}
/// <summary>
/// User is not in role.
/// User is not in role '{0}'.
/// </summary>
internal static string UserNotInRole
{
@ -691,11 +739,11 @@ namespace Microsoft.AspNet.Identity
}
/// <summary>
/// User is not in role.
/// User is not in role '{0}'.
/// </summary>
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)

View File

@ -149,13 +149,13 @@
<value>Email '{0}' is already taken.</value>
<comment>error for duplicate emails</comment>
</data>
<data name="DuplicateName" xml:space="preserve">
<value>Name {0} is already taken.</value>
<data name="DuplicateRoleName" xml:space="preserve">
<value>Role Name '{0}' is already taken.</value>
<comment>error for duplicate usernames</comment>
</data>
<data name="ExternalLoginExists" xml:space="preserve">
<value>A user with that external login already exists.</value>
<comment>Error when a login already linked</comment>
<data name="DuplicateUserName" xml:space="preserve">
<value>UserName '{0}' is already taken.</value>
<comment>error for duplicate usernames</comment>
</data>
<data name="InvalidEmail" xml:space="preserve">
<value>Email '{0}' is invalid.</value>
@ -169,17 +169,21 @@
<value>The iteration count must be a positive integer.</value>
<comment>Error when the iteration count is &lt; 1.</comment>
</data>
<data name="InvalidRoleName" xml:space="preserve">
<value>Role name '{0}' is invalid.</value>
<comment>error for invalid role names</comment>
</data>
<data name="InvalidToken" xml:space="preserve">
<value>Invalid token.</value>
<comment>Error when a token is not recognized</comment>
</data>
<data name="InvalidUserName" xml:space="preserve">
<value>User name {0} is invalid, can only contain letters or digits.</value>
<value>User name '{0}' is invalid, can only contain letters or digits.</value>
<comment>usernames can only contain letters or digits</comment>
</data>
<data name="LockoutNotEnabled" xml:space="preserve">
<value>Lockout is not enabled for this user.</value>
<comment>error when lockout is not enabled</comment>
<data name="LoginAlreadyAssociated" xml:space="preserve">
<value>A user with this login already exists.</value>
<comment>Error when a login already linked</comment>
</data>
<data name="NoMessageProvider" xml:space="preserve">
<value>No IUserMessageProvider named '{0}' is registered.</value>
@ -193,19 +197,19 @@
<value>Incorrect password.</value>
<comment>Error when a password doesn't match</comment>
</data>
<data name="PasswordRequireDigit" xml:space="preserve">
<data name="PasswordRequiresDigit" xml:space="preserve">
<value>Passwords must have at least one digit ('0'-'9').</value>
<comment>Error when passwords do not have a digit</comment>
</data>
<data name="PasswordRequireLower" xml:space="preserve">
<data name="PasswordRequiresLower" xml:space="preserve">
<value>Passwords must have at least one lowercase ('a'-'z').</value>
<comment>Error when passwords do not have a lowercase letter</comment>
</data>
<data name="PasswordRequireNonLetterOrDigit" xml:space="preserve">
<data name="PasswordRequiresNonLetterAndDigit" xml:space="preserve">
<value>Passwords must have at least one non letter and non digit character.</value>
<comment>Error when password does not have enough letter or digit characters</comment>
</data>
<data name="PasswordRequireUpper" xml:space="preserve">
<data name="PasswordRequiresUpper" xml:space="preserve">
<value>Passwords must have at least one uppercase ('A'-'Z').</value>
<comment>Error when passwords do not have an uppercase letter</comment>
</data>
@ -213,10 +217,6 @@
<value>Passwords must be at least {0} characters.</value>
<comment>Error message for passwords that are too short</comment>
</data>
<data name="PropertyTooShort" xml:space="preserve">
<value>{0} cannot be null or empty.</value>
<comment>error for empty or null usernames</comment>
</data>
<data name="RoleNotFound" xml:space="preserve">
<value>Role {0} does not exist.</value>
<comment>error when a role does not exist</comment>
@ -278,15 +278,23 @@
<comment>error when AddPasswordAsync called when a user already has a password</comment>
</data>
<data name="UserAlreadyInRole" xml:space="preserve">
<value>User already in role.</value>
<value>User already in role '{0}'.</value>
<comment>Error when a user is already in a role</comment>
</data>
<data name="UserLockedOut" xml:space="preserve">
<value>User is locked out.</value>
<comment>Error when a user is locked out</comment>
</data>
<data name="UserLockoutNotEnabled" xml:space="preserve">
<value>Lockout is not enabled for this user.</value>
<comment>error when lockout is not enabled</comment>
</data>
<data name="UserNameNotFound" xml:space="preserve">
<value>User {0} does not exist.</value>
<comment>error when a user does not exist</comment>
</data>
<data name="UserNotInRole" xml:space="preserve">
<value>User is not in role.</value>
<value>User is not in role '{0}'.</value>
<comment>Error when a user is not in the role</comment>
</data>
</root>

View File

@ -24,13 +24,16 @@ namespace Microsoft.AspNet.Identity
/// </summary>
/// <param name="store">The IRoleStore commits changes via the UpdateAsync/CreateAsync methods</param>
/// <param name="roleValidator"></param>
public RoleManager(IRoleStore<TRole> store, IEnumerable<IRoleValidator<TRole>> roleValidators)
public RoleManager(IRoleStore<TRole> store,
IEnumerable<IRoleValidator<TRole>> 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
/// </summary>
public IList<IRoleValidator<TRole>> RoleValidators { get; } = new List<IRoleValidator<TRole>>();
/// <summary>
/// Used to generate public API error messages
/// </summary>
public IdentityErrorDescriber ErrorDescriber { get; set; }
/// <summary>
/// Returns an IQueryable of roles if the store is an IQueryableRoleStore
/// </summary>
@ -102,7 +110,7 @@ namespace Microsoft.AspNet.Identity
private async Task<IdentityResult> ValidateRoleInternal(TRole role, CancellationToken cancellationToken)
{
var errors = new List<string>();
var errors = new List<IdentityError>();
foreach (var v in RoleValidators)
{
var result = await v.ValidateAsync(this, role, cancellationToken);

View File

@ -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
/// <typeparam name="TRole"></typeparam>
public class RoleValidator<TRole> : IRoleValidator<TRole> where TRole : class
{
public RoleValidator(IdentityErrorDescriber errors = null)
{
Describer = errors ?? new IdentityErrorDescriber();
}
private IdentityErrorDescriber Describer { get; set; }
/// <summary>
/// Validates a role before saving
/// </summary>
@ -33,7 +39,7 @@ namespace Microsoft.AspNet.Identity
{
throw new ArgumentNullException("role");
}
var errors = new List<string>();
var errors = new List<IdentityError>();
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<TRole> manager, TRole role,
ICollection<string> errors)
private async Task ValidateRoleName(RoleManager<TRole> manager, TRole role,
ICollection<IdentityError> 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));
}
}
}

View File

@ -21,8 +21,10 @@ namespace Microsoft.AspNet.Identity
/// <typeparam name="TUser"></typeparam>
public class SignInManager<TUser> where TUser : class
{
public SignInManager(UserManager<TUser> userManager, IContextAccessor<HttpContext> contextAccessor,
IClaimsIdentityFactory<TUser> claimsFactory, IOptions<IdentityOptions> optionsAccessor)
public SignInManager(UserManager<TUser> userManager,
IContextAccessor<HttpContext> contextAccessor,
IClaimsIdentityFactory<TUser> claimsFactory,
IOptions<IdentityOptions> 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<TUser> UserManager { get; private set; }
@ -96,15 +94,15 @@ namespace Microsoft.AspNet.Identity
return UserManager.SupportsUserLockout && await UserManager.IsLockedOutAsync(user, token);
}
private async Task<SignInStatus?> PreSignInCheck(TUser user, CancellationToken token)
private async Task<SignInResult> 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<SignInStatus> PasswordSignInAsync(TUser user, string password,
public virtual async Task<SignInResult> 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<SignInStatus> PasswordSignInAsync(string userName, string password,
public virtual async Task<SignInResult> 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<SignInStatus> TwoFactorSignInAsync(string provider, string code, bool isPersistent,
public virtual async Task<SignInResult> 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;
}
/// <summary>
@ -292,18 +299,18 @@ namespace Microsoft.AspNet.Identity
return await UserManager.FindByIdAsync(info.UserId, cancellationToken);
}
public virtual async Task<SignInStatus> ExternalLoginSignInAsync(string loginProvider, string providerKey, bool isPersistent,
public async Task<SignInResult> 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<SignInStatus> SignInOrTwoFactorAsync(TUser user, bool isPersistent,
private async Task<SignInResult> 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<TwoFactorAuthenticationInfo> RetrieveTwoFactorInfoAsync(CancellationToken cancellationToken)

View File

@ -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
{
/// <summary>
/// Represents the result of an sign in operation
/// </summary>
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 };
/// <summary>
/// True if the operation was successful
/// </summary>
public bool Succeeded { get; protected set; }
/// <summary>
/// True if the user is locked out
/// </summary>
public bool IsLockedOut { get; protected set; }
/// <summary>
/// True if the user is not allowed to sign in
/// </summary>
public bool IsNotAllowed { get; protected set; }
/// <summary>
/// True if the sign in requires two factor
/// </summary>
public bool RequiresTwoFactor { get; protected set; }
/// <summary>
/// Static success result
/// </summary>
/// <returns></returns>
public static SignInResult Success
{
get { return _success; }
}
/// <summary>
/// Static failure result
/// </summary>
/// <returns></returns>
public static SignInResult Failed
{
get { return _failed; }
}
/// <summary>
/// Static locked out result
/// </summary>
/// <returns></returns>
public static SignInResult LockedOut
{
get { return _lockedOut; }
}
/// <summary>
/// Static not allowed result
/// </summary>
/// <returns></returns>
public static SignInResult NotAllowed
{
get { return _notAllowed; }
}
/// <summary>
/// Static two factor required result
/// </summary>
/// <returns></returns>
public static SignInResult TwoFactorRequired
{
get { return _twoFactorRequired; }
}
}
}

View File

@ -39,30 +39,24 @@ namespace Microsoft.AspNet.Identity
/// <param name="passwordValidator"></param>
/// <param name="claimsIdentityFactory"></param>
public UserManager(IUserStore<TUser> store,
IOptions<IdentityOptions> optionsAccessor,
IPasswordHasher<TUser> passwordHasher,
IEnumerable<IUserValidator<TUser>> userValidators,
IEnumerable<IPasswordValidator<TUser>> passwordValidators,
IUserNameNormalizer userNameNormalizer,
IEnumerable<IUserTokenProvider<TUser>> tokenProviders,
IEnumerable<IIdentityMessageProvider> msgProviders)
IOptions<IdentityOptions> optionsAccessor = null,
IPasswordHasher<TUser> passwordHasher = null,
IEnumerable<IUserValidator<TUser>> userValidators = null,
IEnumerable<IPasswordValidator<TUser>> passwordValidators = null,
IUserNameNormalizer userNameNormalizer = null,
IdentityErrorDescriber errors = null,
IEnumerable<IUserTokenProvider<TUser>> tokenProviders = null,
IEnumerable<IIdentityMessageProvider> 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<TUser>();
UserNameNormalizer = userNameNormalizer ?? new UpperInvariantUserNameNormalizer();
ErrorDescriber = errors ?? new IdentityErrorDescriber();
if (userValidators != null)
{
foreach (var v in userValidators)
@ -136,6 +130,11 @@ namespace Microsoft.AspNet.Identity
/// </summary>
public IUserNameNormalizer UserNameNormalizer { get; set; }
/// <summary>
/// Used to generate public API error messages
/// </summary>
public IdentityErrorDescriber ErrorDescriber { get; set; }
public IdentityOptions Options
{
get
@ -301,7 +300,7 @@ namespace Microsoft.AspNet.Identity
private async Task<IdentityResult> ValidateUserInternal(TUser user, CancellationToken cancellationToken)
{
var errors = new List<string>();
var errors = new List<IdentityError>();
foreach (var v in UserValidators)
{
var result = await v.ValidateAsync(this, user, cancellationToken);
@ -315,7 +314,7 @@ namespace Microsoft.AspNet.Identity
private async Task<IdentityResult> ValidatePasswordInternal(TUser user, string password, CancellationToken cancellationToken)
{
var errors = new List<string>();
var errors = new List<IdentityError>();
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());
}
/// <summary>
@ -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);

View File

@ -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
/// <typeparam name="TUser"></typeparam>
public class UserValidator<TUser> : IUserValidator<TUser> where TUser : class
{
public UserValidator(IdentityErrorDescriber errors = null)
{
Describer = errors ?? new IdentityErrorDescriber();
}
public IdentityErrorDescriber Describer { get; private set; }
/// <summary>
/// Validates a user before saving
/// </summary>
@ -37,7 +43,7 @@ namespace Microsoft.AspNet.Identity
{
throw new ArgumentNullException("user");
}
var errors = new List<string>();
var errors = new List<IdentityError>();
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<TUser> manager, TUser user, ICollection<string> errors)
private async Task ValidateUserName(UserManager<TUser> manager, TUser user, ICollection<IdentityError> 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<TUser> manager, TUser user, List<string> errors)
private async Task ValidateEmail(UserManager<TUser> manager, TUser user, List<IdentityError> 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));
}
}
}

View File

@ -50,10 +50,10 @@ namespace Microsoft.AspNet.Identity.InMemory.Test
var signInManager = app.ApplicationServices.GetRequiredService<SignInManager<ApplicationUser>>();
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();

View File

@ -226,18 +226,7 @@ namespace Microsoft.AspNet.Identity.Test
private class MyUserManager : UserManager<TestUser>
{
public MyUserManager(IUserStore<TestUser> store,
IOptions<IdentityOptions> optionsAccessor,
IPasswordHasher<TestUser> passwordHasher,
IEnumerable<IUserValidator<TestUser>> userValidators,
IEnumerable<IPasswordValidator<TestUser>> passwordValidators,
IUserNameNormalizer userNameNormalizer,
IEnumerable<IUserTokenProvider<TestUser>> tokenProviders,
IEnumerable<IIdentityMessageProvider> msgProviders) :
base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, userNameNormalizer, tokenProviders, msgProviders)
{
}
public MyUserManager(IUserStore<TestUser> store) : base(store) { }
}
private class MyRoleManager : RoleManager<TestRole>

View File

@ -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());
}
}
}

View File

@ -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));
}
}
}
}

View File

@ -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));
}
}
}

View File

@ -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<HttpContext>();
contextAccessor.Setup(a => a.Value).Returns(context.Object);
Assert.Throws<ArgumentNullException>("claimsFactory", () => new SignInManager<IdentityUser>(userManager, contextAccessor.Object, null, null));
var claimsFactory = new Mock<IClaimsIdentityFactory<IdentityUser>>().Object;
Assert.Throws<ArgumentNullException>("optionsAccessor", () => new SignInManager<IdentityUser>(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();

View File

@ -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<TestUser> StorePublic { get { return Store; } }
public TestManager(IUserStore<TestUser> store, IOptions<IdentityOptions> optionsAccessor,
IPasswordHasher<TestUser> passwordHasher, IEnumerable<IUserValidator<TestUser>> userValidator,
IEnumerable<IPasswordValidator<TestUser>> passwordValidator)
: base(store, optionsAccessor, passwordHasher, userValidator, passwordValidator, null, null, null) { }
public TestManager(IUserStore<TestUser> store) : base(store) { }
}
[Fact]
@ -36,8 +32,6 @@ namespace Microsoft.AspNet.Identity.Test
services.AddIdentity<TestUser, IdentityRole>();
var manager = services.BuildServiceProvider().GetRequiredService<TestManager>();
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<IUserStore<TestUser>>();
var user = new TestUser { UserName = "Foo" };
store.Setup(s => s.DeleteAsync(user, CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable();
var userManager = MockHelpers.TestUserManager<TestUser>(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<IUserStore<TestUser>>();
var user = new TestUser();
store.Setup(s => s.SetUserNameAsync(user, It.IsAny<string>(), CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable();
var userManager = MockHelpers.TestUserManager<TestUser>(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<TestUser>(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<TestUser>(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<Claim>(), It.IsAny<Claim>(), CancellationToken.None))
.Returns(Task.FromResult(0))
.Verifiable();
var userManager = MockHelpers.TestUserManager<TestUser>(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<IdentityOptions>(null);
var passwordHasher = new PasswordHasher<TestUser>(new PasswordHasherOptionsAccessor());
Assert.Throws<ArgumentNullException>("store",
() => new UserManager<TestUser>(null, null, null, null, null, null, null, null));
Assert.Throws<ArgumentNullException>("optionsAccessor",
() => new UserManager<TestUser>(store, null, null, null, null, null, null, null));
Assert.Throws<ArgumentNullException>("passwordHasher",
() => new UserManager<TestUser>(store, optionsAccessor, null, null, null, null, null, null));
() => new UserManager<TestUser>(null));
var manager = new UserManager<TestUser>(store, optionsAccessor, passwordHasher, null, null, null, null, null);
var manager = new UserManager<TestUser>(store);
Assert.Throws<ArgumentNullException>("value", () => manager.PasswordHasher = null);
Assert.Throws<ArgumentNullException>("value", () => manager.Options = null);
@ -714,7 +702,7 @@ namespace Microsoft.AspNet.Identity.Test
private class BadPasswordValidator<TUser> : IPasswordValidator<TUser> where TUser : class
{
public const string ErrorMessage = "I'm Bad.";
public static readonly IdentityError ErrorMessage = new IdentityError { Description = "I'm Bad." };
public Task<IdentityResult> ValidateAsync(UserManager<TUser> 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<IUserEmailStore<TestUser>>();
var describer = new TestErrorDescriber();
services.AddInstance<IdentityErrorDescriber>(describer)
.AddInstance<IUserStore<TestUser>>(store.Object)
.AddIdentity<TestUser, IdentityRole>();
var manager = services.BuildServiceProvider().GetRequiredService<UserManager<TestUser>>();
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) };
}
}
}
}

View File

@ -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]

View File

@ -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);
}
}
}

View File

@ -14,20 +14,10 @@ namespace Microsoft.AspNet.Identity.Test
public static Mock<UserManager<TUser>> MockUserManager<TUser>() where TUser : class
{
var store = new Mock<IUserStore<TUser>>();
var options = new OptionsManager<IdentityOptions>(null);
var userValidators = new List<IUserValidator<TUser>>();
userValidators.Add(new UserValidator<TUser>());
var pwdValidators = new List<IPasswordValidator<TUser>>();
pwdValidators.Add(new PasswordValidator<TUser>());
return new Mock<UserManager<TUser>>(
store.Object,
options,
new PasswordHasher<TUser>(new PasswordHasherOptionsAccessor()),
userValidators,
pwdValidators,
new UpperInvariantUserNameNormalizer(),
new List<IUserTokenProvider<TUser>>(),
new List<IIdentityMessageProvider>());
var mgr = new Mock<UserManager<TUser>>(store.Object, null, null, null, null, null, null, null, null);
mgr.Object.UserValidators.Add(new UserValidator<TUser>());
mgr.Object.PasswordValidators.Add(new PasswordValidator<TUser>());
return mgr;
}
public static Mock<RoleManager<TRole>> MockRoleManager<TRole>() where TRole : class
@ -35,7 +25,7 @@ namespace Microsoft.AspNet.Identity.Test
var store = new Mock<IRoleStore<TRole>>();
var roles = new List<IRoleValidator<TRole>>();
roles.Add(new RoleValidator<TRole>());
return new Mock<RoleManager<TRole>>(store.Object, roles);
return new Mock<RoleManager<TRole>>(store.Object, roles, null);
}
public static UserManager<TUser> TestUserManager<TUser>() where TUser : class
@ -45,10 +35,8 @@ namespace Microsoft.AspNet.Identity.Test
public static UserManager<TUser> TestUserManager<TUser>(IUserStore<TUser> store) where TUser : class
{
var options = new OptionsManager<IdentityOptions>(null);
var validator = new Mock<UserValidator<TUser>>();
var userManager = new UserManager<TUser>(store, options, new PasswordHasher<TUser>(new PasswordHasherOptionsAccessor()),
null, null, new UpperInvariantUserNameNormalizer(), null, null);
var validator = new Mock<IUserValidator<TUser>>();
var userManager = new UserManager<TUser>(store);
userManager.UserValidators.Add(validator.Object);
userManager.PasswordValidators.Add(new PasswordValidator<TUser>());
validator.Setup(v => v.ValidateAsync(userManager, It.IsAny<TUser>(), CancellationToken.None))

View File

@ -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; }
}
}

View File

@ -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<TUser>, IRoleValidator<TRole>,
IPasswordValidator<TUser>
{
public const string ErrorMessage = "I'm Bad.";
public static readonly IdentityError ErrorMessage = new IdentityError { Description = "I'm Bad." };
public Task<IdentityResult> ValidateAsync(UserManager<TUser> 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]