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 System.Linq;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.Rendering;
using System.Linq;
using System.Security.Claims; using System.Security.Claims;
using System.Security.Principal; using System.Security.Principal;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.Rendering;
namespace IdentitySample.Models namespace IdentitySample.Models
{ {
@ -43,20 +42,23 @@ namespace IdentitySample.Models
ViewBag.ReturnUrl = returnUrl; ViewBag.ReturnUrl = returnUrl;
if (ModelState.IsValid) if (ModelState.IsValid)
{ {
var signInStatus = await SignInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, shouldLockout: false); var result = await SignInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, shouldLockout: false);
switch (signInStatus) if (result.Succeeded)
{ {
case SignInStatus.Success: return RedirectToLocal(returnUrl);
return RedirectToLocal(returnUrl); }
case SignInStatus.LockedOut: if (result.RequiresTwoFactor)
ModelState.AddModelError("", "User is locked out, try again later."); {
return View(model); return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
case SignInStatus.RequiresVerification: }
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe }); if (result.IsLockedOut)
case SignInStatus.Failure: {
default: return View("Lockout");
ModelState.AddModelError("", "Invalid username or password."); }
return View(model); 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 // 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, var result = await SignInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey,
isPersistent: false); isPersistent: false);
switch (result) if (result.Succeeded)
{ {
case SignInStatus.Success: return RedirectToLocal(returnUrl);
return RedirectToLocal(returnUrl); }
case SignInStatus.LockedOut: if (result.RequiresTwoFactor)
return View("Lockout"); {
case SignInStatus.RequiresVerification: return RedirectToAction("SendCode", new { ReturnUrl = returnUrl });
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl }); }
case SignInStatus.Failure: if (result.IsLockedOut)
default: {
// If the user does not have an account, then prompt the user to create an account return View("Lockout");
ViewBag.ReturnUrl = returnUrl; }
ViewBag.LoginProvider = info.LoginProvider; else
// REVIEW: handle case where email not in claims? {
var email = info.ExternalIdentity.FindFirstValue(ClaimTypes.Email); // If the user does not have an account, then prompt the user to create an account
return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = email }); 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); 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);
return RedirectToLocal(model.ReturnUrl); }
case SignInStatus.LockedOut: if (result.IsLockedOut)
return View("Lockout"); {
default: return View("Lockout");
ModelState.AddModelError("", "Invalid code."); }
return View(model); else
{
ModelState.AddModelError("", "Invalid code.");
return View(model);
} }
} }
@ -398,7 +407,7 @@ namespace IdentitySample.Models
{ {
foreach (var error in result.Errors) 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) 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)); 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 public IdentityBuilder AddPasswordValidator<T>() where T : class
{ {
return AddScoped(typeof(IPasswordValidator<>).MakeGenericType(UserType), typeof(T)); return AddScoped(typeof(IPasswordValidator<>).MakeGenericType(UserType), typeof(T));

View File

@ -3,12 +3,9 @@
namespace Microsoft.AspNet.Identity namespace Microsoft.AspNet.Identity
{ {
public enum SignInStatus public class IdentityError
{ {
Success, public string Code { get; set; }
LockedOut, public string Description { get; set; }
RequiresVerification,
NotAllowed,
Failure
} }
} }

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> /// </summary>
public class IdentityResult public class IdentityResult
{ {
private static readonly IdentityResult _success = new IdentityResult(true); private static readonly IdentityResult _success = new IdentityResult { Succeeded = true };
/// <summary> private List<IdentityError> _errors = new List<IdentityError>();
/// 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];
}
/// <summary> /// <summary>
/// True if the operation was successful /// True if the operation was successful
/// </summary> /// </summary>
public bool Succeeded { get; private set; } public bool Succeeded { get; protected set; }
/// <summary> /// <summary>
/// List of errors /// List of errors
/// </summary> /// </summary>
public IEnumerable<string> Errors { get; private set; } public IEnumerable<IdentityError> Errors { get { return _errors; } }
/// <summary> /// <summary>
/// Static success result /// Static success result
@ -65,9 +39,14 @@ namespace Microsoft.AspNet.Identity
/// </summary> /// </summary>
/// <param name="errors"></param> /// <param name="errors"></param>
/// <returns></returns> /// <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<IPasswordHasher<TUser>, PasswordHasher<TUser>>());
services.TryAdd(describe.Transient<IUserNameNormalizer, UpperInvariantUserNameNormalizer>()); services.TryAdd(describe.Transient<IUserNameNormalizer, UpperInvariantUserNameNormalizer>());
services.TryAdd(describe.Transient<IRoleValidator<TRole>, RoleValidator<TRole>>()); 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<ISecurityStampValidator, SecurityStampValidator<TUser>>());
services.TryAdd(describe.Scoped<IClaimsIdentityFactory<TUser>, ClaimsIdentityFactory<TUser, TRole>>()); services.TryAdd(describe.Scoped<IClaimsIdentityFactory<TUser>, ClaimsIdentityFactory<TUser, TRole>>());
services.TryAdd(describe.Scoped<UserManager<TUser>, UserManager<TUser>>()); 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 /// Constructs a PasswordHasher using the specified options
/// </summary> /// </summary>
/// <param name="options"></param> /// <param name="options"></param>
public PasswordHasher(IOptions<PasswordHasherOptions> options) public PasswordHasher(IOptions<PasswordHasherOptions> optionsAccessor = null)
{ {
if (options == null) var options = optionsAccessor?.Options ?? new PasswordHasherOptions();
{
throw new ArgumentNullException(nameof(options));
}
_compatibilityMode = options.Options.CompatibilityMode; _compatibilityMode = options.CompatibilityMode;
switch (_compatibilityMode) switch (_compatibilityMode)
{ {
case PasswordHasherCompatibilityMode.IdentityV2: case PasswordHasherCompatibilityMode.IdentityV2:
@ -52,7 +49,7 @@ namespace Microsoft.AspNet.Identity
break; break;
case PasswordHasherCompatibilityMode.IdentityV3: case PasswordHasherCompatibilityMode.IdentityV3:
_iterCount = options.Options.IterationCount; _iterCount = options.IterationCount;
if (_iterCount < 1) if (_iterCount < 1)
{ {
throw new InvalidOperationException(Resources.InvalidPasswordHasherIterationCount); throw new InvalidOperationException(Resources.InvalidPasswordHasherIterationCount);
@ -63,7 +60,7 @@ namespace Microsoft.AspNet.Identity
throw new InvalidOperationException(Resources.InvalidPasswordHasherCompatibilityMode); 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. // 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -15,6 +14,13 @@ namespace Microsoft.AspNet.Identity
/// </summary> /// </summary>
public class PasswordValidator<TUser> : IPasswordValidator<TUser> where TUser : class 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> /// <summary>
/// Ensures that the password is of the required length and meets the configured requirements /// Ensures that the password is of the required length and meets the configured requirements
/// </summary> /// </summary>
@ -33,33 +39,32 @@ namespace Microsoft.AspNet.Identity
{ {
throw new ArgumentNullException("manager"); throw new ArgumentNullException("manager");
} }
var errors = new List<string>(); var errors = new List<IdentityError>();
var options = manager.Options.Password; var options = manager.Options.Password;
if (string.IsNullOrWhiteSpace(password) || password.Length < options.RequiredLength) if (string.IsNullOrWhiteSpace(password) || password.Length < options.RequiredLength)
{ {
errors.Add(String.Format(CultureInfo.CurrentCulture, Resources.PasswordTooShort, errors.Add(Describer.PasswordTooShort(options.RequiredLength));
options.RequiredLength));
} }
if (options.RequireNonLetterOrDigit && password.All(IsLetterOrDigit)) if (options.RequireNonLetterOrDigit && password.All(IsLetterOrDigit))
{ {
errors.Add(Resources.PasswordRequireNonLetterOrDigit); errors.Add(Describer.PasswordRequiresNonLetterAndDigit());
} }
if (options.RequireDigit && !password.Any(IsDigit)) if (options.RequireDigit && !password.Any(IsDigit))
{ {
errors.Add(Resources.PasswordRequireDigit); errors.Add(Describer.PasswordRequiresDigit());
} }
if (options.RequireLowercase && !password.Any(IsLower)) if (options.RequireLowercase && !password.Any(IsLower))
{ {
errors.Add(Resources.PasswordRequireLower); errors.Add(Describer.PasswordRequiresLower());
} }
if (options.RequireUppercase && !password.Any(IsUpper)) if (options.RequireUppercase && !password.Any(IsUpper))
{ {
errors.Add(Resources.PasswordRequireUpper); errors.Add(Describer.PasswordRequiresUpper());
} }
return return
Task.FromResult(errors.Count == 0 Task.FromResult(errors.Count == 0
? IdentityResult.Success ? IdentityResult.Success
: IdentityResult.Failed(String.Join(" ", errors))); : IdentityResult.Failed(errors.ToArray()));
} }
/// <summary> /// <summary>

View File

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

View File

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

View File

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

View File

@ -3,7 +3,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -15,6 +14,13 @@ namespace Microsoft.AspNet.Identity
/// <typeparam name="TRole"></typeparam> /// <typeparam name="TRole"></typeparam>
public class RoleValidator<TRole> : IRoleValidator<TRole> where TRole : class public class RoleValidator<TRole> : IRoleValidator<TRole> where TRole : class
{ {
public RoleValidator(IdentityErrorDescriber errors = null)
{
Describer = errors ?? new IdentityErrorDescriber();
}
private IdentityErrorDescriber Describer { get; set; }
/// <summary> /// <summary>
/// Validates a role before saving /// Validates a role before saving
/// </summary> /// </summary>
@ -33,7 +39,7 @@ namespace Microsoft.AspNet.Identity
{ {
throw new ArgumentNullException("role"); throw new ArgumentNullException("role");
} }
var errors = new List<string>(); var errors = new List<IdentityError>();
await ValidateRoleName(manager, role, errors); await ValidateRoleName(manager, role, errors);
if (errors.Count > 0) if (errors.Count > 0)
{ {
@ -42,13 +48,13 @@ namespace Microsoft.AspNet.Identity
return IdentityResult.Success; return IdentityResult.Success;
} }
private static async Task ValidateRoleName(RoleManager<TRole> manager, TRole role, private async Task ValidateRoleName(RoleManager<TRole> manager, TRole role,
ICollection<string> errors) ICollection<IdentityError> errors)
{ {
var roleName = await manager.GetRoleNameAsync(role); var roleName = await manager.GetRoleNameAsync(role);
if (string.IsNullOrWhiteSpace(roleName)) if (string.IsNullOrWhiteSpace(roleName))
{ {
errors.Add(String.Format(CultureInfo.CurrentCulture, Resources.PropertyTooShort, "Name")); errors.Add(Describer.InvalidRoleName(roleName));
} }
else else
{ {
@ -56,7 +62,7 @@ namespace Microsoft.AspNet.Identity
if (owner != null && if (owner != null &&
!string.Equals(await manager.GetRoleIdAsync(owner), await manager.GetRoleIdAsync(role))) !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> /// <typeparam name="TUser"></typeparam>
public class SignInManager<TUser> where TUser : class public class SignInManager<TUser> where TUser : class
{ {
public SignInManager(UserManager<TUser> userManager, IContextAccessor<HttpContext> contextAccessor, public SignInManager(UserManager<TUser> userManager,
IClaimsIdentityFactory<TUser> claimsFactory, IOptions<IdentityOptions> optionsAccessor) IContextAccessor<HttpContext> contextAccessor,
IClaimsIdentityFactory<TUser> claimsFactory,
IOptions<IdentityOptions> optionsAccessor = null)
{ {
if (userManager == null) if (userManager == null)
{ {
@ -36,14 +38,10 @@ namespace Microsoft.AspNet.Identity
{ {
throw new ArgumentNullException(nameof(claimsFactory)); throw new ArgumentNullException(nameof(claimsFactory));
} }
if (optionsAccessor == null || optionsAccessor.Options == null)
{
throw new ArgumentNullException(nameof(optionsAccessor));
}
UserManager = userManager; UserManager = userManager;
Context = contextAccessor.Value; Context = contextAccessor.Value;
ClaimsFactory = claimsFactory; ClaimsFactory = claimsFactory;
Options = optionsAccessor.Options; Options = optionsAccessor?.Options ?? new IdentityOptions();
} }
public UserManager<TUser> UserManager { get; private set; } public UserManager<TUser> UserManager { get; private set; }
@ -96,15 +94,15 @@ namespace Microsoft.AspNet.Identity
return UserManager.SupportsUserLockout && await UserManager.IsLockedOutAsync(user, token); 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)) if (!await CanSignInAsync(user, token))
{ {
return SignInStatus.NotAllowed; return SignInResult.NotAllowed;
} }
if (await IsLockedOut(user, token)) if (await IsLockedOut(user, token))
{ {
return SignInStatus.LockedOut; return SignInResult.LockedOut;
} }
return null; return null;
} }
@ -141,13 +139,21 @@ namespace Microsoft.AspNet.Identity
return null; 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)) bool isPersistent, bool shouldLockout, CancellationToken cancellationToken = default(CancellationToken))
{ {
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var error = await PreSignInCheck(user, cancellationToken); var error = await PreSignInCheck(user, cancellationToken);
if (error != null) if (error != null)
{ {
return error.Value; return error;
}
if (await IsLockedOut(user, cancellationToken))
{
return SignInResult.LockedOut;
} }
if (await UserManager.CheckPasswordAsync(user, password, cancellationToken)) if (await UserManager.CheckPasswordAsync(user, password, cancellationToken))
{ {
@ -160,19 +166,19 @@ namespace Microsoft.AspNet.Identity
await UserManager.AccessFailedAsync(user, cancellationToken); await UserManager.AccessFailedAsync(user, cancellationToken);
if (await UserManager.IsLockedOutAsync(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)) bool isPersistent, bool shouldLockout, CancellationToken cancellationToken = default(CancellationToken))
{ {
var user = await UserManager.FindByNameAsync(userName, cancellationToken); var user = await UserManager.FindByNameAsync(userName, cancellationToken);
if (user == null) if (user == null)
{ {
return SignInStatus.Failure; return SignInResult.Failed;
} }
return await PasswordSignInAsync(user, password, isPersistent, shouldLockout, cancellationToken); return await PasswordSignInAsync(user, password, isPersistent, shouldLockout, cancellationToken);
} }
@ -207,7 +213,6 @@ namespace Microsoft.AspNet.Identity
return false; return false;
} }
var token = await UserManager.GenerateTwoFactorTokenAsync(user, provider, cancellationToken); 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); await UserManager.NotifyTwoFactorTokenAsync(user, provider, token, cancellationToken);
return true; return true;
} }
@ -236,23 +241,23 @@ namespace Microsoft.AspNet.Identity
return Task.FromResult(0); 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)) bool rememberClient, CancellationToken cancellationToken = default(CancellationToken))
{ {
var twoFactorInfo = await RetrieveTwoFactorInfoAsync(cancellationToken); var twoFactorInfo = await RetrieveTwoFactorInfoAsync(cancellationToken);
if (twoFactorInfo == null || twoFactorInfo.UserId == null) if (twoFactorInfo == null || twoFactorInfo.UserId == null)
{ {
return SignInStatus.Failure; return SignInResult.Failed;
} }
var user = await UserManager.FindByIdAsync(twoFactorInfo.UserId, cancellationToken); var user = await UserManager.FindByIdAsync(twoFactorInfo.UserId, cancellationToken);
if (user == null) if (user == null)
{ {
return SignInStatus.Failure; return SignInResult.Failed;
} }
var error = await PreSignInCheck(user, cancellationToken); var error = await PreSignInCheck(user, cancellationToken);
if (error != null) if (error != null)
{ {
return error.Value; return error;
} }
if (await UserManager.VerifyTwoFactorTokenAsync(user, provider, code, cancellationToken)) if (await UserManager.VerifyTwoFactorTokenAsync(user, provider, code, cancellationToken))
{ {
@ -268,11 +273,13 @@ namespace Microsoft.AspNet.Identity
{ {
await RememberTwoFactorClientAsync(user, cancellationToken); 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 // If the token is incorrect, record the failure which also may cause the user to be locked out
await UserManager.AccessFailedAsync(user, cancellationToken); await UserManager.AccessFailedAsync(user, cancellationToken);
return SignInStatus.Failure; return SignInResult.Failed;
} }
/// <summary> /// <summary>
@ -292,18 +299,18 @@ namespace Microsoft.AspNet.Identity
return await UserManager.FindByIdAsync(info.UserId, cancellationToken); 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)) CancellationToken cancellationToken = default(CancellationToken))
{ {
var user = await UserManager.FindByLoginAsync(loginProvider, providerKey, cancellationToken); var user = await UserManager.FindByLoginAsync(loginProvider, providerKey, cancellationToken);
if (user == null) if (user == null)
{ {
return SignInStatus.Failure; return SignInResult.Failed;
} }
var error = await PreSignInCheck(user, cancellationToken); var error = await PreSignInCheck(user, cancellationToken);
if (error != null) if (error != null)
{ {
return error.Value; return error;
} }
return await SignInOrTwoFactorAsync(user, isPersistent, cancellationToken, loginProvider); return await SignInOrTwoFactorAsync(user, isPersistent, cancellationToken, loginProvider);
} }
@ -358,7 +365,7 @@ namespace Microsoft.AspNet.Identity
return properties; 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) CancellationToken cancellationToken, string loginProvider = null)
{ {
if (UserManager.SupportsUserTwoFactor && if (UserManager.SupportsUserTwoFactor &&
@ -370,7 +377,7 @@ namespace Microsoft.AspNet.Identity
// Store the userId for use after two factor check // Store the userId for use after two factor check
var userId = await UserManager.GetUserIdAsync(user, cancellationToken); var userId = await UserManager.GetUserIdAsync(user, cancellationToken);
Context.Response.SignIn(StoreTwoFactorInfo(userId, loginProvider)); Context.Response.SignIn(StoreTwoFactorInfo(userId, loginProvider));
return SignInStatus.RequiresVerification; return SignInResult.TwoFactorRequired;
} }
} }
// Cleanup external cookie // Cleanup external cookie
@ -379,7 +386,7 @@ namespace Microsoft.AspNet.Identity
Context.Response.SignOut(IdentityOptions.ExternalCookieAuthenticationType); Context.Response.SignOut(IdentityOptions.ExternalCookieAuthenticationType);
} }
await SignInAsync(user, isPersistent, loginProvider, cancellationToken); await SignInAsync(user, isPersistent, loginProvider, cancellationToken);
return SignInStatus.Success; return SignInResult.Success;
} }
private async Task<TwoFactorAuthenticationInfo> RetrieveTwoFactorInfoAsync(CancellationToken cancellationToken) 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="passwordValidator"></param>
/// <param name="claimsIdentityFactory"></param> /// <param name="claimsIdentityFactory"></param>
public UserManager(IUserStore<TUser> store, public UserManager(IUserStore<TUser> store,
IOptions<IdentityOptions> optionsAccessor, IOptions<IdentityOptions> optionsAccessor = null,
IPasswordHasher<TUser> passwordHasher, IPasswordHasher<TUser> passwordHasher = null,
IEnumerable<IUserValidator<TUser>> userValidators, IEnumerable<IUserValidator<TUser>> userValidators = null,
IEnumerable<IPasswordValidator<TUser>> passwordValidators, IEnumerable<IPasswordValidator<TUser>> passwordValidators = null,
IUserNameNormalizer userNameNormalizer, IUserNameNormalizer userNameNormalizer = null,
IEnumerable<IUserTokenProvider<TUser>> tokenProviders, IdentityErrorDescriber errors = null,
IEnumerable<IIdentityMessageProvider> msgProviders) IEnumerable<IUserTokenProvider<TUser>> tokenProviders = null,
IEnumerable<IIdentityMessageProvider> msgProviders = null)
{ {
if (store == null) if (store == null)
{ {
throw new ArgumentNullException(nameof(store)); 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; Store = store;
Options = optionsAccessor.Options; Options = optionsAccessor?.Options ?? new IdentityOptions();
PasswordHasher = passwordHasher; PasswordHasher = passwordHasher ?? new PasswordHasher<TUser>();
UserNameNormalizer = userNameNormalizer; UserNameNormalizer = userNameNormalizer ?? new UpperInvariantUserNameNormalizer();
ErrorDescriber = errors ?? new IdentityErrorDescriber();
if (userValidators != null) if (userValidators != null)
{ {
foreach (var v in userValidators) foreach (var v in userValidators)
@ -136,6 +130,11 @@ namespace Microsoft.AspNet.Identity
/// </summary> /// </summary>
public IUserNameNormalizer UserNameNormalizer { get; set; } public IUserNameNormalizer UserNameNormalizer { get; set; }
/// <summary>
/// Used to generate public API error messages
/// </summary>
public IdentityErrorDescriber ErrorDescriber { get; set; }
public IdentityOptions Options public IdentityOptions Options
{ {
get get
@ -301,7 +300,7 @@ namespace Microsoft.AspNet.Identity
private async Task<IdentityResult> ValidateUserInternal(TUser user, CancellationToken cancellationToken) private async Task<IdentityResult> ValidateUserInternal(TUser user, CancellationToken cancellationToken)
{ {
var errors = new List<string>(); var errors = new List<IdentityError>();
foreach (var v in UserValidators) foreach (var v in UserValidators)
{ {
var result = await v.ValidateAsync(this, user, cancellationToken); 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) 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) foreach (var v in PasswordValidators)
{ {
var result = await v.ValidateAsync(this, user, password, cancellationToken); var result = await v.ValidateAsync(this, user, password, cancellationToken);
@ -598,7 +597,7 @@ namespace Microsoft.AspNet.Identity
var hash = await passwordStore.GetPasswordHashAsync(user, cancellationToken); var hash = await passwordStore.GetPasswordHashAsync(user, cancellationToken);
if (hash != null) if (hash != null)
{ {
return new IdentityResult(Resources.UserAlreadyHasPassword); return IdentityResult.Failed(ErrorDescriber.UserAlreadyHasPassword());
} }
var result = await UpdatePasswordInternal(passwordStore, user, password, cancellationToken); var result = await UpdatePasswordInternal(passwordStore, user, password, cancellationToken);
if (!result.Succeeded) if (!result.Succeeded)
@ -634,7 +633,7 @@ namespace Microsoft.AspNet.Identity
} }
return await UpdateAsync(user, cancellationToken); return await UpdateAsync(user, cancellationToken);
} }
return IdentityResult.Failed(Resources.PasswordMismatch); return IdentityResult.Failed(ErrorDescriber.PasswordMismatch());
} }
/// <summary> /// <summary>
@ -766,7 +765,7 @@ namespace Microsoft.AspNet.Identity
// Make sure the token is valid and the stamp matches // Make sure the token is valid and the stamp matches
if (!await VerifyUserTokenAsync(user, Options.PasswordResetTokenProvider, "ResetPassword", token, cancellationToken)) if (!await VerifyUserTokenAsync(user, Options.PasswordResetTokenProvider, "ResetPassword", token, cancellationToken))
{ {
return IdentityResult.Failed(Resources.InvalidToken); return IdentityResult.Failed(ErrorDescriber.InvalidToken());
} }
var passwordStore = GetPasswordStore(); var passwordStore = GetPasswordStore();
var result = await UpdatePasswordInternal(passwordStore, user, newPassword, cancellationToken); 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); var existingUser = await FindByLoginAsync(login.LoginProvider, login.ProviderKey, cancellationToken);
if (existingUser != null) if (existingUser != null)
{ {
return IdentityResult.Failed(Resources.ExternalLoginExists); return IdentityResult.Failed(ErrorDescriber.LoginAlreadyAssociated());
} }
await loginStore.AddLoginAsync(user, login, cancellationToken); await loginStore.AddLoginAsync(user, login, cancellationToken);
return await UpdateAsync(user, cancellationToken); return await UpdateAsync(user, cancellationToken);
@ -1082,7 +1081,7 @@ namespace Microsoft.AspNet.Identity
var userRoles = await userRoleStore.GetRolesAsync(user, cancellationToken); var userRoles = await userRoleStore.GetRolesAsync(user, cancellationToken);
if (userRoles.Contains(role)) if (userRoles.Contains(role))
{ {
return new IdentityResult(Resources.UserAlreadyInRole); return IdentityResult.Failed(ErrorDescriber.UserAlreadyInRole(role));
} }
await userRoleStore.AddToRoleAsync(user, role, cancellationToken); await userRoleStore.AddToRoleAsync(user, role, cancellationToken);
return await UpdateAsync(user, cancellationToken); return await UpdateAsync(user, cancellationToken);
@ -1113,7 +1112,7 @@ namespace Microsoft.AspNet.Identity
{ {
if (userRoles.Contains(role)) if (userRoles.Contains(role))
{ {
return new IdentityResult(Resources.UserAlreadyInRole); return IdentityResult.Failed(ErrorDescriber.UserAlreadyInRole(role));
} }
await userRoleStore.AddToRoleAsync(user, role, cancellationToken); await userRoleStore.AddToRoleAsync(user, role, cancellationToken);
} }
@ -1138,7 +1137,7 @@ namespace Microsoft.AspNet.Identity
} }
if (!await userRoleStore.IsInRoleAsync(user, role, cancellationToken)) if (!await userRoleStore.IsInRoleAsync(user, role, cancellationToken))
{ {
return new IdentityResult(Resources.UserNotInRole); return IdentityResult.Failed(ErrorDescriber.UserNotInRole(role));
} }
await userRoleStore.RemoveFromRoleAsync(user, role, cancellationToken); await userRoleStore.RemoveFromRoleAsync(user, role, cancellationToken);
return await UpdateAsync(user, cancellationToken); return await UpdateAsync(user, cancellationToken);
@ -1168,7 +1167,7 @@ namespace Microsoft.AspNet.Identity
{ {
if (!await userRoleStore.IsInRoleAsync(user, role, cancellationToken)) if (!await userRoleStore.IsInRoleAsync(user, role, cancellationToken))
{ {
return new IdentityResult(Resources.UserNotInRole); return IdentityResult.Failed(ErrorDescriber.UserNotInRole(role));
} }
await userRoleStore.RemoveFromRoleAsync(user, role, cancellationToken); await userRoleStore.RemoveFromRoleAsync(user, role, cancellationToken);
} }
@ -1313,7 +1312,7 @@ namespace Microsoft.AspNet.Identity
} }
if (!await VerifyUserTokenAsync(user, Options.EmailConfirmationTokenProvider, "Confirmation", token, cancellationToken)) 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); await store.SetEmailConfirmedAsync(user, true, cancellationToken);
return await UpdateAsync(user, cancellationToken); return await UpdateAsync(user, cancellationToken);
@ -1374,7 +1373,7 @@ namespace Microsoft.AspNet.Identity
// Make sure the token is valid and the stamp matches // Make sure the token is valid and the stamp matches
if (!await VerifyUserTokenAsync(user, Options.ChangeEmailTokenProvider, GetChangeEmailPurpose(newEmail), token, cancellationToken)) if (!await VerifyUserTokenAsync(user, Options.ChangeEmailTokenProvider, GetChangeEmailPurpose(newEmail), token, cancellationToken))
{ {
return IdentityResult.Failed(Resources.InvalidToken); return IdentityResult.Failed(ErrorDescriber.InvalidToken());
} }
var store = GetEmailStore(); var store = GetEmailStore();
await store.SetEmailAsync(user, newEmail, cancellationToken); await store.SetEmailAsync(user, newEmail, cancellationToken);
@ -1453,7 +1452,7 @@ namespace Microsoft.AspNet.Identity
} }
if (!await VerifyChangePhoneNumberTokenAsync(user, token, phoneNumber, cancellationToken)) 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.SetPhoneNumberAsync(user, phoneNumber, cancellationToken);
await store.SetPhoneNumberConfirmedAsync(user, true, cancellationToken); await store.SetPhoneNumberConfirmedAsync(user, true, cancellationToken);
@ -1890,7 +1889,7 @@ namespace Microsoft.AspNet.Identity
} }
if (!await store.GetLockoutEnabledAsync(user, cancellationToken).ConfigureAwait((false))) if (!await store.GetLockoutEnabledAsync(user, cancellationToken).ConfigureAwait((false)))
{ {
return IdentityResult.Failed(Resources.LockoutNotEnabled); return IdentityResult.Failed(ErrorDescriber.UserLockoutNotEnabled());
} }
await store.SetLockoutEndDateAsync(user, lockoutEnd, cancellationToken); await store.SetLockoutEndDateAsync(user, lockoutEnd, cancellationToken);
return await UpdateAsync(user, cancellationToken); return await UpdateAsync(user, cancellationToken);

