Split ILookupNormalizer.Normalize into Name/Email methods (#8412)

This commit is contained in:
Hao Kung 2019-03-15 14:00:40 -07:00 committed by GitHub
parent 22623b905f
commit 95ab2fa4af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 146 additions and 43 deletions

View File

@ -109,7 +109,8 @@ namespace Microsoft.AspNetCore.Identity
}
public partial interface ILookupNormalizer
{
string Normalize(string key);
string NormalizeEmail(string email);
string NormalizeName(string name);
}
public partial interface ILookupProtector
{
@ -450,10 +451,11 @@ namespace Microsoft.AspNetCore.Identity
[System.Diagnostics.DebuggerStepThroughAttribute]
public virtual System.Threading.Tasks.Task<bool> ValidateAsync(string purpose, string token, Microsoft.AspNetCore.Identity.UserManager<TUser> manager, TUser user) { throw null; }
}
public partial class UpperInvariantLookupNormalizer : Microsoft.AspNetCore.Identity.ILookupNormalizer
public sealed partial class UpperInvariantLookupNormalizer : Microsoft.AspNetCore.Identity.ILookupNormalizer
{
public UpperInvariantLookupNormalizer() { }
public virtual string Normalize(string key) { throw null; }
public string NormalizeEmail(string email) { throw null; }
public string NormalizeName(string name) { throw null; }
}
public partial class UserClaimsPrincipalFactory<TUser> : Microsoft.AspNetCore.Identity.IUserClaimsPrincipalFactory<TUser> where TUser : class
{
@ -600,7 +602,8 @@ namespace Microsoft.AspNetCore.Identity
[System.Diagnostics.DebuggerStepThroughAttribute]
public virtual System.Threading.Tasks.Task<bool> IsLockedOutAsync(TUser user) { throw null; }
public virtual System.Threading.Tasks.Task<bool> IsPhoneNumberConfirmedAsync(TUser user) { throw null; }
public virtual string NormalizeKey(string key) { throw null; }
public virtual string NormalizeEmail(string email) { throw null; }
public virtual string NormalizeName(string name) { throw null; }
[System.Diagnostics.DebuggerStepThroughAttribute]
public virtual System.Threading.Tasks.Task<Microsoft.AspNetCore.Identity.IdentityResult> RedeemTwoFactorRecoveryCodeAsync(TUser user, string code) { throw null; }
public virtual void RegisterTokenProvider(string providerName, Microsoft.AspNetCore.Identity.IUserTwoFactorTokenProvider<TUser> provider) { }

View File

@ -4,15 +4,23 @@
namespace Microsoft.AspNetCore.Identity
{
/// <summary>
/// Provides an abstraction for normalizing keys for lookup purposes.
/// Provides an abstraction for normalizing keys (emails/names) for lookup purposes.
/// </summary>
public interface ILookupNormalizer
{
/// <summary>
/// Returns a normalized representation of the specified <paramref name="key"/>.
/// Returns a normalized representation of the specified <paramref name="name"/>.
/// </summary>
/// <param name="key">The key to normalize.</param>
/// <returns>A normalized representation of the specified <paramref name="key"/>.</returns>
string Normalize(string key);
/// <param name="name">The key to normalize.</param>
/// <returns>A normalized representation of the specified <paramref name="name"/>.</returns>
string NormalizeName(string name);
/// <summary>
/// Returns a normalized representation of the specified <paramref name="email"/>.
/// </summary>
/// <param name="email">The email to normalize.</param>
/// <returns>A normalized representation of the specified <paramref name="email"/>.</returns>
string NormalizeEmail(string email);
}
}
}

View File

@ -244,7 +244,7 @@ namespace Microsoft.AspNetCore.Identity
/// <returns>A normalized representation of the specified <paramref name="key"/>.</returns>
public virtual string NormalizeKey(string key)
{
return (KeyNormalizer == null) ? key : KeyNormalizer.Normalize(key);
return (KeyNormalizer == null) ? key : KeyNormalizer.NormalizeName(key);
}
/// <summary>

