[Fixes #1690] AddDefaultUI() throws when using IdentityUser<T>

This commit is contained in:
Kiran Challa 2018-03-27 15:52:06 -07:00
parent c7711c2515
commit 971f727ea2
27 changed files with 178 additions and 66 deletions

View File

@ -1,7 +1,7 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15 # Visual Studio 15
VisualStudioVersion = 15.0.26730.12 VisualStudioVersion = 15.0.27130.2027
MinimumVisualStudioVersion = 15.0.26730.03 MinimumVisualStudioVersion = 15.0.26730.03
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{0F647068-6602-4E24-B1DC-8ED91481A50A}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{0F647068-6602-4E24-B1DC-8ED91481A50A}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject

View File

@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal
public virtual Task<IActionResult> OnGetAsync(string userId, string code) => throw new NotImplementedException(); public virtual Task<IActionResult> OnGetAsync(string userId, string code) => throw new NotImplementedException();
} }
internal class ConfirmEmailModel<TUser> : ConfirmEmailModel where TUser : IdentityUser internal class ConfirmEmailModel<TUser> : ConfirmEmailModel where TUser : class
{ {
private readonly UserManager<TUser> _userManager; private readonly UserManager<TUser> _userManager;

View File

@ -40,19 +40,22 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal
public virtual Task<IActionResult> OnPostConfirmationAsync(string returnUrl = null) => throw new NotImplementedException(); public virtual Task<IActionResult> OnPostConfirmationAsync(string returnUrl = null) => throw new NotImplementedException();
} }
internal class ExternalLoginModel<TUser> : ExternalLoginModel where TUser : IdentityUser, new() internal class ExternalLoginModel<TUser> : ExternalLoginModel where TUser : class
{ {
private readonly SignInManager<TUser> _signInManager; private readonly SignInManager<TUser> _signInManager;
private readonly UserManager<TUser> _userManager; private readonly UserManager<TUser> _userManager;
private readonly IUserFactory<TUser> _userFactory;
private readonly ILogger<ExternalLoginModel> _logger; private readonly ILogger<ExternalLoginModel> _logger;
public ExternalLoginModel( public ExternalLoginModel(
SignInManager<TUser> signInManager, SignInManager<TUser> signInManager,
UserManager<TUser> userManager, UserManager<TUser> userManager,
IUserFactory<TUser> userFactory,
ILogger<ExternalLoginModel> logger) ILogger<ExternalLoginModel> logger)
{ {
_signInManager = signInManager; _signInManager = signInManager;
_userManager = userManager; _userManager = userManager;
_userFactory = userFactory;
_logger = logger; _logger = logger;
} }
@ -124,7 +127,7 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal
if (ModelState.IsValid) if (ModelState.IsValid)
{ {
var user = new TUser { UserName = Input.Email, Email = Input.Email }; var user = _userFactory.CreateUser(email: Input.Email, userName: Input.Email);
var result = await _userManager.CreateAsync(user); var result = await _userManager.CreateAsync(user);
if (result.Succeeded) if (result.Succeeded)
{ {

View File

@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal
public virtual Task<IActionResult> OnPostAsync() => throw new NotImplementedException(); public virtual Task<IActionResult> OnPostAsync() => throw new NotImplementedException();
} }
internal class ForgotPasswordModel<TUser> : ForgotPasswordModel where TUser : IdentityUser internal class ForgotPasswordModel<TUser> : ForgotPasswordModel where TUser : class
{ {
private readonly UserManager<TUser> _userManager; private readonly UserManager<TUser> _userManager;
private readonly IEmailSender _emailSender; private readonly IEmailSender _emailSender;

View File

@ -47,7 +47,7 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal
public virtual Task<IActionResult> OnPostAsync(string returnUrl = null) => throw new NotImplementedException(); public virtual Task<IActionResult> OnPostAsync(string returnUrl = null) => throw new NotImplementedException();
} }
internal class LoginModel<TUser> : LoginModel where TUser : IdentityUser internal class LoginModel<TUser> : LoginModel where TUser : class
{ {
private readonly SignInManager<TUser> _signInManager; private readonly SignInManager<TUser> _signInManager;
private readonly ILogger<LoginModel> _logger; private readonly ILogger<LoginModel> _logger;

View File

@ -39,14 +39,19 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal
public virtual Task<IActionResult> OnPostAsync(bool rememberMe, string returnUrl = null) => throw new NotImplementedException(); public virtual Task<IActionResult> OnPostAsync(bool rememberMe, string returnUrl = null) => throw new NotImplementedException();
} }
internal class LoginWith2faModel<TUser> : LoginWith2faModel where TUser : IdentityUser internal class LoginWith2faModel<TUser> : LoginWith2faModel where TUser : class
{ {
private readonly SignInManager<TUser> _signInManager; private readonly SignInManager<TUser> _signInManager;
private readonly UserManager<TUser> _userManager;
private readonly ILogger<LoginWith2faModel> _logger; private readonly ILogger<LoginWith2faModel> _logger;
public LoginWith2faModel(SignInManager<TUser> signInManager, ILogger<LoginWith2faModel> logger) public LoginWith2faModel(
SignInManager<TUser> signInManager,
UserManager<TUser> userManager,
ILogger<LoginWith2faModel> logger)
{ {
_signInManager = signInManager; _signInManager = signInManager;
_userManager = userManager;
_logger = logger; _logger = logger;
} }
@ -85,19 +90,21 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal
var result = await _signInManager.TwoFactorAuthenticatorSignInAsync(authenticatorCode, rememberMe, Input.RememberMachine); var result = await _signInManager.TwoFactorAuthenticatorSignInAsync(authenticatorCode, rememberMe, Input.RememberMachine);
var userId = await _userManager.GetUserIdAsync(user);
if (result.Succeeded) if (result.Succeeded)
{ {
_logger.LogInformation("User with ID '{UserId}' logged in with 2fa.", user.Id); _logger.LogInformation("User with ID '{UserId}' logged in with 2fa.", userId);
return LocalRedirect(returnUrl); return LocalRedirect(returnUrl);
} }
else if (result.IsLockedOut) else if (result.IsLockedOut)
{ {
_logger.LogWarning("User with ID '{UserId}' account locked out.", user.Id); _logger.LogWarning("User with ID '{UserId}' account locked out.", userId);
return RedirectToPage("./Lockout"); return RedirectToPage("./Lockout");
} }
else else
{ {
_logger.LogWarning("Invalid authenticator code entered for user with ID '{UserId}'.", user.Id); _logger.LogWarning("Invalid authenticator code entered for user with ID '{UserId}'.", userId);
ModelState.AddModelError(string.Empty, "Invalid authenticator code."); ModelState.AddModelError(string.Empty, "Invalid authenticator code.");
return Page(); return Page();
} }

View File

@ -34,14 +34,19 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal
public virtual Task<IActionResult> OnPostAsync(string returnUrl = null) => throw new NotImplementedException(); public virtual Task<IActionResult> OnPostAsync(string returnUrl = null) => throw new NotImplementedException();
} }
internal class LoginWithRecoveryCodeModel<TUser> : LoginWithRecoveryCodeModel where TUser : IdentityUser internal class LoginWithRecoveryCodeModel<TUser> : LoginWithRecoveryCodeModel where TUser: class
{ {
private readonly SignInManager<TUser> _signInManager; private readonly SignInManager<TUser> _signInManager;
private readonly UserManager<TUser> _userManager;
private readonly ILogger<LoginWithRecoveryCodeModel> _logger; private readonly ILogger<LoginWithRecoveryCodeModel> _logger;
public LoginWithRecoveryCodeModel(SignInManager<TUser> signInManager, ILogger<LoginWithRecoveryCodeModel> logger) public LoginWithRecoveryCodeModel(
SignInManager<TUser> signInManager,
UserManager<TUser> userManager,
ILogger<LoginWithRecoveryCodeModel> logger)
{ {
_signInManager = signInManager; _signInManager = signInManager;
_userManager = userManager;
_logger = logger; _logger = logger;
} }
@ -76,19 +81,21 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal
var result = await _signInManager.TwoFactorRecoveryCodeSignInAsync(recoveryCode); var result = await _signInManager.TwoFactorRecoveryCodeSignInAsync(recoveryCode);
var userId = await _userManager.GetUserIdAsync(user);
if (result.Succeeded) if (result.Succeeded)
{ {
_logger.LogInformation("User with ID '{UserId}' logged in with a recovery code.", user.Id); _logger.LogInformation("User with ID '{UserId}' logged in with a recovery code.", userId);
return LocalRedirect(returnUrl ?? Url.Content("~/")); return LocalRedirect(returnUrl ?? Url.Content("~/"));
} }
if (result.IsLockedOut) if (result.IsLockedOut)
{ {
_logger.LogWarning("User with ID '{UserId}' account locked out.", user.Id); _logger.LogWarning("User with ID '{UserId}' account locked out.", userId);
return RedirectToPage("./Lockout"); return RedirectToPage("./Lockout");
} }
else else
{ {
_logger.LogWarning("Invalid recovery code entered for user with ID '{UserId}' ", user.Id); _logger.LogWarning("Invalid recovery code entered for user with ID '{UserId}' ", userId);
ModelState.AddModelError(string.Empty, "Invalid recovery code entered."); ModelState.AddModelError(string.Empty, "Invalid recovery code entered.");
return Page(); return Page();
} }

View File

@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal
public virtual Task<IActionResult> OnPost(string returnUrl = null) => throw new NotImplementedException(); public virtual Task<IActionResult> OnPost(string returnUrl = null) => throw new NotImplementedException();
} }
internal class LogoutModel<TUser> : LogoutModel where TUser : IdentityUser internal class LogoutModel<TUser> : LogoutModel where TUser : class
{ {
private readonly SignInManager<TUser> _signInManager; private readonly SignInManager<TUser> _signInManager;
private readonly ILogger<LogoutModel> _logger; private readonly ILogger<LogoutModel> _logger;

View File

@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal
public virtual Task<IActionResult> OnPostAsync() => throw new NotImplementedException(); public virtual Task<IActionResult> OnPostAsync() => throw new NotImplementedException();
} }
internal class ChangePasswordModel<TUser> : ChangePasswordModel where TUser : IdentityUser internal class ChangePasswordModel<TUser> : ChangePasswordModel where TUser : class
{ {
private readonly UserManager<TUser> _userManager; private readonly UserManager<TUser> _userManager;
private readonly SignInManager<TUser> _signInManager; private readonly SignInManager<TUser> _signInManager;

View File

@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal
public virtual Task<IActionResult> OnPostAsync() => throw new NotImplementedException(); public virtual Task<IActionResult> OnPostAsync() => throw new NotImplementedException();
} }
internal class DeletePersonalDataModel<TUser> : DeletePersonalDataModel where TUser : IdentityUser internal class DeletePersonalDataModel<TUser> : DeletePersonalDataModel where TUser: class
{ {
private readonly UserManager<TUser> _userManager; private readonly UserManager<TUser> _userManager;
private readonly SignInManager<TUser> _signInManager; private readonly SignInManager<TUser> _signInManager;
@ -77,14 +77,15 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal
} }
var result = await _userManager.DeleteAsync(user); var result = await _userManager.DeleteAsync(user);
var userId = await _userManager.GetUserIdAsync(user);
if (!result.Succeeded) if (!result.Succeeded)
{ {
throw new InvalidOperationException($"Unexpected error occurred deleteing user with ID '{user.Id}'."); throw new InvalidOperationException($"Unexpected error occurred deleteing user with ID '{userId}'.");
} }
await _signInManager.SignOutAsync(); await _signInManager.SignOutAsync();
_logger.LogInformation("User with ID '{UserId}' deleted themselves.", _userManager.GetUserId(User)); _logger.LogInformation("User with ID '{UserId}' deleted themselves.", userId);
return Redirect("~/"); return Redirect("~/");
} }

View File

@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal
public virtual Task<IActionResult> OnPostAsync() => throw new NotImplementedException(); public virtual Task<IActionResult> OnPostAsync() => throw new NotImplementedException();
} }
internal class Disable2faModel<TUser> : Disable2faModel where TUser : IdentityUser internal class Disable2faModel<TUser> : Disable2faModel where TUser : class
{ {
private readonly UserManager<TUser> _userManager; private readonly UserManager<TUser> _userManager;
private readonly ILogger<Disable2faModel> _logger; private readonly ILogger<Disable2faModel> _logger;

View File

@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal
public virtual Task<IActionResult> OnPostAsync() => throw new NotImplementedException(); public virtual Task<IActionResult> OnPostAsync() => throw new NotImplementedException();
} }
internal class DownloadPersonalDataModel<TUser> : DownloadPersonalDataModel where TUser : IdentityUser internal class DownloadPersonalDataModel<TUser> : DownloadPersonalDataModel where TUser : class
{ {
private readonly UserManager<TUser> _userManager; private readonly UserManager<TUser> _userManager;
private readonly ILogger<DownloadPersonalDataModel> _logger; private readonly ILogger<DownloadPersonalDataModel> _logger;

View File

@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal
public virtual Task<IActionResult> OnPostAsync() => throw new NotImplementedException(); public virtual Task<IActionResult> OnPostAsync() => throw new NotImplementedException();
} }
internal class EnableAuthenticatorModel<TUser> : EnableAuthenticatorModel where TUser : IdentityUser internal class EnableAuthenticatorModel<TUser> : EnableAuthenticatorModel where TUser : class
{ {
private readonly UserManager<TUser> _userManager; private readonly UserManager<TUser> _userManager;
private readonly ILogger<EnableAuthenticatorModel> _logger; private readonly ILogger<EnableAuthenticatorModel> _logger;
@ -102,7 +102,8 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal
} }
await _userManager.SetTwoFactorEnabledAsync(user, true); await _userManager.SetTwoFactorEnabledAsync(user, true);
_logger.LogInformation("User with ID '{UserId}' has enabled 2FA with an authenticator app.", user.Id); var userId = await _userManager.GetUserIdAsync(user);
_logger.LogInformation("User with ID '{UserId}' has enabled 2FA with an authenticator app.", userId);
StatusMessage = "Your authenticator app has been verified."; StatusMessage = "Your authenticator app has been verified.";
@ -129,7 +130,9 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal
} }
SharedKey = FormatKey(unformattedKey); SharedKey = FormatKey(unformattedKey);
AuthenticatorUri = GenerateQrCodeUri(user.Email, unformattedKey);
var email = await _userManager.GetEmailAsync(user);
AuthenticatorUri = GenerateQrCodeUri(email, unformattedKey);
} }
private string FormatKey(string unformattedKey) private string FormatKey(string unformattedKey)

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -32,17 +33,20 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal
public virtual Task<IActionResult> OnGetLinkLoginCallbackAsync() => throw new NotImplementedException(); public virtual Task<IActionResult> OnGetLinkLoginCallbackAsync() => throw new NotImplementedException();
} }
internal class ExternalLoginsModel<TUser> : ExternalLoginsModel where TUser : IdentityUser internal class ExternalLoginsModel<TUser> : ExternalLoginsModel where TUser : class
{ {
private readonly UserManager<TUser> _userManager; private readonly UserManager<TUser> _userManager;
private readonly SignInManager<TUser> _signInManager; private readonly SignInManager<TUser> _signInManager;
private readonly IUserStore<TUser> _userStore;
public ExternalLoginsModel( public ExternalLoginsModel(
UserManager<TUser> userManager, UserManager<TUser> userManager,
SignInManager<TUser> signInManager) SignInManager<TUser> signInManager,
IUserStore<TUser> userStore)
{ {
_userManager = userManager; _userManager = userManager;
_signInManager = signInManager; _signInManager = signInManager;
_userStore = userStore;
} }
public override async Task<IActionResult> OnGetAsync() public override async Task<IActionResult> OnGetAsync()
@ -57,7 +61,14 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal
OtherLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()) OtherLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync())
.Where(auth => CurrentLogins.All(ul => auth.Name != ul.LoginProvider)) .Where(auth => CurrentLogins.All(ul => auth.Name != ul.LoginProvider))
.ToList(); .ToList();
ShowRemoveButton = user.PasswordHash != null || CurrentLogins.Count > 1;
string passwordHash = null;
if (_userStore is IUserPasswordStore<TUser> userPasswordStore)
{
passwordHash = await userPasswordStore.GetPasswordHashAsync(user, HttpContext.RequestAborted);
}
ShowRemoveButton = passwordHash != null || CurrentLogins.Count > 1;
return Page(); return Page();
} }
@ -72,7 +83,8 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal
var result = await _userManager.RemoveLoginAsync(user, loginProvider, providerKey); var result = await _userManager.RemoveLoginAsync(user, loginProvider, providerKey);
if (!result.Succeeded) if (!result.Succeeded)
{ {
throw new InvalidOperationException($"Unexpected error occurred removing external login for user with ID '{user.Id}'."); var userId = await _userManager.GetUserIdAsync(user);
throw new InvalidOperationException($"Unexpected error occurred removing external login for user with ID '{userId}'.");
} }
await _signInManager.RefreshSignInAsync(user); await _signInManager.RefreshSignInAsync(user);
@ -99,16 +111,17 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
} }
var info = await _signInManager.GetExternalLoginInfoAsync(await _userManager.GetUserIdAsync(user)); var userId = await _userManager.GetUserIdAsync(user);
var info = await _signInManager.GetExternalLoginInfoAsync(userId);
if (info == null) if (info == null)
{ {
throw new InvalidOperationException($"Unexpected error occurred loading external login info for user with ID '{user.Id}'."); throw new InvalidOperationException($"Unexpected error occurred loading external login info for user with ID '{userId}'.");
} }
var result = await _userManager.AddLoginAsync(user, info); var result = await _userManager.AddLoginAsync(user, info);
if (!result.Succeeded) if (!result.Succeeded)
{ {
throw new InvalidOperationException($"Unexpected error occurred adding external login for user with ID '{user.Id}'."); throw new InvalidOperationException($"Unexpected error occurred adding external login for user with ID '{userId}'.");
} }
// Clear the existing external cookie to ensure a clean login process // Clear the existing external cookie to ensure a clean login process

