diff --git a/samples/IdentitySample.Mvc/LocalConfig.json b/samples/IdentitySample.Mvc/LocalConfig.json
index 06b6876a05..49b1ac00a4 100644
--- a/samples/IdentitySample.Mvc/LocalConfig.json
+++ b/samples/IdentitySample.Mvc/LocalConfig.json
@@ -3,7 +3,7 @@
"DefaultAdminPassword": "YouShouldChangeThisPassword1!",
"Data": {
"IdentityConnection": {
- "Connectionstring": "Server=(localdb)\\mssqllocaldb;Database=IdentityMvc-11-24-14;Trusted_Connection=True;MultipleActiveResultSets=true"
+ "Connectionstring": "Server=(localdb)\\mssqllocaldb;Database=IdentityMvc-1-7-15;Trusted_Connection=True;MultipleActiveResultSets=true"
}
},
"Identity": {
diff --git a/src/Microsoft.AspNet.Identity.EntityFramework/RoleStore.cs b/src/Microsoft.AspNet.Identity.EntityFramework/RoleStore.cs
index 884b7a5b30..89cf656d5a 100644
--- a/src/Microsoft.AspNet.Identity.EntityFramework/RoleStore.cs
+++ b/src/Microsoft.AspNet.Identity.EntityFramework/RoleStore.cs
@@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Linq.Expressions;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
@@ -188,16 +187,39 @@ namespace Microsoft.AspNet.Identity.EntityFramework
}
///
- /// Find a role by name
+ /// Find a role by normalized name
///
- ///
+ ///
///
///
- public virtual Task FindByNameAsync(string name, CancellationToken cancellationToken = default(CancellationToken))
+ public virtual Task FindByNameAsync(string normalizedName, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
- return Roles.FirstOrDefaultAsync(u => u.Name.ToUpper() == name.ToUpper(), cancellationToken);
+ return Roles.FirstOrDefaultAsync(r => r.NormalizedName == normalizedName, cancellationToken);
+ }
+
+ public virtual Task GetNormalizedRoleNameAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ ThrowIfDisposed();
+ if (role == null)
+ {
+ throw new ArgumentNullException(nameof(role));
+ }
+ return Task.FromResult(role.NormalizedName);
+ }
+
+ public virtual Task SetNormalizedRoleNameAsync(TRole role, string normalizedName, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ ThrowIfDisposed();
+ if (role == null)
+ {
+ throw new ArgumentNullException(nameof(role));
+ }
+ role.NormalizedName = normalizedName;
+ return Task.FromResult(0);
}
private void ThrowIfDisposed()
diff --git a/src/Microsoft.AspNet.Identity.EntityFramework/UserStore.cs b/src/Microsoft.AspNet.Identity.EntityFramework/UserStore.cs
index c21198dcbf..74e6ddb234 100644
--- a/src/Microsoft.AspNet.Identity.EntityFramework/UserStore.cs
+++ b/src/Microsoft.AspNet.Identity.EntityFramework/UserStore.cs
@@ -78,7 +78,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework
return AutoSaveChanges ? Context.SaveChangesAsync(cancellationToken) : Task.FromResult(0);
}
- public Task GetUserIdAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
+ public virtual Task GetUserIdAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
@@ -89,7 +89,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework
return Task.FromResult(ConvertIdToString(user.Id));
}
- public Task GetUserNameAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
+ public virtual Task GetUserNameAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
@@ -100,7 +100,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework
return Task.FromResult(user.UserName);
}
- public Task SetUserNameAsync(TUser user, string userName, CancellationToken cancellationToken = default(CancellationToken))
+ public virtual Task SetUserNameAsync(TUser user, string userName, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
@@ -112,7 +112,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework
return Task.FromResult(0);
}
- public Task GetNormalizedUserNameAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
+ public virtual Task GetNormalizedUserNameAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
@@ -123,7 +123,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework
return Task.FromResult(user.NormalizedUserName);
}
- public Task SetNormalizedUserNameAsync(TUser user, string normalizedName, CancellationToken cancellationToken = default(CancellationToken))
+ public virtual Task SetNormalizedUserNameAsync(TUser user, string normalizedName, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
@@ -420,7 +420,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework
private DbSet> UserRoles { get { return Context.Set>(); } }
private DbSet> UserLogins { get { return Context.Set>(); } }
- public async Task> GetClaimsAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
+ public async virtual Task> GetClaimsAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
{
ThrowIfDisposed();
if (user == null)
@@ -431,7 +431,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework
return await UserClaims.Where(uc => uc.UserId.Equals(user.Id)).Select(c => new Claim(c.ClaimType, c.ClaimValue)).ToListAsync(cancellationToken);
}
- public async Task AddClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken = default(CancellationToken))
+ public async virtual Task AddClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken = default(CancellationToken))
{
ThrowIfDisposed();
if (user == null)
@@ -448,7 +448,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework
}
}
- public async Task ReplaceClaimAsync(TUser user, Claim claim, Claim newClaim, CancellationToken cancellationToken = default(CancellationToken))
+ public async virtual Task ReplaceClaimAsync(TUser user, Claim claim, Claim newClaim, CancellationToken cancellationToken = default(CancellationToken))
{
ThrowIfDisposed();
if (user == null)
@@ -472,7 +472,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework
}
}
- public async Task RemoveClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken = default(CancellationToken))
+ public async virtual Task RemoveClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken = default(CancellationToken))
{
ThrowIfDisposed();
if (user == null)
@@ -633,19 +633,40 @@ namespace Microsoft.AspNet.Identity.EntityFramework
return Task.FromResult(user.Email);
}
+ public virtual Task GetNormalizedEmailAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ ThrowIfDisposed();
+ if (user == null)
+ {
+ throw new ArgumentNullException("user");
+ }
+ return Task.FromResult(user.NormalizedEmail);
+ }
+
+ public virtual Task SetNormalizedEmailAsync(TUser user, string normalizedEmail, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ ThrowIfDisposed();
+ if (user == null)
+ {
+ throw new ArgumentNullException("user");
+ }
+ user.NormalizedEmail = normalizedEmail;
+ return Task.FromResult(0);
+ }
+
///
/// Find an user by email
///
///
///
///
- public virtual Task FindByEmailAsync(string email, CancellationToken cancellationToken = default(CancellationToken))
+ public virtual Task FindByEmailAsync(string normalizedEmail, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
- return Users.FirstOrDefaultAsync(u => u.Email == email, cancellationToken);
- // todo: ToUpper blows up with Null Ref
- //return Users.FirstOrDefaultAsync(u => u.Email.ToUpper() == email.ToUpper(), cancellationToken);
+ return Users.FirstOrDefaultAsync(u => u.NormalizedEmail == normalizedEmail, cancellationToken);
}
///
@@ -925,7 +946,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework
///
///
///
- public async Task> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken = default(CancellationToken))
+ public async virtual Task> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
@@ -940,7 +961,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework
&& userclaims.ClaimType == claim.Type
select user;
- return (IList)await query.ToListAsync(cancellationToken);
+ return await query.ToListAsync(cancellationToken);
}
///
@@ -949,7 +970,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework
///
///
///
- public async Task> GetUsersInRoleAsync(string roleName, CancellationToken cancellationToken = default(CancellationToken))
+ public async virtual Task> GetUsersInRoleAsync(string roleName, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
@@ -967,7 +988,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework
where userrole.RoleId.Equals(role.Id)
select user;
- return (IList) await query.ToListAsync(cancellationToken);
+ return await query.ToListAsync(cancellationToken);
}
return new List();
}
diff --git a/src/Microsoft.AspNet.Identity/IUserNameNormalizer.cs b/src/Microsoft.AspNet.Identity/ILookupNormalizer.cs
similarity index 61%
rename from src/Microsoft.AspNet.Identity/IUserNameNormalizer.cs
rename to src/Microsoft.AspNet.Identity/ILookupNormalizer.cs
index b8d1bfcbf2..159f222ffa 100644
--- a/src/Microsoft.AspNet.Identity/IUserNameNormalizer.cs
+++ b/src/Microsoft.AspNet.Identity/ILookupNormalizer.cs
@@ -4,15 +4,15 @@
namespace Microsoft.AspNet.Identity
{
///
- /// Used to normalize a user name
+ /// Used to normalize keys for consistent lookups
///
- public interface IUserNameNormalizer
+ public interface ILookupNormalizer
{
///
- /// Returns the normalized user name
+ /// Returns the normalized key
///
- ///
+ ///
///
- string Normalize(string userName);
+ string Normalize(string key);
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/IRoleStore.cs b/src/Microsoft.AspNet.Identity/IRoleStore.cs
index e4fb97e532..405a47ce31 100644
--- a/src/Microsoft.AspNet.Identity/IRoleStore.cs
+++ b/src/Microsoft.AspNet.Identity/IRoleStore.cs
@@ -63,6 +63,26 @@ namespace Microsoft.AspNet.Identity
Task SetRoleNameAsync(TRole role, string roleName,
CancellationToken cancellationToken = default(CancellationToken));
+ ///
+ /// Get a role's normalized name
+ ///
+ ///
+ ///
+ ///
+ Task GetNormalizedRoleNameAsync(TRole role,
+ CancellationToken cancellationToken = default(CancellationToken));
+
+ ///
+ /// Set a role's normalized name
+ ///
+ ///
+ ///
+ ///
+ ///
+ Task SetNormalizedRoleNameAsync(TRole role, string normalizedName,
+ CancellationToken cancellationToken = default(CancellationToken));
+
+
///
/// Finds a role by id
///
@@ -72,11 +92,11 @@ namespace Microsoft.AspNet.Identity
Task FindByIdAsync(string roleId, CancellationToken cancellationToken = default(CancellationToken));
///
- /// Find a role by name
+ /// Find a role by normalized name
///
- ///
+ ///
///
///
- Task FindByNameAsync(string roleName, CancellationToken cancellationToken = default(CancellationToken));
+ Task FindByNameAsync(string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken));
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/IUserEmailStore.cs b/src/Microsoft.AspNet.Identity/IUserEmailStore.cs
index 6569548ffe..816f9383eb 100644
--- a/src/Microsoft.AspNet.Identity/IUserEmailStore.cs
+++ b/src/Microsoft.AspNet.Identity/IUserEmailStore.cs
@@ -51,9 +51,28 @@ namespace Microsoft.AspNet.Identity
///
/// Returns the user associated with this email
///
- ///
+ ///
///
///
- Task FindByEmailAsync(string email, CancellationToken cancellationToken = default(CancellationToken));
+ Task FindByEmailAsync(string normalizedEmail, CancellationToken cancellationToken = default(CancellationToken));
+
+ ///
+ /// Returns the normalized email
+ ///
+ ///
+ ///
+ ///
+ Task GetNormalizedEmailAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken));
+
+ ///
+ /// Set the normalized email
+ ///
+ ///
+ ///
+ ///
+ ///
+ Task SetNormalizedEmailAsync(TUser user, string normalizedEmail,
+ CancellationToken cancellationToken = default(CancellationToken));
+
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/IdentityErrorDescriber.cs b/src/Microsoft.AspNet.Identity/IdentityErrorDescriber.cs
index 9c998bc6a4..39d1677713 100644
--- a/src/Microsoft.AspNet.Identity/IdentityErrorDescriber.cs
+++ b/src/Microsoft.AspNet.Identity/IdentityErrorDescriber.cs
@@ -24,7 +24,6 @@ namespace Microsoft.AspNet.Identity
};
}
-
public virtual IdentityError PasswordMismatch()
{
return new IdentityError
diff --git a/src/Microsoft.AspNet.Identity/IdentityRole.cs b/src/Microsoft.AspNet.Identity/IdentityRole.cs
index 711ff58f32..0127acb84d 100644
--- a/src/Microsoft.AspNet.Identity/IdentityRole.cs
+++ b/src/Microsoft.AspNet.Identity/IdentityRole.cs
@@ -65,6 +65,7 @@ namespace Microsoft.AspNet.Identity
/// Role name
///
public virtual string Name { get; set; }
+ public virtual string NormalizedName { get; set; }
///
/// A random value that should change whenever a role is persisted to the store
diff --git a/src/Microsoft.AspNet.Identity/IdentityServiceCollectionExtensions.cs b/src/Microsoft.AspNet.Identity/IdentityServiceCollectionExtensions.cs
index 33b4d5e8f6..6ccc4ce6c5 100644
--- a/src/Microsoft.AspNet.Identity/IdentityServiceCollectionExtensions.cs
+++ b/src/Microsoft.AspNet.Identity/IdentityServiceCollectionExtensions.cs
@@ -57,7 +57,7 @@ namespace Microsoft.Framework.DependencyInjection
services.TryAdd(describe.Transient, UserValidator>());
services.TryAdd(describe.Transient, PasswordValidator>());
services.TryAdd(describe.Transient, PasswordHasher>());
- services.TryAdd(describe.Transient());
+ services.TryAdd(describe.Transient());
services.TryAdd(describe.Transient, RoleValidator>());
// No interface for the error describer so we can add errors without rev'ing the interface
services.TryAdd(describe.Transient());
diff --git a/src/Microsoft.AspNet.Identity/IdentityUser.cs b/src/Microsoft.AspNet.Identity/IdentityUser.cs
index 1589199a77..4392bf7db4 100644
--- a/src/Microsoft.AspNet.Identity/IdentityUser.cs
+++ b/src/Microsoft.AspNet.Identity/IdentityUser.cs
@@ -36,6 +36,8 @@ namespace Microsoft.AspNet.Identity
///
public virtual string Email { get; set; }
+ public virtual string NormalizedEmail { get; set; }
+
///
/// True if the email is confirmed, default is false
///
diff --git a/src/Microsoft.AspNet.Identity/PasswordHasherOptions.cs b/src/Microsoft.AspNet.Identity/PasswordHasherOptions.cs
index e441a7bee0..a52e476f15 100644
--- a/src/Microsoft.AspNet.Identity/PasswordHasherOptions.cs
+++ b/src/Microsoft.AspNet.Identity/PasswordHasherOptions.cs
@@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Security.Cryptography;
-using Microsoft.AspNet.Security.DataProtection;
namespace Microsoft.AspNet.Identity
{
diff --git a/src/Microsoft.AspNet.Identity/RoleManager.cs b/src/Microsoft.AspNet.Identity/RoleManager.cs
index cf13cee60a..7b23ca8ab2 100644
--- a/src/Microsoft.AspNet.Identity/RoleManager.cs
+++ b/src/Microsoft.AspNet.Identity/RoleManager.cs
@@ -25,6 +25,7 @@ namespace Microsoft.AspNet.Identity
///
public RoleManager(IRoleStore store,
IEnumerable> roleValidators = null,
+ ILookupNormalizer keyNormalizer = null,
IdentityErrorDescriber errors = null)
{
if (store == null)
@@ -32,6 +33,7 @@ namespace Microsoft.AspNet.Identity
throw new ArgumentNullException("store");
}
Store = store;
+ KeyNormalizer = keyNormalizer ?? new UpperInvariantLookupNormalizer();
ErrorDescriber = errors ?? new IdentityErrorDescriber();
if (roleValidators != null)
@@ -58,6 +60,11 @@ namespace Microsoft.AspNet.Identity
///
public IdentityErrorDescriber ErrorDescriber { get; set; }
+ ///
+ /// Used to normalize user names, role names, emails for uniqueness
+ ///
+ public ILookupNormalizer KeyNormalizer { get; set; }
+
///
/// Returns an IQueryable of roles if the store is an IQueryableRoleStore
///
@@ -141,9 +148,24 @@ namespace Microsoft.AspNet.Identity
{
return result;
}
+ await UpdateNormalizedRoleNameAsync(role, cancellationToken);
return await Store.CreateAsync(role, cancellationToken);
}
+ ///
+ /// Update the user's normalized user name
+ ///
+ ///
+ ///
+ ///
+ public virtual async Task UpdateNormalizedRoleNameAsync(TRole role,
+ CancellationToken cancellationToken = default(CancellationToken))
+ {
+ var name = await GetRoleNameAsync(role, cancellationToken);
+ await Store.SetNormalizedRoleNameAsync(role, NormalizeKey(name), cancellationToken);
+ }
+
+
///
/// UpdateAsync an existing role
///
@@ -164,6 +186,7 @@ namespace Microsoft.AspNet.Identity
{
return result;
}
+ await UpdateNormalizedRoleNameAsync(role, cancellationToken);
return await Store.UpdateAsync(role, cancellationToken);
}
@@ -199,9 +222,20 @@ namespace Microsoft.AspNet.Identity
throw new ArgumentNullException("roleName");
}
- return await FindByNameAsync(roleName, cancellationToken) != null;
+ return await FindByNameAsync(NormalizeKey(roleName), cancellationToken) != null;
}
+ ///
+ /// Normalize a key (role name) for uniqueness comparisons
+ ///
+ ///
+ ///
+ public virtual string NormalizeKey(string key)
+ {
+ return (KeyNormalizer == null) ? key : KeyNormalizer.Normalize(key);
+ }
+
+
///
/// FindByLoginAsync a role by id
///
@@ -240,6 +274,7 @@ namespace Microsoft.AspNet.Identity
{
ThrowIfDisposed();
await Store.SetRoleNameAsync(role, name, cancellationToken);
+ await UpdateNormalizedRoleNameAsync(role, cancellationToken);
return IdentityResult.Success;
}
@@ -271,7 +306,7 @@ namespace Microsoft.AspNet.Identity
throw new ArgumentNullException("roleName");
}
- return await Store.FindByNameAsync(roleName, cancellationToken);
+ return await Store.FindByNameAsync(NormalizeKey(roleName), cancellationToken);
}
// IRoleClaimStore methods
diff --git a/src/Microsoft.AspNet.Identity/SignInManager.cs b/src/Microsoft.AspNet.Identity/SignInManager.cs
index 95ca0c1851..fb170b95e7 100644
--- a/src/Microsoft.AspNet.Identity/SignInManager.cs
+++ b/src/Microsoft.AspNet.Identity/SignInManager.cs
@@ -8,9 +8,9 @@ using System.Security.Claims;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.AspNet.Hosting;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Security;
-using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.OptionsModel;
namespace Microsoft.AspNet.Identity
@@ -22,7 +22,7 @@ namespace Microsoft.AspNet.Identity
public class SignInManager where TUser : class
{
public SignInManager(UserManager userManager,
- IContextAccessor contextAccessor,
+ IHttpContextAccessor contextAccessor,
IClaimsIdentityFactory claimsFactory,
IOptions optionsAccessor = null)
{
diff --git a/src/Microsoft.AspNet.Identity/SignInResult.cs b/src/Microsoft.AspNet.Identity/SignInResult.cs
index e125690fe7..9c88817de2 100644
--- a/src/Microsoft.AspNet.Identity/SignInResult.cs
+++ b/src/Microsoft.AspNet.Identity/SignInResult.cs
@@ -78,7 +78,5 @@ namespace Microsoft.AspNet.Identity
{
get { return _twoFactorRequired; }
}
-
-
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/UpperInvariantUserNameNormalizer.cs b/src/Microsoft.AspNet.Identity/UpperInvariantLookupNormalizer.cs
similarity index 54%
rename from src/Microsoft.AspNet.Identity/UpperInvariantUserNameNormalizer.cs
rename to src/Microsoft.AspNet.Identity/UpperInvariantLookupNormalizer.cs
index 1564ecd3d4..9bfa9454a3 100644
--- a/src/Microsoft.AspNet.Identity/UpperInvariantUserNameNormalizer.cs
+++ b/src/Microsoft.AspNet.Identity/UpperInvariantLookupNormalizer.cs
@@ -6,22 +6,22 @@ using System;
namespace Microsoft.AspNet.Identity
{
///
- /// Normalizes user names via ToUpperInvariant()
+ /// Normalizes via ToUpperInvariant()
///
- public class UpperInvariantUserNameNormalizer : IUserNameNormalizer
+ public class UpperInvariantLookupNormalizer : ILookupNormalizer
{
///
- /// Normalizes user names via ToUpperInvariant()
+ /// Normalizes via ToUpperInvariant()
///
- ///
+ ///
///
- public string Normalize(string userName)
+ public string Normalize(string key)
{
- if (userName == null)
+ if (key == null)
{
return null;
}
- return userName.Normalize().ToUpperInvariant();
+ return key.Normalize().ToUpperInvariant();
}
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/UserManager.cs b/src/Microsoft.AspNet.Identity/UserManager.cs
index d57b654505..1f8f4b663a 100644
--- a/src/Microsoft.AspNet.Identity/UserManager.cs
+++ b/src/Microsoft.AspNet.Identity/UserManager.cs
@@ -30,20 +30,23 @@ namespace Microsoft.AspNet.Identity
private IdentityOptions _options;
///
- /// Constructor which takes a service provider and user store
+ /// Constructor
///
///
///
///
- ///
- ///
- ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
public UserManager(IUserStore store,
IOptions optionsAccessor = null,
IPasswordHasher passwordHasher = null,
IEnumerable> userValidators = null,
IEnumerable> passwordValidators = null,
- IUserNameNormalizer userNameNormalizer = null,
+ ILookupNormalizer keyNormalizer = null,
IdentityErrorDescriber errors = null,
IEnumerable> tokenProviders = null,
IEnumerable msgProviders = null)
@@ -55,7 +58,7 @@ namespace Microsoft.AspNet.Identity
Store = store;
Options = optionsAccessor?.Options ?? new IdentityOptions();
PasswordHasher = passwordHasher ?? new PasswordHasher();
- UserNameNormalizer = userNameNormalizer ?? new UpperInvariantUserNameNormalizer();
+ KeyNormalizer = keyNormalizer ?? new UpperInvariantLookupNormalizer();
ErrorDescriber = errors ?? new IdentityErrorDescriber();
if (userValidators != null)
{
@@ -126,9 +129,9 @@ namespace Microsoft.AspNet.Identity
public IList> PasswordValidators { get; } = new List>();
///
- /// Used to normalize user names for uniqueness
+ /// Used to normalize user names and emails for uniqueness
///
- public IUserNameNormalizer UserNameNormalizer { get; set; }
+ public ILookupNormalizer KeyNormalizer { get; set; }
///
/// Used to generate public API error messages
@@ -353,6 +356,7 @@ namespace Microsoft.AspNet.Identity
await GetUserLockoutStore().SetLockoutEnabledAsync(user, true, cancellationToken);
}
await UpdateNormalizedUserNameAsync(user, cancellationToken);
+ await UpdateNormalizedEmailAsync(user, cancellationToken);
return await Store.CreateAsync(user, cancellationToken);
}
@@ -376,6 +380,7 @@ namespace Microsoft.AspNet.Identity
return result;
}
await UpdateNormalizedUserNameAsync(user, cancellationToken);
+ await UpdateNormalizedEmailAsync(user, cancellationToken);
return await Store.UpdateAsync(user, cancellationToken);
}
@@ -423,7 +428,7 @@ namespace Microsoft.AspNet.Identity
{
throw new ArgumentNullException("userName");
}
- userName = NormalizeUserName(userName);
+ userName = NormalizeKey(userName);
return Store.FindByNameAsync(userName, cancellationToken);
}
@@ -467,13 +472,13 @@ namespace Microsoft.AspNet.Identity
}
///
- /// Normalize a user name for uniqueness comparisons
+ /// Normalize a key (user name, email) for uniqueness comparisons
///
///
///
- public virtual string NormalizeUserName(string userName)
+ public virtual string NormalizeKey(string key)
{
- return (UserNameNormalizer == null) ? userName : UserNameNormalizer.Normalize(userName);
+ return (KeyNormalizer == null) ? key : KeyNormalizer.Normalize(key);
}
///
@@ -485,8 +490,8 @@ namespace Microsoft.AspNet.Identity
public virtual async Task UpdateNormalizedUserNameAsync(TUser user,
CancellationToken cancellationToken = default(CancellationToken))
{
- var userName = await GetUserNameAsync(user, cancellationToken);
- await Store.SetNormalizedUserNameAsync(user, NormalizeUserName(userName), cancellationToken);
+ var normalizedName = NormalizeKey(await GetUserNameAsync(user, cancellationToken));
+ await Store.SetNormalizedUserNameAsync(user, normalizedName, cancellationToken);
}
///
@@ -1215,10 +1220,10 @@ namespace Microsoft.AspNet.Identity
}
// IUserEmailStore methods
- internal IUserEmailStore GetEmailStore()
+ internal IUserEmailStore GetEmailStore(bool throwOnFail = true)
{
var cast = Store as IUserEmailStore;
- if (cast == null)
+ if (throwOnFail && cast == null)
{
throw new NotSupportedException(Resources.StoreNotIUserEmailStore);
}
@@ -1281,9 +1286,27 @@ namespace Microsoft.AspNet.Identity
{
throw new ArgumentNullException("email");
}
- return store.FindByEmailAsync(email, cancellationToken);
+ return store.FindByEmailAsync(NormalizeKey(email), cancellationToken);
}
+ ///
+ /// Update the user's normalized email
+ ///
+ ///
+ ///
+ ///
+ public virtual async Task UpdateNormalizedEmailAsync(TUser user,
+ CancellationToken cancellationToken = default(CancellationToken))
+ {
+ var store = GetEmailStore(throwOnFail: false);
+ if (store != null)
+ {
+ var email = await GetEmailAsync(user, cancellationToken);
+ await store.SetNormalizedEmailAsync(user, NormalizeKey(email), cancellationToken);
+ }
+ }
+
+
///
/// Get the confirmation token for the user
///
diff --git a/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/RoleStoreTest.cs b/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/RoleStoreTest.cs
index 2efc785d71..9299a59ceb 100644
--- a/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/RoleStoreTest.cs
+++ b/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/RoleStoreTest.cs
@@ -76,23 +76,10 @@ namespace Microsoft.AspNet.Identity.EntityFramework.InMemory.Test
var role = new IdentityRole("UpdateRoleName");
IdentityResultAssert.IsSuccess(await manager.CreateAsync(role));
Assert.Null(await manager.FindByNameAsync("New"));
- role.Name = "New";
+ IdentityResultAssert.IsSuccess(await manager.SetRoleNameAsync(role, "New"));
IdentityResultAssert.IsSuccess(await manager.UpdateAsync(role));
Assert.NotNull(await manager.FindByNameAsync("New"));
Assert.Null(await manager.FindByNameAsync("UpdateAsync"));
}
-
- [Fact]
- public async Task CanSetUserName()
- {
- var manager = TestIdentityFactory.CreateRoleManager();
- var role = new IdentityRole("UpdateRoleName");
- IdentityResultAssert.IsSuccess(await manager.CreateAsync(role));
- Assert.Null(await manager.FindByNameAsync("New"));
- IdentityResultAssert.IsSuccess(await manager.SetRoleNameAsync(role, "New"));
- Assert.NotNull(await manager.FindByNameAsync("New"));
- Assert.Null(await manager.FindByNameAsync("UpdateAsync"));
- }
-
}
}
diff --git a/test/Microsoft.AspNet.Identity.InMemory.Test/HttpSignInTest.cs b/test/Microsoft.AspNet.Identity.InMemory.Test/HttpSignInTest.cs
index 81495de872..9f922d9a3b 100644
--- a/test/Microsoft.AspNet.Identity.InMemory.Test/HttpSignInTest.cs
+++ b/test/Microsoft.AspNet.Identity.InMemory.Test/HttpSignInTest.cs
@@ -4,6 +4,7 @@
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Builder;
+using Microsoft.AspNet.Hosting;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Security;
using Microsoft.AspNet.Identity.Test;
@@ -30,7 +31,7 @@ namespace Microsoft.AspNet.Identity.InMemory.Test
var response = new Mock();
context.Setup(c => c.Response).Returns(response.Object).Verifiable();
response.Setup(r => r.SignIn(It.Is(v => v.IsPersistent == isPersistent), It.IsAny())).Verifiable();
- var contextAccessor = new Mock>();
+ var contextAccessor = new Mock();
contextAccessor.Setup(a => a.Value).Returns(context.Object);
app.UseServices(services =>
{
diff --git a/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryRoleStore.cs b/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryRoleStore.cs
index 352311b56c..0f250c4615 100644
--- a/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryRoleStore.cs
+++ b/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryRoleStore.cs
@@ -97,6 +97,17 @@ namespace Microsoft.AspNet.Identity.InMemory
return Task.FromResult(0);
}
+ public Task GetNormalizedRoleNameAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ return Task.FromResult(role.NormalizedName);
+ }
+
+ public Task SetNormalizedRoleNameAsync(TRole role, string normalizedName, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ role.NormalizedName = normalizedName;
+ return Task.FromResult(0);
+ }
+
public IQueryable Roles
{
get { return _roles.Values.AsQueryable(); }
diff --git a/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryUserStore.cs b/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryUserStore.cs
index 8d04d1ae4a..ab3521f6b4 100644
--- a/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryUserStore.cs
+++ b/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryUserStore.cs
@@ -108,6 +108,18 @@ namespace Microsoft.AspNet.Identity.InMemory
return Task.FromResult(user.Email);
}
+ public Task GetNormalizedEmailAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ return Task.FromResult(user.NormalizedEmail);
+ }
+
+ public Task SetNormalizedEmailAsync(TUser user, string normalizedEmail, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ user.NormalizedEmail = normalizedEmail;
+ return Task.FromResult(0);
+ }
+
+
public Task GetEmailConfirmedAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult(user.EmailConfirmed);
@@ -123,7 +135,7 @@ namespace Microsoft.AspNet.Identity.InMemory
{
return
Task.FromResult(
- Users.FirstOrDefault(u => String.Equals(u.Email, email, StringComparison.OrdinalIgnoreCase)));
+ Users.FirstOrDefault(u => u.NormalizedEmail == email));
}
public Task GetLockoutEndDateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
@@ -262,7 +274,7 @@ namespace Microsoft.AspNet.Identity.InMemory
{
return
Task.FromResult(
- Users.FirstOrDefault(u => String.Equals(u.UserName, userName, StringComparison.OrdinalIgnoreCase)));
+ Users.FirstOrDefault(u => u.NormalizedUserName == userName));
}
public Task DeleteAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
diff --git a/test/Microsoft.AspNet.Identity.Test/IHttpContextAccessor.cs b/test/Microsoft.AspNet.Identity.Test/IHttpContextAccessor.cs
new file mode 100644
index 0000000000..b812ed6e8b
--- /dev/null
+++ b/test/Microsoft.AspNet.Identity.Test/IHttpContextAccessor.cs
@@ -0,0 +1,6 @@
+namespace Microsoft.AspNet.Identity.Test
+{
+ internal interface IHttpContextAccessor
+ {
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs b/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs
index 01c7f8281e..488fcc7545 100644
--- a/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs
+++ b/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs
@@ -148,6 +148,11 @@ namespace Microsoft.AspNet.Identity.Test
throw new NotImplementedException();
}
+ public Task GetNormalizedRoleNameAsync(IdentityRole role, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ throw new NotImplementedException();
+ }
+
public Task GetNormalizedUserNameAsync(IdentityUser user, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
@@ -173,6 +178,11 @@ namespace Microsoft.AspNet.Identity.Test
throw new NotImplementedException();
}
+ public Task SetNormalizedRoleNameAsync(IdentityRole role, string normalizedName, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ throw new NotImplementedException();
+ }
+
public Task SetNormalizedUserNameAsync(IdentityUser user, string normalizedName, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
diff --git a/test/Microsoft.AspNet.Identity.Test/NoopRoleStore.cs b/test/Microsoft.AspNet.Identity.Test/NoopRoleStore.cs
index a3c576ca51..4d62b972bf 100644
--- a/test/Microsoft.AspNet.Identity.Test/NoopRoleStore.cs
+++ b/test/Microsoft.AspNet.Identity.Test/NoopRoleStore.cs
@@ -51,5 +51,15 @@ namespace Microsoft.AspNet.Identity.Test
{
return Task.FromResult(null);
}
+
+ public Task GetNormalizedRoleNameAsync(TestRole role, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ return Task.FromResult(null);
+ }
+
+ public Task SetNormalizedRoleNameAsync(TestRole role, string normalizedName, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ return Task.FromResult(0);
+ }
}
}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Identity.Test/RoleManagerTest.cs b/test/Microsoft.AspNet.Identity.Test/RoleManagerTest.cs
index d57c4f2c25..cab11a83df 100644
--- a/test/Microsoft.AspNet.Identity.Test/RoleManagerTest.cs
+++ b/test/Microsoft.AspNet.Identity.Test/RoleManagerTest.cs
@@ -6,12 +6,51 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using Moq;
using Xunit;
namespace Microsoft.AspNet.Identity.Test
{
public class RoleManagerTest
{
+ [Fact]
+ public async Task CreateCallsStore()
+ {
+ // Setup
+ var store = new Mock>();
+ var role = new TestRole { Name = "Foo" };
+ store.Setup(s => s.CreateAsync(role, CancellationToken.None)).ReturnsAsync(IdentityResult.Success).Verifiable();
+ store.Setup(s => s.GetRoleNameAsync(role, CancellationToken.None)).Returns(Task.FromResult(role.Name)).Verifiable();
+ store.Setup(s => s.SetNormalizedRoleNameAsync(role, role.Name.ToUpperInvariant(), CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable();
+ var roleManager = MockHelpers.TestRoleManager(store.Object);
+
+ // Act
+ var result = await roleManager.CreateAsync(role);
+
+ // Assert
+ Assert.True(result.Succeeded);
+ store.VerifyAll();
+ }
+
+ [Fact]
+ public async Task UpdateCallsStore()
+ {
+ // Setup
+ var store = new Mock>();
+ var role = new TestRole { Name = "Foo" };
+ store.Setup(s => s.UpdateAsync(role, CancellationToken.None)).ReturnsAsync(IdentityResult.Success).Verifiable();
+ store.Setup(s => s.GetRoleNameAsync(role, CancellationToken.None)).Returns(Task.FromResult(role.Name)).Verifiable();
+ store.Setup(s => s.SetNormalizedRoleNameAsync(role, role.Name.ToUpperInvariant(), CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable();
+ var roleManager = MockHelpers.TestRoleManager(store.Object);
+
+ // Act
+ var result = await roleManager.UpdateAsync(role);
+
+ // Assert
+ Assert.True(result.Succeeded);
+ store.VerifyAll();
+ }
+
[Fact]
public void RolesQueryableFailWhenStoreNotImplemented()
{
@@ -20,6 +59,59 @@ namespace Microsoft.AspNet.Identity.Test
Assert.Throws(() => manager.Roles.Count());
}
+ [Fact]
+ public async Task FindByNameCallsStoreWithNormalizedName()
+ {
+ // Setup
+ var store = new Mock>();
+ var role = new TestRole { Name = "Foo" };
+ store.Setup(s => s.FindByNameAsync("FOO", CancellationToken.None)).Returns(Task.FromResult(role)).Verifiable();
+ var manager = MockHelpers.TestRoleManager(store.Object);
+
+ // Act
+ var result = await manager.FindByNameAsync(role.Name);
+
+ // Assert
+ Assert.Equal(role, result);
+ store.VerifyAll();
+ }
+
+ [Fact]
+ public async Task CanFindByNameCallsStoreWithoutNormalizedName()
+ {
+ // Setup
+ var store = new Mock>();
+ var role = new TestRole { Name = "Foo" };
+ store.Setup(s => s.FindByNameAsync(role.Name, CancellationToken.None)).Returns(Task.FromResult(role)).Verifiable();
+ var manager = MockHelpers.TestRoleManager(store.Object);
+ manager.KeyNormalizer = null;
+
+ // Act
+ var result = await manager.FindByNameAsync(role.Name);
+
+ // Assert
+ Assert.Equal(role, result);
+ store.VerifyAll();
+ }
+
+ [Fact]
+ public async Task RoleExistsCallsStoreWithNormalizedName()
+ {
+ // Setup
+ var store = new Mock>();
+ var role = new TestRole { Name = "Foo" };
+ store.Setup(s => s.FindByNameAsync("FOO", CancellationToken.None)).Returns(Task.FromResult(role)).Verifiable();
+ var manager = MockHelpers.TestRoleManager(store.Object);
+
+ // Act
+ var result = await manager.RoleExistsAsync(role.Name);
+
+ // Assert
+ Assert.True(result);
+ store.VerifyAll();
+ }
+
+
[Fact]
public void DisposeAfterDisposeDoesNotThrow()
{
@@ -107,6 +199,16 @@ namespace Microsoft.AspNet.Identity.Test
{
throw new NotImplementedException();
}
+
+ public Task GetNormalizedRoleNameAsync(TestRole role, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task SetNormalizedRoleNameAsync(TestRole role, string normalizedName, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ throw new NotImplementedException();
+ }
}
}
}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Identity.Test/SecurityStampValidatorTest.cs b/test/Microsoft.AspNet.Identity.Test/SecurityStampValidatorTest.cs
index 5ae623e01d..1528665dde 100644
--- a/test/Microsoft.AspNet.Identity.Test/SecurityStampValidatorTest.cs
+++ b/test/Microsoft.AspNet.Identity.Test/SecurityStampValidatorTest.cs
@@ -5,6 +5,7 @@ using System;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.AspNet.Hosting;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Security;
using Microsoft.AspNet.Security;
@@ -43,7 +44,7 @@ namespace Microsoft.AspNet.Identity.Test
var options = new Mock>();
options.Setup(a => a.Options).Returns(identityOptions);
var httpContext = new Mock();
- var contextAccessor = new Mock>();
+ var contextAccessor = new Mock();
contextAccessor.Setup(a => a.Value).Returns(httpContext.Object);
var signInManager = new Mock>(userManager.Object,
contextAccessor.Object, claimsManager.Object, options.Object);
@@ -78,7 +79,7 @@ namespace Microsoft.AspNet.Identity.Test
var options = new Mock>();
options.Setup(a => a.Options).Returns(identityOptions);
var httpContext = new Mock();
- var contextAccessor = new Mock>();
+ var contextAccessor = new Mock();
contextAccessor.Setup(a => a.Value).Returns(httpContext.Object);
var signInManager = new Mock>(userManager.Object,
contextAccessor.Object, claimsManager.Object, options.Object);
@@ -112,7 +113,7 @@ namespace Microsoft.AspNet.Identity.Test
var identityOptions = new IdentityOptions { SecurityStampValidationInterval = TimeSpan.Zero };
var options = new Mock>();
options.Setup(a => a.Options).Returns(identityOptions);
- var contextAccessor = new Mock>();
+ var contextAccessor = new Mock();
contextAccessor.Setup(a => a.Value).Returns(httpContext.Object);
var signInManager = new Mock>(userManager.Object,
contextAccessor.Object, claimsManager.Object, options.Object);
@@ -146,7 +147,7 @@ namespace Microsoft.AspNet.Identity.Test
var identityOptions = new IdentityOptions { SecurityStampValidationInterval = TimeSpan.FromDays(1) };
var options = new Mock>();
options.Setup(a => a.Options).Returns(identityOptions);
- var contextAccessor = new Mock>();
+ var contextAccessor = new Mock();
contextAccessor.Setup(a => a.Value).Returns(httpContext.Object);
var signInManager = new Mock>(userManager.Object,
contextAccessor.Object, claimsManager.Object, options.Object);
diff --git a/test/Microsoft.AspNet.Identity.Test/SignInManagerTest.cs b/test/Microsoft.AspNet.Identity.Test/SignInManagerTest.cs
index 60876733cc..7d1d1d6c90 100644
--- a/test/Microsoft.AspNet.Identity.Test/SignInManagerTest.cs
+++ b/test/Microsoft.AspNet.Identity.Test/SignInManagerTest.cs
@@ -7,6 +7,7 @@ using System.Security.Claims;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.AspNet.Hosting;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Security;
using Microsoft.Framework.DependencyInjection;
@@ -31,7 +32,7 @@ namespace Microsoft.AspNet.Identity.Test
// TODO: how to functionally test context?
// var context = new DefaultHttpContext(new FeatureCollection());
- // var contextAccessor = new Mock>();
+ // var contextAccessor = new Mock();
// contextAccessor.Setup(a => a.Value).Returns(context);
// app.UseServices(services =>
// {
@@ -70,7 +71,7 @@ namespace Microsoft.AspNet.Identity.Test
Assert.Throws("userManager", () => new SignInManager(null, null, null, null));
var userManager = MockHelpers.MockUserManager().Object;
Assert.Throws("contextAccessor", () => new SignInManager(userManager, null, null, null));
- var contextAccessor = new Mock>();
+ var contextAccessor = new Mock();
Assert.Throws("contextAccessor", () => new SignInManager(userManager, contextAccessor.Object, null, null));
var context = new Mock();
contextAccessor.Setup(a => a.Value).Returns(context.Object);
@@ -93,7 +94,7 @@ namespace Microsoft.AspNet.Identity.Test
// var response = new Mock();
// context.Setup(c => c.Response).Returns(response.Object).Verifiable();
// response.Setup(r => r.SignIn(testIdentity, It.IsAny())).Verifiable();
- // var contextAccessor = new Mock>();
+ // var contextAccessor = new Mock();
// contextAccessor.Setup(a => a.Value).Returns(context.Object);
// var helper = new HttpAuthenticationManager(contextAccessor.Object);
@@ -117,7 +118,7 @@ namespace Microsoft.AspNet.Identity.Test
manager.Setup(m => m.IsLockedOutAsync(user, CancellationToken.None)).ReturnsAsync(true).Verifiable();
manager.Setup(m => m.FindByNameAsync(user.UserName, CancellationToken.None)).ReturnsAsync(user).Verifiable();
var context = new Mock();
- var contextAccessor = new Mock>();
+ var contextAccessor = new Mock();
contextAccessor.Setup(a => a.Value).Returns(context.Object);
var roleManager = MockHelpers.MockRoleManager();
var identityOptions = new IdentityOptions();
@@ -152,7 +153,7 @@ namespace Microsoft.AspNet.Identity.Test
var response = new Mock();
context.Setup(c => c.Response).Returns(response.Object).Verifiable();
response.Setup(r => r.SignIn(It.Is(v => v.IsPersistent == isPersistent), It.IsAny())).Verifiable();
- var contextAccessor = new Mock>();
+ var contextAccessor = new Mock();
contextAccessor.Setup(a => a.Value).Returns(context.Object);
var roleManager = MockHelpers.MockRoleManager();
var identityOptions = new IdentityOptions();
@@ -189,7 +190,7 @@ namespace Microsoft.AspNet.Identity.Test
var response = new Mock();
response.Setup(r => r.SignIn(It.IsAny(), It.IsAny())).Verifiable();
context.Setup(c => c.Response).Returns(response.Object).Verifiable();
- var contextAccessor = new Mock>();
+ var contextAccessor = new Mock();
contextAccessor.Setup(a => a.Value).Returns(context.Object);
var roleManager = MockHelpers.MockRoleManager();
var identityOptions = new IdentityOptions();
@@ -238,7 +239,7 @@ namespace Microsoft.AspNet.Identity.Test
var response = new Mock();
response.Setup(r => r.SignIn(It.Is(id => id.Name == user.Id))).Verifiable();
context.Setup(c => c.Response).Returns(response.Object).Verifiable();
- var contextAccessor = new Mock>();
+ var contextAccessor = new Mock();
contextAccessor.Setup(a => a.Value).Returns(context.Object);
var roleManager = MockHelpers.MockRoleManager();
var identityOptions = new IdentityOptions();
@@ -282,7 +283,7 @@ namespace Microsoft.AspNet.Identity.Test
response.Setup(r => r.SignIn(
It.Is(v => v.IsPersistent == isPersistent),
It.Is(i => i.FindFirstValue(ClaimTypes.AuthenticationMethod) == loginProvider))).Verifiable();
- var contextAccessor = new Mock>();
+ var contextAccessor = new Mock();
contextAccessor.Setup(a => a.Value).Returns(context.Object);
var roleManager = MockHelpers.MockRoleManager();
var identityOptions = new IdentityOptions();
@@ -342,7 +343,7 @@ namespace Microsoft.AspNet.Identity.Test
manager.Setup(m => m.GetUserNameAsync(user, CancellationToken.None)).ReturnsAsync(user.UserName).Verifiable();
var context = new Mock();
var response = new Mock();
- var contextAccessor = new Mock>();
+ var contextAccessor = new Mock();
var twoFactorInfo = new SignInManager.TwoFactorAuthenticationInfo { UserId = user.Id };
var loginProvider = "loginprovider";
var id = SignInManager.StoreTwoFactorInfo(user.Id, externalLogin ? loginProvider : null);
@@ -397,7 +398,7 @@ namespace Microsoft.AspNet.Identity.Test
var manager = MockHelpers.MockUserManager();
var context = new Mock();
var response = new Mock();
- var contextAccessor = new Mock>();
+ var contextAccessor = new Mock();
var roleManager = MockHelpers.MockRoleManager();
var identityOptions = new IdentityOptions();
var options = new Mock>();
@@ -452,7 +453,7 @@ namespace Microsoft.AspNet.Identity.Test
id.AddClaim(new Claim(ClaimTypes.Name, user.Id));
var authResult = new AuthenticationResult(id, new AuthenticationProperties(), new AuthenticationDescription());
context.Setup(c => c.AuthenticateAsync(IdentityOptions.TwoFactorRememberMeCookieAuthenticationType)).ReturnsAsync(authResult).Verifiable();
- var contextAccessor = new Mock>();
+ var contextAccessor = new Mock();
contextAccessor.Setup(a => a.Value).Returns(context.Object);
var roleManager = MockHelpers.MockRoleManager();
var identityOptions = new IdentityOptions();
@@ -487,7 +488,7 @@ namespace Microsoft.AspNet.Identity.Test
response.Setup(r => r.SignOut(authenticationType)).Verifiable();
response.Setup(r => r.SignOut(IdentityOptions.TwoFactorUserIdCookieAuthenticationType)).Verifiable();
response.Setup(r => r.SignOut(IdentityOptions.ExternalCookieAuthenticationType)).Verifiable();
- var contextAccessor = new Mock>();
+ var contextAccessor = new Mock();
contextAccessor.Setup(a => a.Value).Returns(context.Object);
var roleManager = MockHelpers.MockRoleManager();
var identityOptions = new IdentityOptions();
@@ -518,7 +519,7 @@ namespace Microsoft.AspNet.Identity.Test
manager.Setup(m => m.FindByNameAsync(user.UserName, CancellationToken.None)).ReturnsAsync(user).Verifiable();
manager.Setup(m => m.CheckPasswordAsync(user, "bogus", CancellationToken.None)).ReturnsAsync(false).Verifiable();
var context = new Mock();
- var contextAccessor = new Mock>();
+ var contextAccessor = new Mock();
contextAccessor.Setup(a => a.Value).Returns(context.Object);
var roleManager = MockHelpers.MockRoleManager();
var identityOptions = new IdentityOptions();
@@ -543,7 +544,7 @@ namespace Microsoft.AspNet.Identity.Test
var manager = MockHelpers.MockUserManager();
manager.Setup(m => m.FindByNameAsync("bogus", CancellationToken.None)).ReturnsAsync(null).Verifiable();
var context = new Mock();
- var contextAccessor = new Mock>();
+ var contextAccessor = new Mock();
contextAccessor.Setup(a => a.Value).Returns(context.Object);
var roleManager = MockHelpers.MockRoleManager();
var identityOptions = new IdentityOptions();
@@ -579,7 +580,7 @@ namespace Microsoft.AspNet.Identity.Test
manager.Setup(m => m.FindByNameAsync(user.UserName, CancellationToken.None)).ReturnsAsync(user).Verifiable();
manager.Setup(m => m.CheckPasswordAsync(user, "bogus", CancellationToken.None)).ReturnsAsync(false).Verifiable();
var context = new Mock();
- var contextAccessor = new Mock>();
+ var contextAccessor = new Mock();
contextAccessor.Setup(a => a.Value).Returns(context.Object);
var roleManager = MockHelpers.MockRoleManager();
var identityOptions = new IdentityOptions();
@@ -618,7 +619,7 @@ namespace Microsoft.AspNet.Identity.Test
context.Setup(c => c.Response).Returns(response.Object).Verifiable();
response.Setup(r => r.SignIn(It.Is(v => v.IsPersistent == false), It.IsAny())).Verifiable();
}
- var contextAccessor = new Mock>();
+ var contextAccessor = new Mock();
contextAccessor.Setup(a => a.Value).Returns(context.Object);
var roleManager = MockHelpers.MockRoleManager();
var identityOptions = new IdentityOptions();
@@ -659,7 +660,7 @@ namespace Microsoft.AspNet.Identity.Test
response.Setup(r => r.SignIn(It.Is(v => v.IsPersistent == false), It.IsAny())).Verifiable();
}
- var contextAccessor = new Mock>();
+ var contextAccessor = new Mock();
contextAccessor.Setup(a => a.Value).Returns(context.Object);
var roleManager = MockHelpers.MockRoleManager();
var identityOptions = new IdentityOptions();
diff --git a/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs b/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs
index cb3530fb25..b340caa63f 100644
--- a/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs
+++ b/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs
@@ -43,6 +43,29 @@ namespace Microsoft.AspNet.Identity.Test
var store = new Mock>();
var user = new TestUser { UserName = "Foo" };
store.Setup(s => s.CreateAsync(user, CancellationToken.None)).ReturnsAsync(IdentityResult.Success).Verifiable();
+ store.Setup(s => s.GetUserNameAsync(user, CancellationToken.None)).Returns(Task.FromResult(user.UserName)).Verifiable();
+ store.Setup(s => s.SetNormalizedUserNameAsync(user, user.UserName.ToUpperInvariant(), CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable();
+ var userManager = MockHelpers.TestUserManager(store.Object);
+
+ // Act
+ var result = await userManager.CreateAsync(user);
+
+ // Assert
+ Assert.True(result.Succeeded);
+ store.VerifyAll();
+ }
+
+ [Fact]
+ public async Task CreateCallsUpdateEmailStore()
+ {
+ // Setup
+ var store = new Mock>();
+ var user = new TestUser { UserName = "Foo", Email = "Foo@foo.com" };
+ store.Setup(s => s.CreateAsync(user, CancellationToken.None)).ReturnsAsync(IdentityResult.Success).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();
+ store.Setup(s => s.SetNormalizedEmailAsync(user, user.Email.ToUpperInvariant(), CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable();
+ store.Setup(s => s.SetNormalizedUserNameAsync(user, user.UserName.ToUpperInvariant(), CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable();
var userManager = MockHelpers.TestUserManager(store.Object);
// Act
@@ -76,6 +99,29 @@ namespace Microsoft.AspNet.Identity.Test
// Setup
var store = new Mock>();
var user = new TestUser { UserName = "Foo" };
+ store.Setup(s => s.GetUserNameAsync(user, CancellationToken.None)).Returns(Task.FromResult(user.UserName)).Verifiable();
+ store.Setup(s => s.SetNormalizedUserNameAsync(user, user.UserName.ToUpperInvariant(), CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable();
+ store.Setup(s => s.UpdateAsync(user, CancellationToken.None)).ReturnsAsync(IdentityResult.Success).Verifiable();
+ var userManager = MockHelpers.TestUserManager(store.Object);
+
+ // Act
+ var result = await userManager.UpdateAsync(user);
+
+ // Assert
+ Assert.True(result.Succeeded);
+ store.VerifyAll();
+ }
+
+ [Fact]
+ public async Task UpdateWillUpdateNormalizedEmail()
+ {
+ // Setup
+ var store = new Mock>();
+ var user = new TestUser { UserName = "Foo", Email = "email" };
+ 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();
+ store.Setup(s => s.SetNormalizedUserNameAsync(user, user.UserName.ToUpperInvariant(), CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable();
+ store.Setup(s => s.SetNormalizedEmailAsync(user, user.Email.ToUpperInvariant(), CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable();
store.Setup(s => s.UpdateAsync(user, CancellationToken.None)).ReturnsAsync(IdentityResult.Success).Verifiable();
var userManager = MockHelpers.TestUserManager(store.Object);
@@ -93,7 +139,9 @@ namespace Microsoft.AspNet.Identity.Test
// Setup
var store = new Mock>();
var user = new TestUser();
- store.Setup(s => s.SetUserNameAsync(user, It.IsAny(), CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable();
+ store.Setup(s => s.SetUserNameAsync(user, "foo", CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable();
+ store.Setup(s => s.GetUserNameAsync(user, CancellationToken.None)).Returns(Task.FromResult("foo")).Verifiable();
+ store.Setup(s => s.SetNormalizedUserNameAsync(user, "FOO", CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable();
store.Setup(s => s.UpdateAsync(user, CancellationToken.None)).Returns(Task.FromResult(IdentityResult.Success)).Verifiable();
var userManager = MockHelpers.TestUserManager(store.Object);
@@ -146,8 +194,8 @@ namespace Microsoft.AspNet.Identity.Test
var store = new Mock>();
var user = new TestUser {UserName="Foo"};
store.Setup(s => s.FindByNameAsync(user.UserName, CancellationToken.None)).Returns(Task.FromResult(user)).Verifiable();
- var userManager = MockHelpers.TestUserManager(store.Object);
- userManager.UserNameNormalizer = null;
+ var userManager = MockHelpers.TestUserManager(store.Object);
+ userManager.KeyNormalizer = null;
// Act
var result = await userManager.FindByNameAsync(user.UserName);
@@ -157,6 +205,41 @@ namespace Microsoft.AspNet.Identity.Test
store.VerifyAll();
}
+ [Fact]
+ public async Task FindByEmailCallsStoreWithNormalizedEmail()
+ {
+ // Setup
+ var store = new Mock>();
+ var user = new TestUser { Email = "Foo" };
+ store.Setup(s => s.FindByEmailAsync(user.Email.ToUpperInvariant(), CancellationToken.None)).Returns(Task.FromResult(user)).Verifiable();
+ var userManager = MockHelpers.TestUserManager(store.Object);
+
+ // Act
+ var result = await userManager.FindByEmailAsync(user.Email);
+
+ // Assert
+ Assert.Equal(user, result);
+ store.VerifyAll();
+ }
+
+ [Fact]
+ public async Task CanFindByEmailCallsStoreWithoutNormalizedEmail()
+ {
+ // Setup
+ var store = new Mock>();
+ var user = new TestUser { Email = "Foo" };
+ store.Setup(s => s.FindByEmailAsync(user.Email, CancellationToken.None)).Returns(Task.FromResult(user)).Verifiable();
+ var userManager = MockHelpers.TestUserManager(store.Object);
+ userManager.KeyNormalizer = null;
+
+ // Act
+ var result = await userManager.FindByEmailAsync(user.Email);
+
+ // Assert
+ Assert.Equal(user, result);
+ store.VerifyAll();
+ }
+
[Fact]
public async Task AddToRolesCallsStore()
{
@@ -961,6 +1044,16 @@ namespace Microsoft.AspNet.Identity.Test
{
return Task.FromResult>(new List());
}
+
+ public Task GetNormalizedEmailAsync(TestUser user, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ return Task.FromResult("");
+ }
+
+ public Task SetNormalizedEmailAsync(TestUser user, string normalizedEmail, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ return Task.FromResult(0);
+ }
}
private class NoOpTokenProvider : IUserTokenProvider
@@ -1228,6 +1321,16 @@ namespace Microsoft.AspNet.Identity.Test
{
throw new NotImplementedException();
}
+
+ public Task GetNormalizedEmailAsync(TestUser user, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task SetNormalizedEmailAsync(TestUser user, string normalizedEmail, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ throw new NotImplementedException();
+ }
}
[Fact]
@@ -1245,7 +1348,7 @@ namespace Microsoft.AspNet.Identity.Test
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))
+ store.Setup(s => s.FindByEmailAsync("DUPE@EMAIL.COM", CancellationToken.None))
.Returns(Task.FromResult(user2))
.Verifiable();
store.Setup(s => s.GetUserIdAsync(user2, CancellationToken.None))
diff --git a/test/Shared/MockHelpers.cs b/test/Shared/MockHelpers.cs
index 4a73bba084..f70541bec0 100644
--- a/test/Shared/MockHelpers.cs
+++ b/test/Shared/MockHelpers.cs
@@ -20,21 +20,17 @@ namespace Microsoft.AspNet.Identity.Test
return mgr;
}
- public static Mock> MockRoleManager() where TRole : class
+ public static Mock> MockRoleManager(IRoleStore store = null) where TRole : class
{
- var store = new Mock>();
+ store = store ?? new Mock>().Object;
var roles = new List>();
roles.Add(new RoleValidator());
- return new Mock>(store.Object, roles, null);
+ return new Mock>(store, roles, null, null);
}
- public static UserManager TestUserManager() where TUser : class
- {
- return TestUserManager(new Mock>().Object);
- }
-
- public static UserManager TestUserManager(IUserStore store) where TUser : class
+ public static UserManager TestUserManager(IUserStore store = null) where TUser : class
{
+ store = store ?? new Mock>().Object;
var validator = new Mock>();
var userManager = new UserManager(store);
userManager.UserValidators.Add(validator.Object);
@@ -43,5 +39,14 @@ namespace Microsoft.AspNet.Identity.Test
.Returns(Task.FromResult(IdentityResult.Success)).Verifiable();
return userManager;
}
+
+ public static RoleManager TestRoleManager(IRoleStore store = null) where TRole : class
+ {
+ store = store ?? new Mock>().Object;
+ var roles = new List>();
+ roles.Add(new RoleValidator());
+ return new RoleManager(store, roles);
+ }
+
}
}
\ No newline at end of file
diff --git a/test/Shared/UserManagerTestBase.cs b/test/Shared/UserManagerTestBase.cs
index 2b27a389b1..1be1741852 100644
--- a/test/Shared/UserManagerTestBase.cs
+++ b/test/Shared/UserManagerTestBase.cs
@@ -1016,10 +1016,10 @@ namespace Microsoft.AspNet.Identity.Test
Assert.False(await manager.RoleExistsAsync(role.Name));
IdentityResultAssert.IsSuccess(await manager.CreateAsync(role));
Assert.True(await manager.RoleExistsAsync(role.Name));
- role.Name = "Changed";
+ IdentityResultAssert.IsSuccess(await manager.SetRoleNameAsync(role, "Changed"));
IdentityResultAssert.IsSuccess(await manager.UpdateAsync(role));
Assert.False(await manager.RoleExistsAsync("update"));
- Assert.Equal(role, await manager.FindByNameAsync(role.Name));
+ Assert.Equal(role, await manager.FindByNameAsync("Changed"));
}
[Fact]