Skip to content

Commit d42bdf6

Browse files
authored
Merged PR 47274: Prevent RefreshSignInAsync if it is called with wrong user (#60881)
1 parent e6eb151 commit d42bdf6

File tree

2 files changed

+80
-15
lines changed

2 files changed

+80
-15
lines changed

src/Identity/Core/src/SignInManager.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,21 @@ public virtual async Task<bool> CanSignInAsync(TUser user)
162162
public virtual async Task RefreshSignInAsync(TUser user)
163163
{
164164
var auth = await Context.AuthenticateAsync(AuthenticationScheme);
165-
IList<Claim> claims = Array.Empty<Claim>();
165+
if (!auth.Succeeded || auth.Principal?.Identity?.IsAuthenticated != true)
166+
{
167+
Logger.LogError("RefreshSignInAsync prevented because the user is not currently authenticated. Use SignInAsync instead for initial sign in.");
168+
return;
169+
}
166170

171+
var authenticatedUserId = UserManager.GetUserId(auth.Principal);
172+
var newUserId = await UserManager.GetUserIdAsync(user);
173+
if (authenticatedUserId == null || authenticatedUserId != newUserId)
174+
{
175+
Logger.LogError("RefreshSignInAsync prevented because currently authenticated user has a different UserId. Use SignInAsync instead to change users.");
176+
return;
177+
}
178+
179+
IList<Claim> claims = Array.Empty<Claim>();
167180
var authenticationMethod = auth?.Principal?.FindFirst(ClaimTypes.AuthenticationMethod);
168181
var amr = auth?.Principal?.FindFirst("amr");
169182

src/Identity/test/Identity.Test/SignInManagerTest.cs

Lines changed: 66 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -592,38 +592,38 @@ public async Task CanExternalSignIn(bool isPersistent, bool supportsLockout)
592592
[InlineData(true, false)]
593593
[InlineData(false, true)]
594594
[InlineData(false, false)]
595-
public async Task CanResignIn(
596-
// Suppress warning that says theory methods should use all of their parameters.
597-
// See comments below about why this isn't used.
598-
#pragma warning disable xUnit1026
599-
bool isPersistent,
600-
#pragma warning restore xUnit1026
601-
bool externalLogin)
595+
public async Task CanResignIn(bool isPersistent, bool externalLogin)
602596
{
603597
// Setup
604598
var user = new PocoUser { UserName = "Foo" };
605599
var context = new DefaultHttpContext();
606600
var auth = MockAuth(context);
607601
var loginProvider = "loginprovider";
608-
var id = new ClaimsIdentity();
602+
var id = new ClaimsIdentity("authscheme");
609603
if (externalLogin)
610604
{
611605
id.AddClaim(new Claim(ClaimTypes.AuthenticationMethod, loginProvider));
612606
}
613-
// REVIEW: auth changes we lost the ability to mock is persistent
614-
//var properties = new AuthenticationProperties { IsPersistent = isPersistent };
615-
var authResult = AuthenticateResult.NoResult();
607+
608+
var claimsPrincipal = new ClaimsPrincipal(id);
609+
var properties = new AuthenticationProperties { IsPersistent = isPersistent };
610+
var authResult = AuthenticateResult.Success(new AuthenticationTicket(claimsPrincipal, properties, "authscheme"));
616611
auth.Setup(a => a.AuthenticateAsync(context, IdentityConstants.ApplicationScheme))
617612
.Returns(Task.FromResult(authResult)).Verifiable();
618613
var manager = SetupUserManager(user);
614+
manager.Setup(m => m.GetUserId(claimsPrincipal)).Returns(user.Id.ToString());
619615
var signInManager = new Mock<SignInManager<PocoUser>>(manager.Object,
620616
new HttpContextAccessor { HttpContext = context },
621617
new Mock<IUserClaimsPrincipalFactory<PocoUser>>().Object,
622618
null, null, new Mock<IAuthenticationSchemeProvider>().Object, null)
623619
{ CallBase = true };
624-
//signInManager.Setup(s => s.SignInAsync(user, It.Is<AuthenticationProperties>(p => p.IsPersistent == isPersistent),
625-
//externalLogin? loginProvider : null)).Returns(Task.FromResult(0)).Verifiable();
626-
signInManager.Setup(s => s.SignInWithClaimsAsync(user, It.IsAny<AuthenticationProperties>(), It.IsAny<IEnumerable<Claim>>())).Returns(Task.FromResult(0)).Verifiable();
620+
621+
signInManager.Setup(s => s.SignInWithClaimsAsync(user,
622+
It.Is<AuthenticationProperties>(properties => properties.IsPersistent == isPersistent),
623+
It.Is<IEnumerable<Claim>>(claims => !externalLogin ||
624+
claims.Any(claim => claim.Type == ClaimTypes.AuthenticationMethod && claim.Value == loginProvider))))
625+
.Returns(Task.FromResult(0)).Verifiable();
626+
627627
signInManager.Object.Context = context;
628628

629629
// Act
@@ -634,6 +634,58 @@ public async Task CanResignIn(
634634
signInManager.Verify();
635635
}
636636

637+
[Fact]
638+
public async Task ResignInNoOpsAndLogsErrorIfNotAuthenticated()
639+
{
640+
var user = new PocoUser { UserName = "Foo" };
641+
var context = new DefaultHttpContext();
642+
var auth = MockAuth(context);
643+
var manager = SetupUserManager(user);
644+
var logger = new TestLogger<SignInManager<PocoUser>>();
645+
var signInManager = new Mock<SignInManager<PocoUser>>(manager.Object,
646+
new HttpContextAccessor { HttpContext = context },
647+
new Mock<IUserClaimsPrincipalFactory<PocoUser>>().Object,
648+
null, logger, new Mock<IAuthenticationSchemeProvider>().Object, null)
649+
{ CallBase = true };
650+
auth.Setup(a => a.AuthenticateAsync(context, IdentityConstants.ApplicationScheme))
651+
.Returns(Task.FromResult(AuthenticateResult.NoResult())).Verifiable();
652+
653+
await signInManager.Object.RefreshSignInAsync(user);
654+
655+
Assert.Contains("RefreshSignInAsync prevented because the user is not currently authenticated. Use SignInAsync instead for initial sign in.", logger.LogMessages);
656+
auth.Verify();
657+
signInManager.Verify(s => s.SignInWithClaimsAsync(It.IsAny<PocoUser>(), It.IsAny<AuthenticationProperties>(), It.IsAny<IEnumerable<Claim>>()),
658+
Times.Never());
659+
}
660+
661+
[Fact]
662+
public async Task ResignInNoOpsAndLogsErrorIfAuthenticatedWithDifferentUser()
663+
{
664+
var user = new PocoUser { UserName = "Foo" };
665+
var context = new DefaultHttpContext();
666+
var auth = MockAuth(context);
667+
var manager = SetupUserManager(user);
668+
var logger = new TestLogger<SignInManager<PocoUser>>();
669+
var signInManager = new Mock<SignInManager<PocoUser>>(manager.Object,
670+
new HttpContextAccessor { HttpContext = context },
671+
new Mock<IUserClaimsPrincipalFactory<PocoUser>>().Object,
672+
null, logger, new Mock<IAuthenticationSchemeProvider>().Object, null)
673+
{ CallBase = true };
674+
var id = new ClaimsIdentity("authscheme");
675+
var claimsPrincipal = new ClaimsPrincipal(id);
676+
var authResult = AuthenticateResult.Success(new AuthenticationTicket(claimsPrincipal, new AuthenticationProperties(), "authscheme"));
677+
auth.Setup(a => a.AuthenticateAsync(context, IdentityConstants.ApplicationScheme))
678+
.Returns(Task.FromResult(authResult)).Verifiable();
679+
manager.Setup(m => m.GetUserId(claimsPrincipal)).Returns("different");
680+
681+
await signInManager.Object.RefreshSignInAsync(user);
682+
683+
Assert.Contains("RefreshSignInAsync prevented because currently authenticated user has a different UserId. Use SignInAsync instead to change users.", logger.LogMessages);
684+
auth.Verify();
685+
signInManager.Verify(s => s.SignInWithClaimsAsync(It.IsAny<PocoUser>(), It.IsAny<AuthenticationProperties>(), It.IsAny<IEnumerable<Claim>>()),
686+
Times.Never());
687+
}
688+
637689
[Theory]
638690
[InlineData(true, true, true, true)]
639691
[InlineData(true, true, false, true)]

0 commit comments

Comments
 (0)