From f4c80ca99f42cd37dd8128e44eacecc0e584bc51 Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Fri, 8 Mar 2019 11:39:26 -0800 Subject: [PATCH] EF UserStore FindByEmail will throw on dupes (#8220) --- .../EntityFrameworkCore/src/UserOnlyStore.cs | 2 +- .../EntityFrameworkCore/src/UserStore.cs | 2 +- .../test/EF.Test/UserOnlyTest.cs | 23 +++++++++++++++++++ .../test/EF.Test/UserStoreTest.cs | 17 ++++++++++++++ .../Extensions.Core/src/UserManager.cs | 2 ++ 5 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/Identity/EntityFrameworkCore/src/UserOnlyStore.cs b/src/Identity/EntityFrameworkCore/src/UserOnlyStore.cs index 614d4fbf6d..366bf4badd 100644 --- a/src/Identity/EntityFrameworkCore/src/UserOnlyStore.cs +++ b/src/Identity/EntityFrameworkCore/src/UserOnlyStore.cs @@ -502,7 +502,7 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); - return Users.FirstOrDefaultAsync(u => u.NormalizedEmail == normalizedEmail, cancellationToken); + return Users.SingleOrDefaultAsync(u => u.NormalizedEmail == normalizedEmail, cancellationToken); } /// diff --git a/src/Identity/EntityFrameworkCore/src/UserStore.cs b/src/Identity/EntityFrameworkCore/src/UserStore.cs index 453cb6efec..736c82922e 100644 --- a/src/Identity/EntityFrameworkCore/src/UserStore.cs +++ b/src/Identity/EntityFrameworkCore/src/UserStore.cs @@ -635,7 +635,7 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); - return Users.FirstOrDefaultAsync(u => u.NormalizedEmail == normalizedEmail, cancellationToken); + return Users.SingleOrDefaultAsync(u => u.NormalizedEmail == normalizedEmail, cancellationToken); } /// diff --git a/src/Identity/EntityFrameworkCore/test/EF.Test/UserOnlyTest.cs b/src/Identity/EntityFrameworkCore/test/EF.Test/UserOnlyTest.cs index c19711e34a..8f1ea38881 100644 --- a/src/Identity/EntityFrameworkCore/test/EF.Test/UserOnlyTest.cs +++ b/src/Identity/EntityFrameworkCore/test/EF.Test/UserOnlyTest.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder.Internal; using Microsoft.AspNetCore.Identity.Test; @@ -65,5 +66,27 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test IdentityResultAssert.IsSuccess(await userManager.CreateAsync(user, password)); IdentityResultAssert.IsSuccess(await userManager.DeleteAsync(user)); } + + [ConditionalFact] + [FrameworkSkipCondition(RuntimeFrameworks.Mono)] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + public async Task FindByEmailThrowsWithTwoUsersWithSameEmail() + { + var userStore = _builder.ApplicationServices.GetRequiredService>(); + var manager = _builder.ApplicationServices.GetRequiredService>(); + + Assert.NotNull(userStore); + Assert.NotNull(manager); + + var userA = new IdentityUser(Guid.NewGuid().ToString()); + userA.Email = "dupe@dupe.com"; + const string password = "1qaz@WSX"; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(userA, password)); + var userB = new IdentityUser(Guid.NewGuid().ToString()); + userB.Email = "dupe@dupe.com"; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(userB, password)); + await Assert.ThrowsAsync(async () => await manager.FindByEmailAsync("dupe@dupe.com")); + } } } diff --git a/src/Identity/EntityFrameworkCore/test/EF.Test/UserStoreTest.cs b/src/Identity/EntityFrameworkCore/test/EF.Test/UserStoreTest.cs index 34a824d62e..d143047a58 100644 --- a/src/Identity/EntityFrameworkCore/test/EF.Test/UserStoreTest.cs +++ b/src/Identity/EntityFrameworkCore/test/EF.Test/UserStoreTest.cs @@ -208,6 +208,23 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test Assert.NotEqual(userA.PasswordHash, userB.PasswordHash); } + [ConditionalFact] + [FrameworkSkipCondition(RuntimeFrameworks.Mono)] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + public async Task FindByEmailThrowsWithTwoUsersWithSameEmail() + { + var manager = CreateManager(); + var userA = new IdentityUser(Guid.NewGuid().ToString()); + userA.Email = "dupe@dupe.com"; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(userA, "password")); + var userB = new IdentityUser(Guid.NewGuid().ToString()); + userB.Email = "dupe@dupe.com"; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(userB, "password")); + await Assert.ThrowsAsync(async () => await manager.FindByEmailAsync("dupe@dupe.com")); + + } + [ConditionalFact] [FrameworkSkipCondition(RuntimeFrameworks.Mono)] [OSSkipCondition(OperatingSystems.Linux)] diff --git a/src/Identity/Extensions.Core/src/UserManager.cs b/src/Identity/Extensions.Core/src/UserManager.cs index e5569a65aa..5d1be95124 100644 --- a/src/Identity/Extensions.Core/src/UserManager.cs +++ b/src/Identity/Extensions.Core/src/UserManager.cs @@ -1393,6 +1393,8 @@ namespace Microsoft.AspNetCore.Identity /// /// Gets the user, if any, associated with the normalized value of the specified email address. + /// Note: Its recommended that identityOptions.User.RequireUniqueEmail be set to true when using this method, otherwise + /// the store may throw if there are users with duplicate emails. /// /// The email address to return the user for. ///