View File

@ -3,7 +3,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
#if ASPNET50 #if ASPNET50
using System.Net.Mail; using System.Net.Mail;
#endif #endif
@ -19,6 +18,13 @@ namespace Microsoft.AspNet.Identity
/// <typeparam name="TUser"></typeparam> /// <typeparam name="TUser"></typeparam>
public class UserValidator<TUser> : IUserValidator<TUser> where TUser : class 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> /// <summary>
/// Validates a user before saving /// Validates a user before saving
/// </summary> /// </summary>
@ -37,7 +43,7 @@ namespace Microsoft.AspNet.Identity
{ {
throw new ArgumentNullException("user"); throw new ArgumentNullException("user");
} }
var errors = new List<string>(); var errors = new List<IdentityError>();
await ValidateUserName(manager, user, errors); await ValidateUserName(manager, user, errors);
if (manager.Options.User.RequireUniqueEmail) if (manager.Options.User.RequireUniqueEmail)
{ {
@ -46,16 +52,16 @@ namespace Microsoft.AspNet.Identity
return errors.Count > 0 ? IdentityResult.Failed(errors.ToArray()) : IdentityResult.Success; 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); var userName = await manager.GetUserNameAsync(user);
if (string.IsNullOrWhiteSpace(userName)) 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)) 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 else
{ {
@ -63,18 +69,18 @@ namespace Microsoft.AspNet.Identity
if (owner != null && if (owner != null &&
!string.Equals(await manager.GetUserIdAsync(owner), await manager.GetUserIdAsync(user))) !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 // 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); var email = await manager.GetEmailAsync(user);
if (string.IsNullOrWhiteSpace(email)) if (string.IsNullOrWhiteSpace(email))
{ {
errors.Add(String.Format(CultureInfo.CurrentCulture, Resources.PropertyTooShort, "Email")); errors.Add(Describer.InvalidEmail(email));
return; return;
} }
#if ASPNET50 #if ASPNET50
@ -84,7 +90,7 @@ namespace Microsoft.AspNet.Identity
} }
catch (FormatException) catch (FormatException)
{ {
errors.Add(String.Format(CultureInfo.CurrentCulture, Resources.InvalidEmail, email)); errors.Add(Describer.InvalidEmail(email));
return; return;
} }
#endif #endif
@ -92,7 +98,7 @@ namespace Microsoft.AspNet.Identity
if (owner != null && if (owner != null &&
!string.Equals(await manager.GetUserIdAsync(owner), await manager.GetUserIdAsync(user))) !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>>(); var signInManager = app.ApplicationServices.GetRequiredService<SignInManager<ApplicationUser>>();
IdentityResultAssert.IsSuccess(await userManager.CreateAsync(user, password)); 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
Assert.Equal(SignInStatus.Success, result); Assert.True(result.Succeeded);
context.VerifyAll(); context.VerifyAll();
response.VerifyAll(); response.VerifyAll();
contextAccessor.VerifyAll(); contextAccessor.VerifyAll();

View File

@ -226,18 +226,7 @@ namespace Microsoft.AspNet.Identity.Test
private class MyUserManager : UserManager<TestUser> private class MyUserManager : UserManager<TestUser>
{ {
public MyUserManager(IUserStore<TestUser> store, public MyUserManager(IUserStore<TestUser> store) : base(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)
{
}
} }
private class MyRoleManager : RoleManager<TestRole> private class MyRoleManager : RoleManager<TestRole>

View File

@ -13,17 +13,15 @@ namespace Microsoft.AspNet.Identity.Test
{ {
var result = new IdentityResult(); var result = new IdentityResult();
Assert.False(result.Succeeded); Assert.False(result.Succeeded);
Assert.Equal(1, result.Errors.Count()); Assert.Equal(0, result.Errors.Count());
Assert.Equal("An unknown failure has occured.", result.Errors.First());
} }
[Fact] [Fact]
public void NullErrorListUsesDefaultError() public void NullFailedUsesEmptyErrors()
{ {
var result = new IdentityResult(null); var result = IdentityResult.Failed();
Assert.False(result.Succeeded); Assert.False(result.Succeeded);
Assert.Equal(1, result.Errors.Count()); Assert.Equal(0, result.Errors.Count());
Assert.Equal("An unknown failure has occured.", result.Errors.First());
} }
} }
} }

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Xunit; using Xunit;
@ -133,13 +134,18 @@ namespace Microsoft.AspNet.Identity.Test
{ {
errors.Add(upperError); errors.Add(upperError);
} }
var result = await valid.ValidateAsync(manager, null, input);
if (errors.Count == 0) if (errors.Count == 0)
{ {
IdentityResultAssert.IsSuccess(await valid.ValidateAsync(manager, null, input)); IdentityResultAssert.IsSuccess(result);
} }
else 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); var result = await validator.ValidateAsync(manager, user);
// Assert // 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. // 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. // 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Security.Claims; using System.Security.Claims;
using System.Security.Principal; using System.Security.Principal;
using System.Threading; using System.Threading;
using System.Threading.Tasks; 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; using Xunit;
namespace Microsoft.AspNet.Identity.Test namespace Microsoft.AspNet.Identity.Test
@ -75,8 +75,6 @@ namespace Microsoft.AspNet.Identity.Test
var context = new Mock<HttpContext>(); var context = new Mock<HttpContext>();
contextAccessor.Setup(a => a.Value).Returns(context.Object); contextAccessor.Setup(a => a.Value).Returns(context.Object);
Assert.Throws<ArgumentNullException>("claimsFactory", () => new SignInManager<IdentityUser>(userManager, contextAccessor.Object, null, null)); 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) //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); var result = await helper.PasswordSignInAsync(user.UserName, "bogus", false, false);
// Assert // Assert
Assert.Equal(SignInStatus.LockedOut, result); Assert.False(result.Succeeded);
Assert.True(result.IsLockedOut);
manager.VerifyAll(); manager.VerifyAll();
} }
@ -167,7 +166,7 @@ namespace Microsoft.AspNet.Identity.Test
var result = await helper.PasswordSignInAsync(user.UserName, "password", isPersistent, false); var result = await helper.PasswordSignInAsync(user.UserName, "password", isPersistent, false);
// Assert // Assert
Assert.Equal(SignInStatus.Success, result); Assert.True(result.Succeeded);
manager.VerifyAll(); manager.VerifyAll();
context.VerifyAll(); context.VerifyAll();
response.VerifyAll(); response.VerifyAll();
@ -203,7 +202,7 @@ namespace Microsoft.AspNet.Identity.Test
var result = await helper.PasswordSignInAsync(user.UserName, "password", false, false); var result = await helper.PasswordSignInAsync(user.UserName, "password", false, false);
// Assert // Assert
Assert.Equal(SignInStatus.Success, result); Assert.True(result.Succeeded);
manager.VerifyAll(); manager.VerifyAll();
context.VerifyAll(); context.VerifyAll();
response.VerifyAll(); response.VerifyAll();
@ -251,7 +250,8 @@ namespace Microsoft.AspNet.Identity.Test
var result = await helper.PasswordSignInAsync(user.UserName, "password", false, false); var result = await helper.PasswordSignInAsync(user.UserName, "password", false, false);
// Assert // Assert
Assert.Equal(SignInStatus.RequiresVerification, result); Assert.False(result.Succeeded);
Assert.True(result.RequiresTwoFactor);
manager.VerifyAll(); manager.VerifyAll();
context.VerifyAll(); context.VerifyAll();
response.VerifyAll(); response.VerifyAll();
@ -297,7 +297,7 @@ namespace Microsoft.AspNet.Identity.Test
var result = await helper.ExternalLoginSignInAsync(loginProvider, providerKey, isPersistent); var result = await helper.ExternalLoginSignInAsync(loginProvider, providerKey, isPersistent);
// Assert // Assert
Assert.Equal(SignInStatus.Success, result); Assert.True(result.Succeeded);
manager.VerifyAll(); manager.VerifyAll();
context.VerifyAll(); context.VerifyAll();
response.VerifyAll(); response.VerifyAll();
@ -382,7 +382,7 @@ namespace Microsoft.AspNet.Identity.Test
var result = await helper.TwoFactorSignInAsync(provider, code, isPersistent, rememberClient); var result = await helper.TwoFactorSignInAsync(provider, code, isPersistent, rememberClient);
// Assert // Assert
Assert.Equal(SignInStatus.Success, result); Assert.True(result.Succeeded);
manager.VerifyAll(); manager.VerifyAll();
context.VerifyAll(); context.VerifyAll();
response.VerifyAll(); response.VerifyAll();
@ -466,7 +466,7 @@ namespace Microsoft.AspNet.Identity.Test
var result = await helper.PasswordSignInAsync(user.UserName, "password", isPersistent, false); var result = await helper.PasswordSignInAsync(user.UserName, "password", isPersistent, false);
// Assert // Assert
Assert.Equal(SignInStatus.Success, result); Assert.True(result.Succeeded);
manager.VerifyAll(); manager.VerifyAll();
context.VerifyAll(); context.VerifyAll();
response.VerifyAll(); response.VerifyAll();
@ -530,7 +530,7 @@ namespace Microsoft.AspNet.Identity.Test
var result = await helper.PasswordSignInAsync(user.UserName, "bogus", false, false); var result = await helper.PasswordSignInAsync(user.UserName, "bogus", false, false);
// Assert // Assert
Assert.Equal(SignInStatus.Failure, result); Assert.False(result.Succeeded);
manager.VerifyAll(); manager.VerifyAll();
context.VerifyAll(); context.VerifyAll();
contextAccessor.VerifyAll(); contextAccessor.VerifyAll();
@ -556,7 +556,7 @@ namespace Microsoft.AspNet.Identity.Test
var result = await helper.PasswordSignInAsync("bogus", "bogus", false, false); var result = await helper.PasswordSignInAsync("bogus", "bogus", false, false);
// Assert // Assert
Assert.Equal(SignInStatus.Failure, result); Assert.False(result.Succeeded);
manager.VerifyAll(); manager.VerifyAll();
context.VerifyAll(); context.VerifyAll();
contextAccessor.VerifyAll(); contextAccessor.VerifyAll();
@ -592,7 +592,8 @@ namespace Microsoft.AspNet.Identity.Test
var result = await helper.PasswordSignInAsync(user.UserName, "bogus", false, true); var result = await helper.PasswordSignInAsync(user.UserName, "bogus", false, true);
// Assert // Assert
Assert.Equal(SignInStatus.LockedOut, result); Assert.False(result.Succeeded);
Assert.True(result.IsLockedOut);
manager.VerifyAll(); manager.VerifyAll();
} }
@ -631,7 +632,9 @@ namespace Microsoft.AspNet.Identity.Test
var result = await helper.PasswordSignInAsync(user, "password", false, false); var result = await helper.PasswordSignInAsync(user, "password", false, false);
// Assert // Assert
Assert.Equal(confirmed ? SignInStatus.Success : SignInStatus.NotAllowed, result);
Assert.Equal(confirmed, result.Succeeded);
Assert.NotEqual(confirmed, result.IsNotAllowed);
manager.VerifyAll(); manager.VerifyAll();
context.VerifyAll(); context.VerifyAll();
response.VerifyAll(); response.VerifyAll();
@ -670,7 +673,8 @@ namespace Microsoft.AspNet.Identity.Test
var result = await helper.PasswordSignInAsync(user, "password", false, false); var result = await helper.PasswordSignInAsync(user, "password", false, false);
// Assert // Assert
Assert.Equal(confirmed ? SignInStatus.Success : SignInStatus.NotAllowed, result); Assert.Equal(confirmed, result.Succeeded);
Assert.NotEqual(confirmed, result.IsNotAllowed);
manager.VerifyAll(); manager.VerifyAll();
context.VerifyAll(); context.VerifyAll();
response.VerifyAll(); response.VerifyAll();