View File

@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal
public virtual Task<IActionResult> OnPostAsync() => throw new NotImplementedException(); public virtual Task<IActionResult> OnPostAsync() => throw new NotImplementedException();
} }
internal class GenerateRecoveryCodesModel<TUser> : GenerateRecoveryCodesModel where TUser : IdentityUser internal class GenerateRecoveryCodesModel<TUser> : GenerateRecoveryCodesModel where TUser : class
{ {
private readonly UserManager<TUser> _userManager; private readonly UserManager<TUser> _userManager;
private readonly ILogger<GenerateRecoveryCodesModel> _logger; private readonly ILogger<GenerateRecoveryCodesModel> _logger;
@ -45,9 +45,11 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
} }
if (!user.TwoFactorEnabled) var isTwoFactorEnabled = await _userManager.GetTwoFactorEnabledAsync(user);
if (!isTwoFactorEnabled)
{ {
throw new InvalidOperationException($"Cannot generate recovery codes for user with ID '{user.Id}' because they do not have 2FA enabled."); var userId = await _userManager.GetUserIdAsync(user);
throw new InvalidOperationException($"Cannot generate recovery codes for user with ID '{userId}' because they do not have 2FA enabled.");
} }
return Page(); return Page();
@ -61,15 +63,17 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
} }
if (!user.TwoFactorEnabled) var isTwoFactorEnabled = await _userManager.GetTwoFactorEnabledAsync(user);
var userId = await _userManager.GetUserIdAsync(user);
if (!isTwoFactorEnabled)
{ {
throw new InvalidOperationException($"Cannot generate recovery codes for user with ID '{user.Id}' as they do not have 2FA enabled."); throw new InvalidOperationException($"Cannot generate recovery codes for user with ID '{userId}' as they do not have 2FA enabled.");
} }
var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10); var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10);
RecoveryCodes = recoveryCodes.ToArray(); RecoveryCodes = recoveryCodes.ToArray();
_logger.LogInformation("User with ID '{UserId}' has generated new 2FA recovery codes.", user.Id); _logger.LogInformation("User with ID '{UserId}' has generated new 2FA recovery codes.", userId);
StatusMessage = "You have generated new recovery codes."; StatusMessage = "You have generated new recovery codes.";
return RedirectToPage("./ShowRecoveryCodes"); return RedirectToPage("./ShowRecoveryCodes");
} }