View File

@ -6,21 +6,27 @@ namespace Microsoft.AspNetCore.Identity
/// <summary>
/// Implements <see cref="ILookupNormalizer"/> by converting keys to their upper cased invariant culture representation.
/// </summary>
public class UpperInvariantLookupNormalizer : ILookupNormalizer
public sealed class UpperInvariantLookupNormalizer : ILookupNormalizer
{
/// <summary>
/// Returns a normalized representation of the specified <paramref name="key"/>
/// by converting keys to their upper cased invariant culture representation.
/// Returns a normalized representation of the specified <paramref name="name"/>.
/// </summary>
/// <param name="key">The key to normalize.</param>
/// <returns>A normalized representation of the specified <paramref name="key"/>.</returns>
public virtual string Normalize(string key)
/// <param name="name">The key to normalize.</param>
/// <returns>A normalized representation of the specified <paramref name="name"/>.</returns>
public string NormalizeName(string name)
{
if (key == null)
if (name == null)
{
return null;
}
return key.Normalize().ToUpperInvariant();
return name.Normalize().ToUpperInvariant();
}
/// <summary>
/// Returns a normalized representation of the specified <paramref name="email"/>.
/// </summary>
/// <param name="email">The email to normalize.</param>
/// <returns>A normalized representation of the specified <paramref name="email"/>.</returns>
public string NormalizeEmail(string email) => NormalizeName(email);
}
}

View File