View File

@ -1,16 +1,15 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // 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. // 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Security.Claims; using System.Security.Claims;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.DependencyInjection.Fallback;
using Moq;
using Xunit; using Xunit;
namespace Microsoft.AspNet.Identity.Test namespace Microsoft.AspNet.Identity.Test
@ -21,10 +20,7 @@ namespace Microsoft.AspNet.Identity.Test
{ {
public IUserStore<TestUser> StorePublic { get { return Store; } } public IUserStore<TestUser> StorePublic { get { return Store; } }
public TestManager(IUserStore<TestUser> store, IOptions<IdentityOptions> optionsAccessor, public TestManager(IUserStore<TestUser> store) : base(store) { }
IPasswordHasher<TestUser> passwordHasher, IEnumerable<IUserValidator<TestUser>> userValidator,
IEnumerable<IPasswordValidator<TestUser>> passwordValidator)
: base(store, optionsAccessor, passwordHasher, userValidator, passwordValidator, null, null, null) { }
} }
[Fact] [Fact]
@ -36,8 +32,6 @@ namespace Microsoft.AspNet.Identity.Test
services.AddIdentity<TestUser, IdentityRole>(); services.AddIdentity<TestUser, IdentityRole>();
var manager = services.BuildServiceProvider().GetRequiredService<TestManager>(); var manager = services.BuildServiceProvider().GetRequiredService<TestManager>();
Assert.NotNull(manager.PasswordHasher); Assert.NotNull(manager.PasswordHasher);
Assert.Equal(1, manager.PasswordValidators.Count);
Assert.Equal(1, manager.UserValidators.Count);
Assert.NotNull(manager.StorePublic); Assert.NotNull(manager.StorePublic);
Assert.NotNull(manager.Options); Assert.NotNull(manager.Options);
} }
@ -66,7 +60,7 @@ namespace Microsoft.AspNet.Identity.Test
var store = new Mock<IUserStore<TestUser>>(); var store = new Mock<IUserStore<TestUser>>();
var user = new TestUser { UserName = "Foo" }; var user = new TestUser { UserName = "Foo" };
store.Setup(s => s.DeleteAsync(user, CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable(); 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 // Act
var result = await userManager.DeleteAsync(user); var result = await userManager.DeleteAsync(user);
@ -100,7 +94,7 @@ namespace Microsoft.AspNet.Identity.Test
var store = new Mock<IUserStore<TestUser>>(); var store = new Mock<IUserStore<TestUser>>();
var user = new TestUser(); var user = new TestUser();
store.Setup(s => s.SetUserNameAsync(user, It.IsAny<string>(), CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable(); 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 // Act
var result = await userManager.SetUserNameAsync(user, "foo"); var result = await userManager.SetUserNameAsync(user, "foo");
@ -207,7 +201,7 @@ namespace Microsoft.AspNet.Identity.Test
var result = await userManager.AddToRolesAsync(user, roles); var result = await userManager.AddToRolesAsync(user, roles);
// Assert // Assert
IdentityResultAssert.IsFailure(result, "User already in role."); IdentityResultAssert.IsFailure(result, IdentityErrorDescriber.Default.UserAlreadyInRole("B"));
store.VerifyAll(); store.VerifyAll();
} }
@ -269,7 +263,7 @@ namespace Microsoft.AspNet.Identity.Test
var result = await userManager.RemoveFromRolesAsync(user, roles); var result = await userManager.RemoveFromRolesAsync(user, roles);
// Assert // Assert
IdentityResultAssert.IsFailure(result, "User is not in role."); IdentityResultAssert.IsFailure(result, IdentityErrorDescriber.Default.UserNotInRole("B"));
store.VerifyAll(); store.VerifyAll();
} }
@ -284,7 +278,7 @@ namespace Microsoft.AspNet.Identity.Test
.Returns(Task.FromResult(0)) .Returns(Task.FromResult(0))
.Verifiable(); .Verifiable();
store.Setup(s => s.UpdateAsync(user, CancellationToken.None)).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 // Act
var result = await userManager.AddClaimsAsync(user, claims); var result = await userManager.AddClaimsAsync(user, claims);
@ -305,7 +299,7 @@ namespace Microsoft.AspNet.Identity.Test
.Returns(Task.FromResult(0)) .Returns(Task.FromResult(0))
.Verifiable(); .Verifiable();
store.Setup(s => s.UpdateAsync(user, CancellationToken.None)).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 // Act
var result = await userManager.AddClaimAsync(user, claim); 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)) store.Setup(s => s.ReplaceClaimAsync(user, It.IsAny<Claim>(), It.IsAny<Claim>(), CancellationToken.None))
.Returns(Task.FromResult(0)) .Returns(Task.FromResult(0))
.Verifiable(); .Verifiable();
var userManager = MockHelpers.TestUserManager<TestUser>(store.Object); var userManager = MockHelpers.TestUserManager(store.Object);
// Act // Act
var result = await userManager.ReplaceClaimAsync(user, claim, newClaim); var result = await userManager.ReplaceClaimAsync(user, claim, newClaim);
@ -533,17 +527,11 @@ namespace Microsoft.AspNet.Identity.Test
public async Task ManagerPublicNullChecks() public async Task ManagerPublicNullChecks()
{ {
var store = new NotImplementedStore(); var store = new NotImplementedStore();
var optionsAccessor = new OptionsManager<IdentityOptions>(null);
var passwordHasher = new PasswordHasher<TestUser>(new PasswordHasherOptionsAccessor());
Assert.Throws<ArgumentNullException>("store", Assert.Throws<ArgumentNullException>("store",
() => new UserManager<TestUser>(null, null, null, null, null, null, null, null)); () => new UserManager<TestUser>(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));
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.PasswordHasher = null);
Assert.Throws<ArgumentNullException>("value", () => manager.Options = 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 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)) 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(); 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); var result = await validator.ValidateAsync(manager, user);
// Assert // Assert
IdentityResultAssert.IsFailure(result, "UserName cannot be null or empty."); IdentityResultAssert.IsFailure(result, IdentityErrorDescriber.Default.InvalidUserName(input));
} }
[Theory] [Theory]