View File

@ -42,7 +42,7 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal
public virtual Task<IActionResult> OnPostSendVerificationEmailAsync() => throw new NotImplementedException(); public virtual Task<IActionResult> OnPostSendVerificationEmailAsync() => throw new NotImplementedException();
} }
internal class IndexModel<TUser> : IndexModel where TUser : IdentityUser internal class IndexModel<TUser> : IndexModel where TUser : class
{ {
private readonly UserManager<TUser> _userManager; private readonly UserManager<TUser> _userManager;
private readonly SignInManager<TUser> _signInManager; private readonly SignInManager<TUser> _signInManager;
@ -65,13 +65,16 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal
{ {
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
} }
var userName = await _userManager.GetUserNameAsync(user);
var email = await _userManager.GetEmailAsync(user);
var phoneNumber = await _userManager.GetPhoneNumberAsync(user);
Username = user.UserName; Username = userName;
Input = new InputModel Input = new InputModel
{ {
Email = user.Email, Email = email,
PhoneNumber = user.PhoneNumber PhoneNumber = phoneNumber
}; };
IsEmailConfirmed = await _userManager.IsEmailConfirmedAsync(user); IsEmailConfirmed = await _userManager.IsEmailConfirmedAsync(user);
@ -92,21 +95,25 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
} }
if (Input.Email != user.Email) var email = await _userManager.GetEmailAsync(user);
if (Input.Email != email)
{ {
var setEmailResult = await _userManager.SetEmailAsync(user, Input.Email); var setEmailResult = await _userManager.SetEmailAsync(user, Input.Email);
if (!setEmailResult.Succeeded) if (!setEmailResult.Succeeded)
{ {
throw new InvalidOperationException($"Unexpected error occurred setting email for user with ID '{user.Id}'."); var userId = await _userManager.GetUserIdAsync(user);
throw new InvalidOperationException($"Unexpected error occurred setting email for user with ID '{userId}'.");
} }
} }
if (Input.PhoneNumber != user.PhoneNumber) var phoneNumber = await _userManager.GetPhoneNumberAsync(user);
if (Input.PhoneNumber != phoneNumber)
{ {
var setPhoneResult = await _userManager.SetPhoneNumberAsync(user, Input.PhoneNumber); var setPhoneResult = await _userManager.SetPhoneNumberAsync(user, Input.PhoneNumber);
if (!setPhoneResult.Succeeded) if (!setPhoneResult.Succeeded)
{ {
throw new InvalidOperationException($"Unexpected error occurred setting phone number for user with ID '{user.Id}'."); var userId = await _userManager.GetUserIdAsync(user);
throw new InvalidOperationException($"Unexpected error occurred setting phone number for user with ID '{userId}'.");
} }
} }
@ -128,14 +135,17 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
} }
var userId = await _userManager.GetUserIdAsync(user);
var email = await _userManager.GetEmailAsync(user);
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.Page( var callbackUrl = Url.Page(
"/Account/ConfirmEmail", "/Account/ConfirmEmail",
pageHandler: null, pageHandler: null,
values: new { userId = user.Id, code = code }, values: new { userId = userId, code = code },
protocol: Request.Scheme); protocol: Request.Scheme);
await _emailSender.SendEmailAsync( await _emailSender.SendEmailAsync(
user.Email, email,
"Confirm your email", "Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>."); $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");

View File

@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal
public virtual Task<IActionResult> OnGet() => throw new NotImplementedException(); public virtual Task<IActionResult> OnGet() => throw new NotImplementedException();
} }
internal class PersonalDataModel<TUser> : PersonalDataModel where TUser : IdentityUser internal class PersonalDataModel<TUser> : PersonalDataModel where TUser : class
{ {
private readonly UserManager<TUser> _userManager; private readonly UserManager<TUser> _userManager;
private readonly ILogger<PersonalDataModel> _logger; private readonly ILogger<PersonalDataModel> _logger;

View File

@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal
public virtual Task<IActionResult> OnPostAsync() => throw new NotImplementedException(); public virtual Task<IActionResult> OnPostAsync() => throw new NotImplementedException();
} }
internal class ResetAuthenticatorModel<TUser> : ResetAuthenticatorModel where TUser : IdentityUser internal class ResetAuthenticatorModel<TUser> : ResetAuthenticatorModel where TUser : class
{ {
UserManager<TUser> _userManager; UserManager<TUser> _userManager;
private readonly SignInManager<TUser> _signInManager; private readonly SignInManager<TUser> _signInManager;
@ -57,7 +57,8 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal
await _userManager.SetTwoFactorEnabledAsync(user, false); await _userManager.SetTwoFactorEnabledAsync(user, false);
await _userManager.ResetAuthenticatorKeyAsync(user); await _userManager.ResetAuthenticatorKeyAsync(user);
_logger.LogInformation("User with ID '{UserId}' has reset their authentication app key.", user.Id); var userId = await _userManager.GetUserIdAsync(user);
_logger.LogInformation("User with ID '{UserId}' has reset their authentication app key.", userId);
await _signInManager.RefreshSignInAsync(user); await _signInManager.RefreshSignInAsync(user);
StatusMessage = "Your authenticator app key has been reset, you will need to configure your authenticator app using the new key."; StatusMessage = "Your authenticator app key has been reset, you will need to configure your authenticator app using the new key.";

View File

@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal
public virtual Task<IActionResult> OnPostAsync() => throw new NotImplementedException(); public virtual Task<IActionResult> OnPostAsync() => throw new NotImplementedException();
} }
internal class SetPasswordModel<TUser> : SetPasswordModel where TUser : IdentityUser internal class SetPasswordModel<TUser> : SetPasswordModel where TUser : class
{ {
private readonly UserManager<TUser> _userManager; private readonly UserManager<TUser> _userManager;
private readonly SignInManager<TUser> _signInManager; private readonly SignInManager<TUser> _signInManager;

View File

@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal
} }
internal class TwoFactorAuthenticationModel<TUser> : TwoFactorAuthenticationModel where TUser : IdentityUser internal class TwoFactorAuthenticationModel<TUser> : TwoFactorAuthenticationModel where TUser : class
{ {
private readonly UserManager<TUser> _userManager; private readonly UserManager<TUser> _userManager;
private readonly SignInManager<TUser> _signInManager; private readonly SignInManager<TUser> _signInManager;

View File

@ -46,9 +46,10 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal
public virtual Task<IActionResult> OnPostAsync(string returnUrl = null) => throw new NotImplementedException(); public virtual Task<IActionResult> OnPostAsync(string returnUrl = null) => throw new NotImplementedException();
} }
internal class RegisterModel<TUser> : RegisterModel where TUser : IdentityUser, new() internal class RegisterModel<TUser> : RegisterModel where TUser : class
{ {
private readonly SignInManager<TUser> _signInManager; private readonly SignInManager<TUser> _signInManager;
private readonly IUserFactory<TUser> _userFactory;
private readonly UserManager<TUser> _userManager; private readonly UserManager<TUser> _userManager;
private readonly ILogger<LoginModel> _logger; private readonly ILogger<LoginModel> _logger;
private readonly IEmailSender _emailSender; private readonly IEmailSender _emailSender;
@ -56,11 +57,13 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal
public RegisterModel( public RegisterModel(
UserManager<TUser> userManager, UserManager<TUser> userManager,
SignInManager<TUser> signInManager, SignInManager<TUser> signInManager,
IUserFactory<TUser> userFactory,
ILogger<LoginModel> logger, ILogger<LoginModel> logger,
IEmailSender emailSender) IEmailSender emailSender)
{ {
_userManager = userManager; _userManager = userManager;
_signInManager = signInManager; _signInManager = signInManager;
_userFactory = userFactory;
_logger = logger; _logger = logger;
_emailSender = emailSender; _emailSender = emailSender;
} }
@ -75,17 +78,18 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal
returnUrl = returnUrl ?? Url.Content("~/"); returnUrl = returnUrl ?? Url.Content("~/");
if (ModelState.IsValid) if (ModelState.IsValid)
{ {
var user = new TUser { UserName = Input.Email, Email = Input.Email }; var user = _userFactory.CreateUser(email: Input.Email, userName: Input.Email);
var result = await _userManager.CreateAsync(user, Input.Password); var result = await _userManager.CreateAsync(user, Input.Password);
if (result.Succeeded) if (result.Succeeded)
{ {
_logger.LogInformation("User created a new account with password."); _logger.LogInformation("User created a new account with password.");
var userId = await _userManager.GetUserIdAsync(user);
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.Page( var callbackUrl = Url.Page(
"/Account/ConfirmEmail", "/Account/ConfirmEmail",
pageHandler: null, pageHandler: null,
values: new { userId = user.Id, code = code }, values: new { userId = userId, code = code },
protocol: Request.Scheme); protocol: Request.Scheme);
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email", await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",

View File

@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal
public virtual Task<IActionResult> OnPostAsync() => throw new NotImplementedException(); public virtual Task<IActionResult> OnPostAsync() => throw new NotImplementedException();
} }
internal class ResetPasswordModel<TUser> : ResetPasswordModel where TUser : IdentityUser internal class ResetPasswordModel<TUser> : ResetPasswordModel where TUser : class
{ {
private readonly UserManager<TUser> _userManager; private readonly UserManager<TUser> _userManager;

20
src/UI/IUserFactory.cs Normal file
View File

@ -0,0 +1,20 @@
// 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.
namespace Microsoft.AspNetCore.Identity.UI
{
/// <summary>
/// Provides an abstraction for instantiating the given user type.
/// </summary>
/// <typeparam name="TUser">The type of user.</typeparam>
public interface IUserFactory<TUser> where TUser : class
{
/// <summary>
/// Creates an instance of a user and assigns the provided values.
/// </summary>
/// <param name="email">Email address</param>
/// <param name="userName">User name</param>
/// <returns>Created user</returns>
TUser CreateUser(string email, string userName);
}
}

View File

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq; using System.Linq;
using Microsoft.AspNetCore.Identity.UI; using Microsoft.AspNetCore.Identity.UI;
using Microsoft.AspNetCore.Identity.UI.Services; using Microsoft.AspNetCore.Identity.UI.Services;
@ -36,6 +37,13 @@ namespace Microsoft.AspNetCore.Identity
.MakeGenericType(builder.UserType)); .MakeGenericType(builder.UserType));
builder.Services.TryAddTransient<IEmailSender, EmailSender>(); builder.Services.TryAddTransient<IEmailSender, EmailSender>();
if (TryGetIdentityUserType(builder.UserType, out var primaryKeyType))
{
var userFactoryType = typeof(IUserFactory<>).MakeGenericType(builder.UserType);
var defaultUserFactoryType = typeof(UserFactory<,>).MakeGenericType(builder.UserType, primaryKeyType);
builder.Services.TryAddSingleton(userFactoryType, defaultUserFactoryType);
}
return builder; return builder;
} }
@ -63,5 +71,24 @@ namespace Microsoft.AspNetCore.Identity
} }
}); });
} }
private static bool TryGetIdentityUserType(Type userType, out Type primaryKeyType)
{
primaryKeyType = null;
var baseType = userType.BaseType;
while (baseType != null)
{
if (baseType.IsGenericType &&
baseType.GetGenericTypeDefinition() == typeof(IdentityUser<>))
{
primaryKeyType = baseType.GetGenericArguments()[0];
return true;
}
baseType = baseType.BaseType;
}
return false;
}
} }
} }

