Skip to content

Commit f4c80ca

Browse files
authored
EF UserStore FindByEmail will throw on dupes (#8220)
1 parent a53accf commit f4c80ca

File tree

5 files changed

+44
-2
lines changed

5 files changed

+44
-2
lines changed

src/Identity/EntityFrameworkCore/src/UserOnlyStore.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,7 @@ public async override Task<TUser> FindByLoginAsync(string loginProvider, string
502502
{
503503
cancellationToken.ThrowIfCancellationRequested();
504504
ThrowIfDisposed();
505-
return Users.FirstOrDefaultAsync(u => u.NormalizedEmail == normalizedEmail, cancellationToken);
505+
return Users.SingleOrDefaultAsync(u => u.NormalizedEmail == normalizedEmail, cancellationToken);
506506
}
507507

508508
/// <summary>

src/Identity/EntityFrameworkCore/src/UserStore.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -635,7 +635,7 @@ public async override Task<TUser> FindByLoginAsync(string loginProvider, string
635635
{
636636
cancellationToken.ThrowIfCancellationRequested();
637637
ThrowIfDisposed();
638-
return Users.FirstOrDefaultAsync(u => u.NormalizedEmail == normalizedEmail, cancellationToken);
638+
return Users.SingleOrDefaultAsync(u => u.NormalizedEmail == normalizedEmail, cancellationToken);
639639
}
640640

641641
/// <summary>

src/Identity/EntityFrameworkCore/test/EF.Test/UserOnlyTest.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System;
45
using System.Threading.Tasks;
56
using Microsoft.AspNetCore.Builder.Internal;
67
using Microsoft.AspNetCore.Identity.Test;
@@ -65,5 +66,27 @@ public async Task EnsureStartupUsageWorks()
6566
IdentityResultAssert.IsSuccess(await userManager.CreateAsync(user, password));
6667
IdentityResultAssert.IsSuccess(await userManager.DeleteAsync(user));
6768
}
69+
70+
[ConditionalFact]
71+
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
72+
[OSSkipCondition(OperatingSystems.Linux)]
73+
[OSSkipCondition(OperatingSystems.MacOSX)]
74+
public async Task FindByEmailThrowsWithTwoUsersWithSameEmail()
75+
{
76+
var userStore = _builder.ApplicationServices.GetRequiredService<IUserStore<IdentityUser>>();
77+
var manager = _builder.ApplicationServices.GetRequiredService<UserManager<IdentityUser>>();
78+
79+
Assert.NotNull(userStore);
80+
Assert.NotNull(manager);
81+
82+
var userA = new IdentityUser(Guid.NewGuid().ToString());
83+
userA.Email = "[email protected]";
84+
const string password = "1qaz@WSX";
85+
IdentityResultAssert.IsSuccess(await manager.CreateAsync(userA, password));
86+
var userB = new IdentityUser(Guid.NewGuid().ToString());
87+
userB.Email = "[email protected]";
88+
IdentityResultAssert.IsSuccess(await manager.CreateAsync(userB, password));
89+
await Assert.ThrowsAsync<InvalidOperationException>(async () => await manager.FindByEmailAsync("[email protected]"));
90+
}
6891
}
6992
}

src/Identity/EntityFrameworkCore/test/EF.Test/UserStoreTest.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,23 @@ public async Task TwoUsersSamePasswordDifferentHash()
208208
Assert.NotEqual(userA.PasswordHash, userB.PasswordHash);
209209
}
210210

211+
[ConditionalFact]
212+
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
213+
[OSSkipCondition(OperatingSystems.Linux)]
214+
[OSSkipCondition(OperatingSystems.MacOSX)]
215+
public async Task FindByEmailThrowsWithTwoUsersWithSameEmail()
216+
{
217+
var manager = CreateManager();
218+
var userA = new IdentityUser(Guid.NewGuid().ToString());
219+
userA.Email = "[email protected]";
220+
IdentityResultAssert.IsSuccess(await manager.CreateAsync(userA, "password"));
221+
var userB = new IdentityUser(Guid.NewGuid().ToString());
222+
userB.Email = "[email protected]";
223+
IdentityResultAssert.IsSuccess(await manager.CreateAsync(userB, "password"));
224+
await Assert.ThrowsAsync<InvalidOperationException>(async () => await manager.FindByEmailAsync("[email protected]"));
225+
226+
}
227+
211228
[ConditionalFact]
212229
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
213230
[OSSkipCondition(OperatingSystems.Linux)]

src/Identity/Extensions.Core/src/UserManager.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1393,6 +1393,8 @@ public virtual async Task<IdentityResult> SetEmailAsync(TUser user, string email
13931393

13941394
/// <summary>
13951395
/// Gets the user, if any, associated with the normalized value of the specified email address.
1396+
/// Note: Its recommended that identityOptions.User.RequireUniqueEmail be set to true when using this method, otherwise
1397+
/// the store may throw if there are users with duplicate emails.
13961398
/// </summary>
13971399
/// <param name="email">The email address to return the user for.</param>
13981400
/// <returns>

0 commit comments

Comments
 (0)