Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ public SecurityStampValidator(Microsoft.Extensions.Options.IOptions<Microsoft.As
}
public partial class SignInManager<TUser> where TUser : class
{
public SignInManager(Microsoft.AspNetCore.Identity.UserManager<TUser> userManager, Microsoft.AspNetCore.Http.IHttpContextAccessor contextAccessor, Microsoft.AspNetCore.Identity.IUserClaimsPrincipalFactory<TUser> claimsFactory, Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Identity.IdentityOptions> optionsAccessor, Microsoft.Extensions.Logging.ILogger<Microsoft.AspNetCore.Identity.SignInManager<TUser>> logger, Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider schemes) { }
public SignInManager(Microsoft.AspNetCore.Identity.UserManager<TUser> userManager, Microsoft.AspNetCore.Http.IHttpContextAccessor contextAccessor, Microsoft.AspNetCore.Identity.IUserClaimsPrincipalFactory<TUser> claimsFactory, Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Identity.IdentityOptions> optionsAccessor, Microsoft.Extensions.Logging.ILogger<Microsoft.AspNetCore.Identity.SignInManager<TUser>> logger, Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider schemes, Microsoft.AspNetCore.Identity.IUserConfirmation<TUser> confirmation) { }
public Microsoft.AspNetCore.Identity.IUserClaimsPrincipalFactory<TUser> ClaimsFactory { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public Microsoft.AspNetCore.Http.HttpContext Context { get { throw null; } set { } }
public virtual Microsoft.Extensions.Logging.ILogger Logger { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ public static IdentityBuilder AddIdentity<TUser, TRole>(
services.TryAddScoped<ISecurityStampValidator, SecurityStampValidator<TUser>>();
services.TryAddScoped<ITwoFactorSecurityStampValidator, TwoFactorSecurityStampValidator<TUser>>();
services.TryAddScoped<IUserClaimsPrincipalFactory<TUser>, UserClaimsPrincipalFactory<TUser, TRole>>();
services.TryAddScoped<IUserConfirmation<TUser>, DefaultUserConfirmation<TUser>>();
services.TryAddScoped<UserManager<TUser>>();
services.TryAddScoped<SignInManager<TUser>>();
services.TryAddScoped<RoleManager<TRole>>();
Expand Down
12 changes: 10 additions & 2 deletions src/Identity/Core/src/SignInManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,14 @@ public class SignInManager<TUser> where TUser : class
/// <param name="optionsAccessor">The accessor used to access the <see cref="IdentityOptions"/>.</param>
/// <param name="logger">The logger used to log messages, warnings and errors.</param>
/// <param name="schemes">The scheme provider that is used enumerate the authentication schemes.</param>
/// <param name="confirmation">The <see cref="IUserConfirmation{TUser}"/> used check whether a user account is confirmed.</param>
public SignInManager(UserManager<TUser> userManager,
IHttpContextAccessor contextAccessor,
IUserClaimsPrincipalFactory<TUser> claimsFactory,
IOptions<IdentityOptions> optionsAccessor,
ILogger<SignInManager<TUser>> logger,
IAuthenticationSchemeProvider schemes)
IAuthenticationSchemeProvider schemes,
IUserConfirmation<TUser> confirmation)
{
if (userManager == null)
{
Expand All @@ -57,11 +59,13 @@ public SignInManager(UserManager<TUser> userManager,
Options = optionsAccessor?.Value ?? new IdentityOptions();
Logger = logger;
_schemes = schemes;
_confirmation = confirmation;
}

private readonly IHttpContextAccessor _contextAccessor;
private HttpContext _context;
private IAuthenticationSchemeProvider _schemes;
private IUserConfirmation<TUser> _confirmation;

/// <summary>
/// Gets the <see cref="ILogger"/> used to log messages from the manager.
Expand Down Expand Up @@ -148,7 +152,11 @@ public virtual async Task<bool> CanSignInAsync(TUser user)
Logger.LogWarning(1, "User {userId} cannot sign in without a confirmed phone number.", await UserManager.GetUserIdAsync(user));
return false;
}

if (Options.SignIn.RequireConfirmedAccount && !(await _confirmation.IsConfirmedAsync(UserManager, user)))
{
Logger.LogWarning(4, "User {userId} cannot sign in without a confirmed account.", await UserManager.GetUserIdAsync(user));
return false;
}
return true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ public DefaultPersonalDataProtector(Microsoft.AspNetCore.Identity.ILookupProtect
public virtual string Protect(string data) { throw null; }
public virtual string Unprotect(string data) { throw null; }
}
public partial class DefaultUserConfirmation<TUser> : Microsoft.AspNetCore.Identity.IUserConfirmation<TUser> where TUser : class
{
public DefaultUserConfirmation() { }
[System.Diagnostics.DebuggerStepThroughAttribute]
public virtual System.Threading.Tasks.Task<bool> IsConfirmedAsync(Microsoft.AspNetCore.Identity.UserManager<TUser> manager, TUser user) { throw null; }
}
public partial class EmailTokenProvider<TUser> : Microsoft.AspNetCore.Identity.TotpSecurityStampBasedTokenProvider<TUser> where TUser : class
{
public EmailTokenProvider() { }
Expand Down Expand Up @@ -194,6 +200,10 @@ public partial interface IUserClaimStore<TUser> : Microsoft.AspNetCore.Identity.
System.Threading.Tasks.Task RemoveClaimsAsync(TUser user, System.Collections.Generic.IEnumerable<System.Security.Claims.Claim> claims, System.Threading.CancellationToken cancellationToken);
System.Threading.Tasks.Task ReplaceClaimAsync(TUser user, System.Security.Claims.Claim claim, System.Security.Claims.Claim newClaim, System.Threading.CancellationToken cancellationToken);
}
public partial interface IUserConfirmation<TUser> where TUser : class
{
System.Threading.Tasks.Task<bool> IsConfirmedAsync(Microsoft.AspNetCore.Identity.UserManager<TUser> manager, TUser user);
}
public partial interface IUserEmailStore<TUser> : Microsoft.AspNetCore.Identity.IUserStore<TUser>, System.IDisposable where TUser : class
{
System.Threading.Tasks.Task<TUser> FindByEmailAsync(string normalizedEmail, System.Threading.CancellationToken cancellationToken);
Expand Down Expand Up @@ -396,6 +406,7 @@ public RoleValidator(Microsoft.AspNetCore.Identity.IdentityErrorDescriber errors
public partial class SignInOptions
{
public SignInOptions() { }
public bool RequireConfirmedAccount { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public bool RequireConfirmedEmail { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public bool RequireConfirmedPhoneNumber { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
}
Expand Down
29 changes: 29 additions & 0 deletions src/Identity/Extensions.Core/src/DefaultUserConfirmation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// 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.Threading.Tasks;

namespace Microsoft.AspNetCore.Identity
{
/// <summary>
/// Default implementation of <see cref="IUserConfirmation{TUser}"/>.
/// </summary>
/// <typeparam name="TUser">The type encapsulating a user.</typeparam>
public class DefaultUserConfirmation<TUser> : IUserConfirmation<TUser> where TUser : class
{
/// <summary>
/// Determines whether the specified <paramref name="user"/> is confirmed.
/// </summary>
/// <param name="manager">The <see cref="UserManager{TUser}"/> that can be used to retrieve user properties.</param>
/// <param name="user">The user.</param>
/// <returns>The <see cref="Task"/> that represents the asynchronous operation, containing the <see cref="IdentityResult"/> of the confirmation operation.</returns>
public async virtual Task<bool> IsConfirmedAsync(UserManager<TUser> manager, TUser user)
{
if (!await manager.IsEmailConfirmedAsync(user))
{
return false;
}
return true;
}
}
}
22 changes: 22 additions & 0 deletions src/Identity/Extensions.Core/src/IUserConfirmation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// 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.Threading.Tasks;

namespace Microsoft.AspNetCore.Identity
{
/// <summary>
/// Provides an abstraction for confirmation of user accounts.
/// </summary>
/// <typeparam name="TUser">The type encapsulating a user.</typeparam>
public interface IUserConfirmation<TUser> where TUser : class
{
/// <summary>
/// Determines whether the specified <paramref name="user"/> is confirmed.
/// </summary>
/// <param name="manager">The <see cref="UserManager{TUser}"/> that can be used to retrieve user properties.</param>
/// <param name="user">The user.</param>
/// <returns>Whether the user is confirmed.</returns>
Task<bool> IsConfirmedAsync(UserManager<TUser> manager, TUser user);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public static IdentityBuilder AddIdentityCore<TUser>(this IServiceCollection ser
services.TryAddScoped<IPasswordValidator<TUser>, PasswordValidator<TUser>>();
services.TryAddScoped<IPasswordHasher<TUser>, PasswordHasher<TUser>>();
services.TryAddScoped<ILookupNormalizer, UpperInvariantLookupNormalizer>();
services.TryAddScoped<IUserConfirmation<TUser>, DefaultUserConfirmation<TUser>>();
// No interface for the error describer so we can add errors without rev'ing the interface
services.TryAddScoped<IdentityErrorDescriber>();
services.TryAddScoped<IUserClaimsPrincipalFactory<TUser>, UserClaimsPrincipalFactory<TUser>>();
Expand Down
8 changes: 7 additions & 1 deletion src/Identity/Extensions.Core/src/SignInOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,11 @@ public class SignInOptions
/// </summary>
/// <value>True if a user must have a confirmed telephone number before they can sign in, otherwise false.</value>
public bool RequireConfirmedPhoneNumber { get; set; }

/// <summary>
/// Gets or sets a flag indicating whether a confirmed <see cref="IUserConfirmation{TUser}"/> account is required to sign in. Defaults to false.
/// </summary>
/// <value>True if a user must have a confirmed account before they can sign in, otherwise false.</value>
public bool RequireConfirmedAccount { get; set; }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1710,6 +1710,34 @@ public async Task CanChangeEmail()
Assert.NotEqual(stamp, await manager.GetSecurityStampAsync(user));
}

/// <summary>
/// Test.
/// </summary>
/// <returns>Task</returns>
[Fact]
public async Task CanChangeEmailOnlyIfEmailSame()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateManager();
var user = CreateTestUser("foouser");
IdentityResultAssert.IsSuccess(await manager.CreateAsync(user));
var email = await manager.GetUserNameAsync(user) + "@diddly.bop";
IdentityResultAssert.IsSuccess(await manager.SetEmailAsync(user, email));
Assert.False(await manager.IsEmailConfirmedAsync(user));
var stamp = await manager.GetSecurityStampAsync(user);
var newEmail = await manager.GetUserNameAsync(user) + "@en.vec";
var token1 = await manager.GenerateChangeEmailTokenAsync(user, newEmail);
var token2 = await manager.GenerateChangeEmailTokenAsync(user, "[email protected]");
IdentityResultAssert.IsSuccess(await manager.ChangeEmailAsync(user, newEmail, token1));
Assert.True(await manager.IsEmailConfirmedAsync(user));
Assert.Equal(await manager.GetEmailAsync(user), newEmail);
Assert.NotEqual(stamp, await manager.GetSecurityStampAsync(user));
IdentityResultAssert.IsFailure(await manager.ChangeEmailAsync(user, "[email protected]", token2));
}

/// <summary>
/// Test.
/// </summary>
Expand Down
Loading