View File

@ -24,7 +24,16 @@ namespace Microsoft.AspNet.Identity.Test
{ {
Assert.NotNull(result); Assert.NotNull(result);
Assert.False(result.Succeeded); 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 public static Mock<UserManager<TUser>> MockUserManager<TUser>() where TUser : class
{ {
var store = new Mock<IUserStore<TUser>>(); var store = new Mock<IUserStore<TUser>>();
var options = new OptionsManager<IdentityOptions>(null); var mgr = new Mock<UserManager<TUser>>(store.Object, null, null, null, null, null, null, null, null);
var userValidators = new List<IUserValidator<TUser>>(); mgr.Object.UserValidators.Add(new UserValidator<TUser>());
userValidators.Add(new UserValidator<TUser>()); mgr.Object.PasswordValidators.Add(new PasswordValidator<TUser>());
var pwdValidators = new List<IPasswordValidator<TUser>>(); return mgr;
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>());
} }
public static Mock<RoleManager<TRole>> MockRoleManager<TRole>() where TRole : class 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 store = new Mock<IRoleStore<TRole>>();
var roles = new List<IRoleValidator<TRole>>(); var roles = new List<IRoleValidator<TRole>>();
roles.Add(new RoleValidator<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 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 public static UserManager<TUser> TestUserManager<TUser>(IUserStore<TUser> store) where TUser : class
{ {
var options = new OptionsManager<IdentityOptions>(null); var validator = new Mock<IUserValidator<TUser>>();
var validator = new Mock<UserValidator<TUser>>(); var userManager = new UserManager<TUser>(store);
var userManager = new UserManager<TUser>(store, options, new PasswordHasher<TUser>(new PasswordHasherOptionsAccessor()),
null, null, new UpperInvariantUserNameNormalizer(), null, null);
userManager.UserValidators.Add(validator.Object); userManager.UserValidators.Add(validator.Object);
userManager.PasswordValidators.Add(new PasswordValidator<TUser>()); userManager.PasswordValidators.Add(new PasswordValidator<TUser>());
validator.Setup(v => v.ValidateAsync(userManager, It.IsAny<TUser>(), CancellationToken.None)) 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 Id { get; private set; }
public string UserName { get; 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 manager = CreateManager();
var user = CreateTestUser(); var user = CreateTestUser();
manager.Options.User.RequireUniqueEmail = true; 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 #if ASPNET50
@ -194,7 +194,7 @@ namespace Microsoft.AspNet.Identity.Test
var manager = CreateManager(); var manager = CreateManager();
var user = new TUser() { UserName = "UpdateBlocked", Email = email }; var user = new TUser() { UserName = "UpdateBlocked", Email = email };
manager.Options.User.RequireUniqueEmail = true; 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 #endif
@ -414,7 +414,7 @@ namespace Microsoft.AspNet.Identity.Test
var user = CreateTestUser(); var user = CreateTestUser();
var user2 = new TUser() { UserName = user.UserName }; var user2 = new TUser() { UserName = user.UserName };
IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); 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] [Fact]
@ -440,7 +440,7 @@ namespace Microsoft.AspNet.Identity.Test
user.Email = email; user.Email = email;
user2.Email = email; user2.Email = email;
IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); 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] [Fact]
@ -465,7 +465,7 @@ namespace Microsoft.AspNet.Identity.Test
IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); IdentityResultAssert.IsSuccess(await manager.CreateAsync(user));
IdentityResultAssert.IsSuccess(await manager.AddLoginAsync(user, login)); IdentityResultAssert.IsSuccess(await manager.AddLoginAsync(user, login));
var result = 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 // Email tests
@ -842,7 +842,7 @@ namespace Microsoft.AspNet.Identity.Test
private class AlwaysBadValidator : IUserValidator<TUser>, IRoleValidator<TRole>, private class AlwaysBadValidator : IUserValidator<TUser>, IRoleValidator<TRole>,
IPasswordValidator<TUser> 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)) 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 userMgr.CreateAsync(user));
IdentityResultAssert.IsSuccess(await roleMgr.CreateAsync(role)); IdentityResultAssert.IsSuccess(await roleMgr.CreateAsync(role));
var result = await userMgr.RemoveFromRoleAsync(user, role.Name); 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] [Fact]
@ -1157,7 +1157,7 @@ namespace Microsoft.AspNet.Identity.Test
IdentityResultAssert.IsSuccess(await roleMgr.CreateAsync(role)); IdentityResultAssert.IsSuccess(await roleMgr.CreateAsync(role));
IdentityResultAssert.IsSuccess(await userMgr.AddToRoleAsync(user, role.Name)); IdentityResultAssert.IsSuccess(await userMgr.AddToRoleAsync(user, role.Name));
Assert.True(await userMgr.IsInRoleAsync(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] [Fact]