@ -105,15 +105,15 @@ namespace Microsoft.AspNetCore.Identity
foreach (var providerName in Options.Tokens.ProviderMap.Keys)
{
var description = Options.Tokens.ProviderMap[providerName];
var provider = (description.ProviderInstance ?? services.GetRequiredService(description.ProviderType))
var provider = (description.ProviderInstance ?? services.GetRequiredService(description.ProviderType))
as IUserTwoFactorTokenProvider<TUser>;
if (provider != null)
{
RegisterTokenProvider(providerName, provider);
}
}
}
}
if (Options.Stores.ProtectPersonalData)
{
@ -161,7 +161,7 @@ namespace Microsoft.AspNetCore.Identity
/// The <see cref="ILookupNormalizer"/> used to normalize things like user and role names.
/// </summary>
public ILookupNormalizer KeyNormalizer { get; set; }
/// <summary>
/// The <see cref="IdentityErrorDescriber"/> used to generate error messages.
/// </summary>
@ -547,7 +547,7 @@ namespace Microsoft.AspNetCore.Identity
{
throw new ArgumentNullException(nameof(userName));
}
userName = NormalizeKey(userName);
userName = NormalizeName(userName);
var user = await Store.FindByNameAsync(userName, CancellationToken);
@ -603,15 +603,21 @@ namespace Microsoft.AspNetCore.Identity
}
/// <summary>
/// Normalize a key (user name, email) for consistent comparisons.
/// Normalize user or role name for consistent comparisons.
/// </summary>
/// <param name="key">The key to normalize.</param>
/// <returns>A normalized value representing the specified <paramref name="key"/>.</returns>
public virtual string NormalizeKey(string key)
{
return (KeyNormalizer == null) ? key : KeyNormalizer.Normalize(key);
}
/// <param name="name">The name to normalize.</param>
/// <returns>A normalized value representing the specified <paramref name="name"/>.</returns>
public virtual string NormalizeName(string name)
=> (KeyNormalizer == null) ? name : KeyNormalizer.NormalizeName(name);
/// <summary>
/// Normalize email for consistent comparisons.
/// </summary>
/// <param name="email">The email to normalize.</param>
/// <returns>A normalized value representing the specified <paramref name="email"/>.</returns>
public virtual string NormalizeEmail(string email)
=> (KeyNormalizer == null) ? email : KeyNormalizer.NormalizeEmail(email);
private string ProtectPersonalData(string data)
{
if (Options.Stores.ProtectPersonalData)
@ -630,7 +636,7 @@ namespace Microsoft.AspNetCore.Identity
/// <returns>The <see cref="Task"/> that represents the asynchronous operation.</returns>
public virtual async Task UpdateNormalizedUserNameAsync(TUser user)
{
var normalizedName = NormalizeKey(await GetUserNameAsync(user));
var normalizedName = NormalizeName(await GetUserNameAsync(user));
normalizedName = ProtectPersonalData(normalizedName);
await Store.SetNormalizedUserNameAsync(user, normalizedName, CancellationToken);
}
@ -1199,7 +1205,7 @@ namespace Microsoft.AspNetCore.Identity
throw new ArgumentNullException(nameof(user));
}
var normalizedRole = NormalizeKey(role);
var normalizedRole = NormalizeName(role);
if (await userRoleStore.IsInRoleAsync(user, normalizedRole, CancellationToken))
{
return await UserAlreadyInRoleError(user, role);
@ -1232,7 +1238,7 @@ namespace Microsoft.AspNetCore.Identity
foreach (var role in roles.Distinct())
{
var normalizedRole = NormalizeKey(role);
var normalizedRole = NormalizeName(role);
if (await userRoleStore.IsInRoleAsync(user, normalizedRole, CancellationToken))
{
return await UserAlreadyInRoleError(user, role);
@ -1260,7 +1266,7 @@ namespace Microsoft.AspNetCore.Identity
throw new ArgumentNullException(nameof(user));
}
var normalizedRole = NormalizeKey(role);
var normalizedRole = NormalizeName(role);
if (!await userRoleStore.IsInRoleAsync(user, normalizedRole, CancellationToken))
{
return await UserNotInRoleError(user, role);
@ -1305,7 +1311,7 @@ namespace Microsoft.AspNetCore.Identity
foreach (var role in roles)
{
var normalizedRole = NormalizeKey(role);
var normalizedRole = NormalizeName(role);
if (!await userRoleStore.IsInRoleAsync(user, normalizedRole, CancellationToken))
{
return await UserNotInRoleError(user, role);
@ -1348,7 +1354,7 @@ namespace Microsoft.AspNetCore.Identity
{
throw new ArgumentNullException(nameof(user));
}
return await userRoleStore.IsInRoleAsync(user, NormalizeKey(role), CancellationToken);
return await userRoleStore.IsInRoleAsync(user, NormalizeName(role), CancellationToken);
}
/// <summary>
@ -1409,7 +1415,7 @@ namespace Microsoft.AspNetCore.Identity
throw new ArgumentNullException(nameof(email));
}
email = NormalizeKey(email);
email = NormalizeEmail(email);
var user = await store.FindByEmailAsync(email, CancellationToken);
// Need to potentially check all keys
@ -1444,7 +1450,7 @@ namespace Microsoft.AspNetCore.Identity
if (store != null)
{
var email = await GetEmailAsync(user);
await store.SetNormalizedEmailAsync(user, ProtectPersonalData(NormalizeKey(email)), CancellationToken);
await store.SetNormalizedEmailAsync(user, ProtectPersonalData(NormalizeEmail(email)), CancellationToken);
}
}
@ -2097,7 +2103,7 @@ namespace Microsoft.AspNetCore.Identity
throw new ArgumentNullException(nameof(roleName));
}
return store.GetUsersInRoleAsync(NormalizeKey(roleName), CancellationToken);
return store.GetUsersInRoleAsync(NormalizeName(roleName), CancellationToken);
}
/// <summary>

View File

@ -111,7 +111,6 @@ namespace Microsoft.AspNetCore.Identity.Test
store.VerifyAll();
}
[Fact]
public void DisposeAfterDisposeDoesNotThrow()
{
@ -209,4 +208,4 @@ namespace Microsoft.AspNetCore.Identity.Test
}
}
}
}
}