View File

@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Identity.UI
internal class IdentityDefaultUIConfigureOptions<TUser> : internal class IdentityDefaultUIConfigureOptions<TUser> :
IPostConfigureOptions<RazorPagesOptions>, IPostConfigureOptions<RazorPagesOptions>,
IPostConfigureOptions<StaticFileOptions>, IPostConfigureOptions<StaticFileOptions>,
IPostConfigureOptions<CookieAuthenticationOptions> where TUser : IdentityUser IPostConfigureOptions<CookieAuthenticationOptions> where TUser : class
{ {
private const string IdentityUIDefaultAreaName = "Identity"; private const string IdentityUIDefaultAreaName = "Identity";

View File

@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Mvc.ApplicationModels;
namespace Microsoft.AspNetCore.Identity.UI namespace Microsoft.AspNetCore.Identity.UI
{ {
internal class IdentityPageModelConvention<TUser> : IPageApplicationModelConvention where TUser : IdentityUser internal class IdentityPageModelConvention<TUser> : IPageApplicationModelConvention where TUser : class
{ {
public void Apply(PageApplicationModel model) public void Apply(PageApplicationModel model)
{ {
@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Identity.UI
private void ValidateTemplate(Type template) private void ValidateTemplate(Type template)
{ {
if(template.IsAbstract || !template.IsGenericTypeDefinition) if (template.IsAbstract || !template.IsGenericTypeDefinition)
{ {
throw new InvalidOperationException("Implementation type can't be abstract or non generic."); throw new InvalidOperationException("Implementation type can't be abstract or non generic.");
} }
@ -33,11 +33,6 @@ namespace Microsoft.AspNetCore.Identity.UI
{ {
throw new InvalidOperationException("Implementation type contains wrong generic arity."); throw new InvalidOperationException("Implementation type contains wrong generic arity.");
} }
var argument = genericArguments[0];
if (!typeof(IdentityUser).IsAssignableFrom(typeof(TUser)))
{
throw new InvalidOperationException("Generic implementation type is not compatible.");
};
} }
} }
} }

17
src/UI/UserFactory.cs Normal file
View File

@ -0,0 +1,17 @@
// 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;
namespace Microsoft.AspNetCore.Identity.UI
{
internal class UserFactory<TUser, TKey> : IUserFactory<TUser>
where TUser : IdentityUser<TKey>, new()
where TKey : IEquatable<TKey>
{
public TUser CreateUser(string email, string userName)
{
return new TUser() { Email = email, UserName = userName };
}
}
}