View File

@ -269,6 +269,48 @@ namespace Microsoft.AspNetCore.Identity.Test
store.VerifyAll();
}
private class CustomNormalizer : ILookupNormalizer
{
public string NormalizeName(string key) => "#"+key;
public string NormalizeEmail(string key) => "@" + key;
}
[Fact]
public async Task FindByEmailCallsStoreWithCustomNormalizedEmail()
{
// Setup
var store = new Mock<IUserEmailStore<PocoUser>>();
var user = new PocoUser { Email = "Foo" };
store.Setup(s => s.FindByEmailAsync("@Foo", CancellationToken.None)).Returns(Task.FromResult(user)).Verifiable();
var userManager = MockHelpers.TestUserManager(store.Object);
userManager.KeyNormalizer = new CustomNormalizer();
// Act
var result = await userManager.FindByEmailAsync(user.Email);
// Assert
Assert.Equal(user, result);
store.VerifyAll();
}
[Fact]
public async Task FindByNameCallsStoreWithCustomNormalizedName()
{
// Setup
var store = new Mock<IUserEmailStore<PocoUser>>();
var user = new PocoUser { UserName = "Foo", Email = "Bar" };
store.Setup(s => s.FindByNameAsync("#Foo", CancellationToken.None)).Returns(Task.FromResult(user)).Verifiable();
var userManager = MockHelpers.TestUserManager(store.Object);
userManager.KeyNormalizer = new CustomNormalizer();
// Act
var result = await userManager.FindByNameAsync(user.UserName);
// Assert
Assert.Equal(user, result);
store.VerifyAll();
}
[Fact]
public async Task AddToRolesCallsStore()
{
@ -307,6 +349,45 @@ namespace Microsoft.AspNetCore.Identity.Test
store.Verify(s => s.AddToRoleAsync(user, "C", CancellationToken.None), Times.Once());
}
[Fact]
public async Task AddToRolesCallsStoreWithCustomNameNormalizer()
{
// Setup
var store = new Mock<IUserRoleStore<PocoUser>>();
var user = new PocoUser { UserName = "Foo" };
var roles = new string[] { "A", "B", "C", "C" };
store.Setup(s => s.AddToRoleAsync(user, "#A", CancellationToken.None))
.Returns(Task.FromResult(0))
.Verifiable();
store.Setup(s => s.AddToRoleAsync(user, "#B", CancellationToken.None))
.Returns(Task.FromResult(0))
.Verifiable();
store.Setup(s => s.AddToRoleAsync(user, "#C", CancellationToken.None))
.Returns(Task.FromResult(0))
.Verifiable();
store.Setup(s => s.UpdateAsync(user, CancellationToken.None)).ReturnsAsync(IdentityResult.Success).Verifiable();
store.Setup(s => s.IsInRoleAsync(user, "#A", CancellationToken.None))
.Returns(Task.FromResult(false))
.Verifiable();
store.Setup(s => s.IsInRoleAsync(user, "#B", CancellationToken.None))
.Returns(Task.FromResult(false))
.Verifiable();
store.Setup(s => s.IsInRoleAsync(user, "#C", CancellationToken.None))
.Returns(Task.FromResult(false))
.Verifiable();
var userManager = MockHelpers.TestUserManager<PocoUser>(store.Object);
userManager.KeyNormalizer = new CustomNormalizer();
// Act
var result = await userManager.AddToRolesAsync(user, roles);
// Assert
Assert.True(result.Succeeded);
store.VerifyAll();
store.Verify(s => s.AddToRoleAsync(user, "#C", CancellationToken.None), Times.Once());
}
[Fact]
public async Task AddToRolesFailsIfUserInRole()
{
@ -1664,4 +1745,4 @@ namespace Microsoft.AspNetCore.Identity.Test
}
}
}
}