From 185482c8cfdcf4a267fb8b90e3fce48a00ab3076 Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Sun, 21 Jan 2018 13:39:23 -0800 Subject: [PATCH] [Fixes #1572] Improve the extensibility of the default identity UI --- build/dependencies.props | 104 +++++------ korebuild-lock.txt | 4 +- .../Pages/Account/Manage/Index.cshtml | 53 ++++++ .../Pages/Account/Manage/Index.cshtml.cs | 164 ++++++++++++++++++ .../Identity/Pages/Account/Register.cshtml | 47 +++++ .../Identity/Pages/Account/Register.cshtml.cs | 115 ++++++++++++ .../Areas/Identity/Pages/_ViewImports.cshtml | 2 + .../Data/ApplicationDbContext.cs | 16 ++ .../Data/ApplicationUser.cs | 14 ++ ...26174859_CreateIdentitySchema.Designer.cs} | 123 ++++++------- ...=> 20180126174859_CreateIdentitySchema.cs} | 4 +- ...s => ApplicationDbContextModelSnapshot.cs} | 123 ++++++------- samples/IdentitySample.DefaultUI/Startup.cs | 7 +- .../Views/Shared/_LoginPartial.cshtml | 5 +- .../IdentitySample.DefaultUI/appsettings.json | 2 +- .../Filters/ExternalLoginsPageFilter.cs | 32 ++++ .../Pages/Account/AccessDenied.cshtml.cs | 2 +- .../Pages/Account/ConfirmEmail.cshtml.cs | 16 +- .../Pages/Account/ExternalLogin.cshtml.cs | 57 +++--- .../Pages/Account/ForgotPassword.cshtml.cs | 31 ++-- .../ForgotPasswordConfirmation.cshtml.cs | 2 +- .../Identity/Pages/Account/Lockout.cshtml.cs | 2 +- .../Identity/Pages/Account/Login.cshtml.cs | 35 ++-- .../Pages/Account/LoginWith2fa.cshtml.cs | 36 ++-- .../Account/LoginWithRecoveryCode.cshtml.cs | 36 ++-- .../Identity/Pages/Account/Logout.cshtml.cs | 25 ++- .../Account/Manage/ChangePassword.cshtml.cs | 44 +++-- .../Manage/DeletePersonalData.cshtml.cs | 44 +++-- .../Pages/Account/Manage/Disable2fa.cshtml.cs | 20 ++- .../Manage/DownloadPersonalData.cshtml.cs | 16 +- .../Manage/EnableAuthenticator.cshtml.cs | 48 ++--- .../Account/Manage/ExternalLogins.cshtml.cs | 46 +++-- .../Manage/GenerateRecoveryCodes.cshtml.cs | 26 ++- .../Pages/Account/Manage/Index.cshtml.cs | 49 ++++-- .../Pages/Account/Manage/ManageNavPages.cs | 2 +- .../Account/Manage/PersonalData.cshtml.cs | 17 +- .../Manage/ResetAuthenticator.cshtml.cs | 21 ++- .../Account/Manage/SetPassword.cshtml.cs | 38 ++-- .../Manage/ShowRecoveryCodes.cshtml.cs | 2 +- .../Manage/TwoFactorAuthentication.cshtml.cs | 38 ++-- .../Pages/Account/Manage/_ManageNav.cshtml | 9 +- .../Pages/Account/Manage/_ViewImports.cshtml | 2 +- .../Identity/Pages/Account/Register.cshtml.cs | 53 +++--- .../Pages/Account/ResetPassword.cshtml.cs | 26 ++- .../ResetPasswordConfirmation.cshtml.cs | 2 +- .../Pages/Account/_ViewImports.cshtml | 2 +- .../Areas/Identity/Pages/_ViewImports.cshtml | 2 +- src/UI/IdentityBuilderUIExtensions.cs | 4 +- src/UI/IdentityDefaultUIAttribute.cs | 18 ++ src/UI/IdentityDefaultUIConfigureOptions.cs | 14 +- src/UI/IdentityPageModelConvention.cs | 43 +++++ 51 files changed, 1170 insertions(+), 473 deletions(-) create mode 100644 samples/IdentitySample.DefaultUI/Areas/Identity/Pages/Account/Manage/Index.cshtml create mode 100644 samples/IdentitySample.DefaultUI/Areas/Identity/Pages/Account/Manage/Index.cshtml.cs create mode 100644 samples/IdentitySample.DefaultUI/Areas/Identity/Pages/Account/Register.cshtml create mode 100644 samples/IdentitySample.DefaultUI/Areas/Identity/Pages/Account/Register.cshtml.cs create mode 100644 samples/IdentitySample.DefaultUI/Areas/Identity/Pages/_ViewImports.cshtml create mode 100644 samples/IdentitySample.DefaultUI/Data/ApplicationDbContext.cs create mode 100644 samples/IdentitySample.DefaultUI/Data/ApplicationUser.cs rename samples/IdentitySample.DefaultUI/Data/Migrations/{20171220235730_CreateIdentitySchema.Designer.cs => 20180126174859_CreateIdentitySchema.Designer.cs} (91%) rename samples/IdentitySample.DefaultUI/Data/Migrations/{20171220235730_CreateIdentitySchema.cs => 20180126174859_CreateIdentitySchema.cs} (98%) rename samples/IdentitySample.DefaultUI/Data/Migrations/{IdentityDbContextModelSnapshot.cs => ApplicationDbContextModelSnapshot.cs} (91%) create mode 100644 src/UI/Areas/Identity/Filters/ExternalLoginsPageFilter.cs create mode 100644 src/UI/IdentityDefaultUIAttribute.cs create mode 100644 src/UI/IdentityPageModelConvention.cs diff --git a/build/dependencies.props b/build/dependencies.props index d485f96dd6..da8e5db641 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -3,66 +3,66 @@ $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - 2.1.0-preview1-15679 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 + 2.1.0-preview2-15684 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 2.2.1 2.3.2 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 - 2.1.0-preview1-28153 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 + 2.1.0-preview1-28179 3.14.2 5.2.0-preview2-41113220915 2.0.0 - 2.1.0-preview1-26115-03 + 2.1.0-preview1-26122-01 15.3.0 3.0.1 - 2.1.0-preview1-28153 + 2.1.0-preview1-28179 4.7.49 - 4.5.0-preview1-26112-01 + 4.5.0-preview1-26119-06 0.8.0 2.3.1 2.3.1 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index a474bc0e35..1ae5d69845 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.1.0-preview1-15679 -commithash:5347461137cb45a77ddcc0b55b2478092de43338 +version:2.1.0-preview2-15684 +commithash:308af8e3279892aad7f189ffbddd3ff9a60970d3 diff --git a/samples/IdentitySample.DefaultUI/Areas/Identity/Pages/Account/Manage/Index.cshtml b/samples/IdentitySample.DefaultUI/Areas/Identity/Pages/Account/Manage/Index.cshtml new file mode 100644 index 0000000000..3f15aaca23 --- /dev/null +++ b/samples/IdentitySample.DefaultUI/Areas/Identity/Pages/Account/Manage/Index.cshtml @@ -0,0 +1,53 @@ +@page +@model IndexModel +@{ + ViewData["Title"] = "Profile"; +} + +

@ViewData["Title"]

+@Html.Partial("_StatusMessage", Model.StatusMessage) +
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + @if (Model.IsEmailConfirmed) + { +
+ + +
+} +else +{ + + +} + +
+
+ + + +
+ +
+
+
+ +@section Scripts { + +} diff --git a/samples/IdentitySample.DefaultUI/Areas/Identity/Pages/Account/Manage/Index.cshtml.cs b/samples/IdentitySample.DefaultUI/Areas/Identity/Pages/Account/Manage/Index.cshtml.cs new file mode 100644 index 0000000000..6ca6705ca5 --- /dev/null +++ b/samples/IdentitySample.DefaultUI/Areas/Identity/Pages/Account/Manage/Index.cshtml.cs @@ -0,0 +1,164 @@ +// 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.ComponentModel.DataAnnotations; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using IdentitySample.DefaultUI.Data; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.UI.Services; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace IdentitySample.DefaultUI +{ + public class IndexModel : PageModel + { + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; + private readonly IEmailSender _emailSender; + + public IndexModel( + UserManager userManager, + SignInManager signInManager, + IEmailSender emailSender) + { + _userManager = userManager; + _signInManager = signInManager; + _emailSender = emailSender; + } + + public string Username { get; set; } + + public bool IsEmailConfirmed { get; set; } + + [TempData] + public string StatusMessage { get; set; } + + [BindProperty] + public InputModel Input { get; set; } + + public class InputModel + { + [Required] + [EmailAddress] + public string Email { get; set; } + + [Phone] + [Display(Name = "Phone number")] + public string PhoneNumber { get; set; } + + [Required] + [DataType(DataType.Text)] + [Display(Name = "Full Name")] + public string Name { get; set; } + + [Required] + [Range(0, 199, ErrorMessage = "Age must be between 0 and 199")] + [Display(Name = "Age")] + public int Age { get; set; } + } + + public async Task OnGetAsync() + { + var user = await _userManager.GetUserAsync(User); + if (user == null) + { + return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); + } + + Username = user.UserName; + Input = new InputModel + { + Name = user.Name, + Age = user.Age, + Email = user.Email, + PhoneNumber = user.PhoneNumber + }; + + IsEmailConfirmed = await _userManager.IsEmailConfirmedAsync(user); + + return Page(); + } + + public async Task OnPostAsync() + { + if (!ModelState.IsValid) + { + return Page(); + } + + var user = await _userManager.GetUserAsync(User); + if (user == null) + { + return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); + } + + if (Input.Name != user.Name) + { + user.Name = Input.Name; + } + + if (Input.Age != user.Age) + { + user.Age = Input.Age; + } + + var updateProfileResult = await _userManager.UpdateAsync(user); + if (!updateProfileResult.Succeeded) + { + throw new InvalidOperationException($"Unexpected error ocurred updating the profile for user with ID '{user.Id}'"); + } + + if (Input.Email != user.Email) + { + var setEmailResult = await _userManager.SetEmailAsync(user, Input.Email); + if (!setEmailResult.Succeeded) + { + throw new InvalidOperationException($"Unexpected error occurred setting email for user with ID '{user.Id}'."); + } + } + + if (Input.PhoneNumber != user.PhoneNumber) + { + var setPhoneResult = await _userManager.SetPhoneNumberAsync(user, Input.PhoneNumber); + if (!setPhoneResult.Succeeded) + { + throw new InvalidOperationException($"Unexpected error occurred setting phone number for user with ID '{user.Id}'."); + } + } + + StatusMessage = "Your profile has been updated"; + return RedirectToPage(); + } + + public async Task OnPostSendVerificationEmailAsync() + { + if (!ModelState.IsValid) + { + return Page(); + } + + var user = await _userManager.GetUserAsync(User); + if (user == null) + { + return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); + } + + var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); + var callbackUrl = Url.Page( + "/Account/ConfirmEmail", + pageHandler: null, + values: new { user.Id, code }, + protocol: Request.Scheme); + await _emailSender.SendEmailAsync( + user.Email, + "Confirm your email", + $"Please confirm your account by clicking here."); + + StatusMessage = "Verification email sent. Please check your email."; + return RedirectToPage(); + } + } +} diff --git a/samples/IdentitySample.DefaultUI/Areas/Identity/Pages/Account/Register.cshtml b/samples/IdentitySample.DefaultUI/Areas/Identity/Pages/Account/Register.cshtml new file mode 100644 index 0000000000..259373e966 --- /dev/null +++ b/samples/IdentitySample.DefaultUI/Areas/Identity/Pages/Account/Register.cshtml @@ -0,0 +1,47 @@ +@page +@model RegisterModel +@{ + ViewData["Title"] = "Register"; +} + +

@ViewData["Title"]

+ +
+
+
+

Create a new account.

+
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+ +
+
+
+ +@section Scripts { + +} diff --git a/samples/IdentitySample.DefaultUI/Areas/Identity/Pages/Account/Register.cshtml.cs b/samples/IdentitySample.DefaultUI/Areas/Identity/Pages/Account/Register.cshtml.cs new file mode 100644 index 0000000000..35cb402bb8 --- /dev/null +++ b/samples/IdentitySample.DefaultUI/Areas/Identity/Pages/Account/Register.cshtml.cs @@ -0,0 +1,115 @@ +// 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.ComponentModel.DataAnnotations; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using IdentitySample.DefaultUI.Data; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.UI.Services; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.Extensions.Logging; + +namespace IdentitySample.DefaultUI +{ + public class RegisterModel : PageModel + { + private readonly SignInManager _signInManager; + private readonly UserManager _userManager; + private readonly ILogger _logger; + private readonly IEmailSender _emailSender; + + public RegisterModel( + UserManager userManager, + SignInManager signInManager, + ILogger logger, + IEmailSender emailSender) + { + _userManager = userManager; + _signInManager = signInManager; + _logger = logger; + _emailSender = emailSender; + } + + [BindProperty] + public InputModel Input { get; set; } + + public string ReturnUrl { get; set; } + + public class InputModel + { + [Required] + [EmailAddress] + [Display(Name = "Email")] + public string Email { get; set; } + + [Required] + [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] + [DataType(DataType.Password)] + [Display(Name = "Password")] + public string Password { get; set; } + + [DataType(DataType.Password)] + [Display(Name = "Confirm password")] + [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] + public string ConfirmPassword { get; set; } + + [Required] + [DataType(DataType.Text)] + [Display(Name = "Full name")] + public string Name { get; set; } + + [Required] + [Range(0,199, ErrorMessage = "Age must be between 0 and 199 years")] + [Display(Name = "Age")] + public int Age { get; set; } + } + + public void OnGet(string returnUrl = null) + { + ReturnUrl = returnUrl; + } + + public async Task OnPostAsync(string returnUrl = null) + { + returnUrl = returnUrl ?? Url.Content("~/"); + if (ModelState.IsValid) + { + var user = new ApplicationUser { + UserName = Input.Email, + Email = Input.Email, + Name = Input.Name, + Age = Input.Age + }; + + var result = await _userManager.CreateAsync(user, Input.Password); + if (result.Succeeded) + { + _logger.LogInformation("User created a new account with password."); + + var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); + var callbackUrl = Url.Page( + "/Account/ConfirmEmail", + pageHandler: null, + values: new { userId = user.Id, code = code }, + protocol: Request.Scheme); + + await _emailSender.SendEmailAsync(Input.Email, "Confirm your email", + $"Please confirm your account by clicking here."); + + await _signInManager.SignInAsync(user, isPersistent: false); + return LocalRedirect(returnUrl); + } + foreach (var error in result.Errors) + { + ModelState.AddModelError(string.Empty, error.Description); + } + } + + // If we got this far, something failed, redisplay form + return Page(); + } + } +} diff --git a/samples/IdentitySample.DefaultUI/Areas/Identity/Pages/_ViewImports.cshtml b/samples/IdentitySample.DefaultUI/Areas/Identity/Pages/_ViewImports.cshtml new file mode 100644 index 0000000000..777abd9e19 --- /dev/null +++ b/samples/IdentitySample.DefaultUI/Areas/Identity/Pages/_ViewImports.cshtml @@ -0,0 +1,2 @@ +@namespace IdentitySample.DefaultUI +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/samples/IdentitySample.DefaultUI/Data/ApplicationDbContext.cs b/samples/IdentitySample.DefaultUI/Data/ApplicationDbContext.cs new file mode 100644 index 0000000000..1e96af6bee --- /dev/null +++ b/samples/IdentitySample.DefaultUI/Data/ApplicationDbContext.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + +namespace IdentitySample.DefaultUI.Data +{ + public class ApplicationDbContext : IdentityDbContext + { + public ApplicationDbContext(DbContextOptions options) : base(options) + { + } + } +} diff --git a/samples/IdentitySample.DefaultUI/Data/ApplicationUser.cs b/samples/IdentitySample.DefaultUI/Data/ApplicationUser.cs new file mode 100644 index 0000000000..134677ce4f --- /dev/null +++ b/samples/IdentitySample.DefaultUI/Data/ApplicationUser.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; + +namespace IdentitySample.DefaultUI.Data +{ + public class ApplicationUser : IdentityUser + { + public string Name { get; set; } + public int Age { get; set; } + } +} diff --git a/samples/IdentitySample.DefaultUI/Data/Migrations/20171220235730_CreateIdentitySchema.Designer.cs b/samples/IdentitySample.DefaultUI/Data/Migrations/20180126174859_CreateIdentitySchema.Designer.cs similarity index 91% rename from samples/IdentitySample.DefaultUI/Data/Migrations/20171220235730_CreateIdentitySchema.Designer.cs rename to samples/IdentitySample.DefaultUI/Data/Migrations/20180126174859_CreateIdentitySchema.Designer.cs index 9d4043e024..acee5cb362 100644 --- a/samples/IdentitySample.DefaultUI/Data/Migrations/20171220235730_CreateIdentitySchema.Designer.cs +++ b/samples/IdentitySample.DefaultUI/Data/Migrations/20180126174859_CreateIdentitySchema.Designer.cs @@ -1,26 +1,82 @@ // using System; -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using IdentitySample.DefaultUI.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Storage.Internal; namespace IdentitySample.DefaultUI.Data.Migrations { - [DbContext(typeof(IdentityDbContext))] - [Migration("20171220235730_CreateIdentitySchema")] + [DbContext(typeof(ApplicationDbContext))] + [Migration("20180126174859_CreateIdentitySchema")] partial class CreateIdentitySchema { protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "2.1.0-preview1-27965") + .HasAnnotation("ProductVersion", "2.1.0-preview1-28153") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + modelBuilder.Entity("IdentitySample.DefaultUI.Data.ApplicationUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("Age"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("Name"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => { b.Property("Id") @@ -64,57 +120,6 @@ namespace IdentitySample.DefaultUI.Data.Migrations b.ToTable("AspNetRoleClaims"); }); - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("AccessFailedCount"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken(); - - b.Property("Email") - .HasMaxLength(256); - - b.Property("EmailConfirmed"); - - b.Property("LockoutEnabled"); - - b.Property("LockoutEnd"); - - b.Property("NormalizedEmail") - .HasMaxLength(256); - - b.Property("NormalizedUserName") - .HasMaxLength(256); - - b.Property("PasswordHash"); - - b.Property("PhoneNumber"); - - b.Property("PhoneNumberConfirmed"); - - b.Property("SecurityStamp"); - - b.Property("TwoFactorEnabled"); - - b.Property("UserName") - .HasMaxLength(256); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasName("UserNameIndex") - .HasFilter("[NormalizedUserName] IS NOT NULL"); - - b.ToTable("AspNetUsers"); - }); - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => { b.Property("Id") @@ -194,7 +199,7 @@ namespace IdentitySample.DefaultUI.Data.Migrations modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") + b.HasOne("IdentitySample.DefaultUI.Data.ApplicationUser") .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade); @@ -202,7 +207,7 @@ namespace IdentitySample.DefaultUI.Data.Migrations modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") + b.HasOne("IdentitySample.DefaultUI.Data.ApplicationUser") .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade); @@ -215,7 +220,7 @@ namespace IdentitySample.DefaultUI.Data.Migrations .HasForeignKey("RoleId") .OnDelete(DeleteBehavior.Cascade); - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") + b.HasOne("IdentitySample.DefaultUI.Data.ApplicationUser") .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade); @@ -223,7 +228,7 @@ namespace IdentitySample.DefaultUI.Data.Migrations modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") + b.HasOne("IdentitySample.DefaultUI.Data.ApplicationUser") .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade); diff --git a/samples/IdentitySample.DefaultUI/Data/Migrations/20171220235730_CreateIdentitySchema.cs b/samples/IdentitySample.DefaultUI/Data/Migrations/20180126174859_CreateIdentitySchema.cs similarity index 98% rename from samples/IdentitySample.DefaultUI/Data/Migrations/20171220235730_CreateIdentitySchema.cs rename to samples/IdentitySample.DefaultUI/Data/Migrations/20180126174859_CreateIdentitySchema.cs index fcf45f7c3e..713e581f6d 100644 --- a/samples/IdentitySample.DefaultUI/Data/Migrations/20171220235730_CreateIdentitySchema.cs +++ b/samples/IdentitySample.DefaultUI/Data/Migrations/20180126174859_CreateIdentitySchema.cs @@ -41,7 +41,9 @@ namespace IdentitySample.DefaultUI.Data.Migrations TwoFactorEnabled = table.Column(nullable: false), LockoutEnd = table.Column(nullable: true), LockoutEnabled = table.Column(nullable: false), - AccessFailedCount = table.Column(nullable: false) + AccessFailedCount = table.Column(nullable: false), + Name = table.Column(nullable: true), + Age = table.Column(nullable: false) }, constraints: table => { diff --git a/samples/IdentitySample.DefaultUI/Data/Migrations/IdentityDbContextModelSnapshot.cs b/samples/IdentitySample.DefaultUI/Data/Migrations/ApplicationDbContextModelSnapshot.cs similarity index 91% rename from samples/IdentitySample.DefaultUI/Data/Migrations/IdentityDbContextModelSnapshot.cs rename to samples/IdentitySample.DefaultUI/Data/Migrations/ApplicationDbContextModelSnapshot.cs index 97f59eff98..8eb1302cd7 100644 --- a/samples/IdentitySample.DefaultUI/Data/Migrations/IdentityDbContextModelSnapshot.cs +++ b/samples/IdentitySample.DefaultUI/Data/Migrations/ApplicationDbContextModelSnapshot.cs @@ -1,25 +1,81 @@ // using System; -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using IdentitySample.DefaultUI.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Storage.Internal; namespace IdentitySample.DefaultUI.Data.Migrations { - [DbContext(typeof(IdentityDbContext))] - partial class IdentityDbContextModelSnapshot : ModelSnapshot + [DbContext(typeof(ApplicationDbContext))] + partial class ApplicationDbContextModelSnapshot : ModelSnapshot { protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "2.1.0-preview1-27965") + .HasAnnotation("ProductVersion", "2.1.0-preview1-28153") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + modelBuilder.Entity("IdentitySample.DefaultUI.Data.ApplicationUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("Age"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("Name"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => { b.Property("Id") @@ -63,57 +119,6 @@ namespace IdentitySample.DefaultUI.Data.Migrations b.ToTable("AspNetRoleClaims"); }); - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("AccessFailedCount"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken(); - - b.Property("Email") - .HasMaxLength(256); - - b.Property("EmailConfirmed"); - - b.Property("LockoutEnabled"); - - b.Property("LockoutEnd"); - - b.Property("NormalizedEmail") - .HasMaxLength(256); - - b.Property("NormalizedUserName") - .HasMaxLength(256); - - b.Property("PasswordHash"); - - b.Property("PhoneNumber"); - - b.Property("PhoneNumberConfirmed"); - - b.Property("SecurityStamp"); - - b.Property("TwoFactorEnabled"); - - b.Property("UserName") - .HasMaxLength(256); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasName("UserNameIndex") - .HasFilter("[NormalizedUserName] IS NOT NULL"); - - b.ToTable("AspNetUsers"); - }); - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => { b.Property("Id") @@ -193,7 +198,7 @@ namespace IdentitySample.DefaultUI.Data.Migrations modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") + b.HasOne("IdentitySample.DefaultUI.Data.ApplicationUser") .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade); @@ -201,7 +206,7 @@ namespace IdentitySample.DefaultUI.Data.Migrations modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") + b.HasOne("IdentitySample.DefaultUI.Data.ApplicationUser") .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade); @@ -214,7 +219,7 @@ namespace IdentitySample.DefaultUI.Data.Migrations .HasForeignKey("RoleId") .OnDelete(DeleteBehavior.Cascade); - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") + b.HasOne("IdentitySample.DefaultUI.Data.ApplicationUser") .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade); @@ -222,7 +227,7 @@ namespace IdentitySample.DefaultUI.Data.Migrations modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") + b.HasOne("IdentitySample.DefaultUI.Data.ApplicationUser") .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade); diff --git a/samples/IdentitySample.DefaultUI/Startup.cs b/samples/IdentitySample.DefaultUI/Startup.cs index 65c9629e73..505c7f0490 100644 --- a/samples/IdentitySample.DefaultUI/Startup.cs +++ b/samples/IdentitySample.DefaultUI/Startup.cs @@ -1,3 +1,4 @@ +using IdentitySample.DefaultUI.Data; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Identity; @@ -28,12 +29,12 @@ namespace IdentitySample.DefaultUI public void ConfigureServices(IServiceCollection services) { // Add framework services. - services.AddDbContext( + services.AddDbContext( options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"), x => x.MigrationsAssembly("IdentitySample.DefaultUI"))); - services.AddIdentity(o => o.Stores.MaxLengthForKeys = 128) - .AddEntityFrameworkStores() + services.AddIdentity(o => o.Stores.MaxLengthForKeys = 128) + .AddEntityFrameworkStores() .AddDefaultUI() .AddDefaultTokenProviders(); diff --git a/samples/IdentitySample.DefaultUI/Views/Shared/_LoginPartial.cshtml b/samples/IdentitySample.DefaultUI/Views/Shared/_LoginPartial.cshtml index 96228c799e..3e8d8f66d3 100644 --- a/samples/IdentitySample.DefaultUI/Views/Shared/_LoginPartial.cshtml +++ b/samples/IdentitySample.DefaultUI/Views/Shared/_LoginPartial.cshtml @@ -1,5 +1,6 @@ -@inject SignInManager SignInManager -@inject UserManager UserManager +@using IdentitySample.DefaultUI.Data +@inject SignInManager SignInManager +@inject UserManager UserManager @if (SignInManager.IsSignedIn(User)) { diff --git a/samples/IdentitySample.DefaultUI/appsettings.json b/samples/IdentitySample.DefaultUI/appsettings.json index 46fe46e66a..9adcb1012d 100644 --- a/samples/IdentitySample.DefaultUI/appsettings.json +++ b/samples/IdentitySample.DefaultUI/appsettings.json @@ -1,6 +1,6 @@ { "ConnectionStrings": { - "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-IdentitySample.DefaultUI-2ff9bc27-5e8c-4484-90ca-e3aace89b72a;Trusted_Connection=True;MultipleActiveResultSets=true" + "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-IdentitySample.DefaultUI-d97faff4-1cfe-4c5d-b031-aa23aeb03a5e;Trusted_Connection=True;MultipleActiveResultSets=true" }, "Logging": { "IncludeScopes": false, diff --git a/src/UI/Areas/Identity/Filters/ExternalLoginsPageFilter.cs b/src/UI/Areas/Identity/Filters/ExternalLoginsPageFilter.cs new file mode 100644 index 0000000000..75719adc01 --- /dev/null +++ b/src/UI/Areas/Identity/Filters/ExternalLoginsPageFilter.cs @@ -0,0 +1,32 @@ +// 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.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Identity.UI.Areas.Identity.Filters +{ + internal class ExternalLoginsPageFilter : IAsyncPageFilter where TUser : class + { + public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next) + { + var result = await next(); + if (result.Result is PageResult page) + { + var signInManager = context.HttpContext.RequestServices.GetRequiredService>(); + var schemes = await signInManager.GetExternalAuthenticationSchemesAsync(); + var hasExternalLogins = schemes.Any(); + + page.ViewData["ManageNav.HasExternalLogins"] = hasExternalLogins; + } + } + + public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context) + { + return Task.CompletedTask; + } + } +} diff --git a/src/UI/Areas/Identity/Pages/Account/AccessDenied.cshtml.cs b/src/UI/Areas/Identity/Pages/Account/AccessDenied.cshtml.cs index 215aacc344..0d0399307d 100644 --- a/src/UI/Areas/Identity/Pages/Account/AccessDenied.cshtml.cs +++ b/src/UI/Areas/Identity/Pages/Account/AccessDenied.cshtml.cs @@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Mvc.RazorPages; -namespace Microsoft.AspNetCore.Identity.UI.Pages.Account +namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal { public class AccessDeniedModel : PageModel { diff --git a/src/UI/Areas/Identity/Pages/Account/ConfirmEmail.cshtml.cs b/src/UI/Areas/Identity/Pages/Account/ConfirmEmail.cshtml.cs index e31499f856..8ad4f34427 100644 --- a/src/UI/Areas/Identity/Pages/Account/ConfirmEmail.cshtml.cs +++ b/src/UI/Areas/Identity/Pages/Account/ConfirmEmail.cshtml.cs @@ -6,18 +6,24 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; -namespace Microsoft.AspNetCore.Identity.UI.Pages.Account +namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal { - public class ConfirmEmailModel : PageModel + [IdentityDefaultUI(typeof(ConfirmEmailModel<>))] + public abstract class ConfirmEmailModel : PageModel { - private readonly UserManager _userManager; + public virtual Task OnGetAsync(string userId, string code) => throw new NotImplementedException(); + } - public ConfirmEmailModel(UserManager userManager) + internal class ConfirmEmailModel : ConfirmEmailModel where TUser : IdentityUser + { + private readonly UserManager _userManager; + + public ConfirmEmailModel(UserManager userManager) { _userManager = userManager; } - public async Task OnGetAsync(string userId, string code) + public override async Task OnGetAsync(string userId, string code) { if (userId == null || code == null) { diff --git a/src/UI/Areas/Identity/Pages/Account/ExternalLogin.cshtml.cs b/src/UI/Areas/Identity/Pages/Account/ExternalLogin.cshtml.cs index 85b015a642..8a04c751b7 100644 --- a/src/UI/Areas/Identity/Pages/Account/ExternalLogin.cshtml.cs +++ b/src/UI/Areas/Identity/Pages/Account/ExternalLogin.cshtml.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.ComponentModel.DataAnnotations; using System.Security.Claims; using System.Threading.Tasks; @@ -8,24 +9,11 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.Logging; -namespace Microsoft.AspNetCore.Identity.UI.Pages.Account +namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal { + [IdentityDefaultUI(typeof(ExternalLoginModel<>))] public class ExternalLoginModel : PageModel { - private readonly SignInManager _signInManager; - private readonly UserManager _userManager; - private readonly ILogger _logger; - - public ExternalLoginModel( - SignInManager signInManager, - UserManager userManager, - ILogger logger) - { - _signInManager = signInManager; - _userManager = userManager; - _logger = logger; - } - [BindProperty] public InputModel Input { get; set; } @@ -43,12 +31,37 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account public string Email { get; set; } } - public IActionResult OnGetAsync() + public virtual IActionResult OnGet() => throw new NotImplementedException(); + + public virtual IActionResult OnPost(string provider, string returnUrl = null) => throw new NotImplementedException(); + + public virtual Task OnGetCallbackAsync(string returnUrl = null, string remoteError = null) => throw new NotImplementedException(); + + public virtual Task OnPostConfirmationAsync(string returnUrl = null) => throw new NotImplementedException(); + } + + internal class ExternalLoginModel : ExternalLoginModel where TUser : IdentityUser, new() + { + private readonly SignInManager _signInManager; + private readonly UserManager _userManager; + private readonly ILogger _logger; + + public ExternalLoginModel( + SignInManager signInManager, + UserManager userManager, + ILogger logger) + { + _signInManager = signInManager; + _userManager = userManager; + _logger = logger; + } + + public override IActionResult OnGet() { return RedirectToPage("./Login"); } - public IActionResult OnPost(string provider, string returnUrl = null) + public override IActionResult OnPost(string provider, string returnUrl = null) { // Request a redirect to the external login provider. var redirectUrl = Url.Page("./ExternalLogin", pageHandler: "Callback", values: new { returnUrl }); @@ -56,7 +69,7 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account return new ChallengeResult(provider, properties); } - public async Task OnGetCallbackAsync(string returnUrl = null, string remoteError = null) + public override async Task OnGetCallbackAsync(string returnUrl = null, string remoteError = null) { returnUrl = returnUrl ?? Url.Content("~/"); if (remoteError != null) @@ -98,20 +111,20 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account } } - public async Task OnPostConfirmationAsync(string returnUrl = null) + public override async Task OnPostConfirmationAsync(string returnUrl = null) { returnUrl = returnUrl ?? Url.Content("~/"); // Get the information about the user from the external login provider var info = await _signInManager.GetExternalLoginInfoAsync(); if (info == null) { - ErrorMessage = "Error loading external login information during confirmation."; - return RedirectToPage("./Login", new { ReturnUrl = returnUrl }); + ErrorMessage = "Error loading external login information during confirmation."; + return RedirectToPage("./Login", new { ReturnUrl = returnUrl }); } if (ModelState.IsValid) { - var user = new IdentityUser { UserName = Input.Email, Email = Input.Email }; + var user = new TUser { UserName = Input.Email, Email = Input.Email }; var result = await _userManager.CreateAsync(user); if (result.Succeeded) { diff --git a/src/UI/Areas/Identity/Pages/Account/ForgotPassword.cshtml.cs b/src/UI/Areas/Identity/Pages/Account/ForgotPassword.cshtml.cs index d41f2e9769..ef7dd95b92 100644 --- a/src/UI/Areas/Identity/Pages/Account/ForgotPassword.cshtml.cs +++ b/src/UI/Areas/Identity/Pages/Account/ForgotPassword.cshtml.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.ComponentModel.DataAnnotations; using System.Text.Encodings.Web; using System.Threading.Tasks; @@ -8,19 +9,11 @@ using Microsoft.AspNetCore.Identity.UI.Services; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; -namespace Microsoft.AspNetCore.Identity.UI.Pages.Account +namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal { - public class ForgotPasswordModel : PageModel + [IdentityDefaultUI(typeof(ForgotPasswordModel<>))] + public abstract class ForgotPasswordModel : PageModel { - private readonly UserManager _userManager; - private readonly IEmailSender _emailSender; - - public ForgotPasswordModel(UserManager userManager, IEmailSender emailSender) - { - _userManager = userManager; - _emailSender = emailSender; - } - [BindProperty] public InputModel Input { get; set; } @@ -31,7 +24,21 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account public string Email { get; set; } } - public async Task OnPostAsync() + public virtual Task OnPostAsync() => throw new NotImplementedException(); + } + + internal class ForgotPasswordModel : ForgotPasswordModel where TUser : IdentityUser + { + private readonly UserManager _userManager; + private readonly IEmailSender _emailSender; + + public ForgotPasswordModel(UserManager userManager, IEmailSender emailSender) + { + _userManager = userManager; + _emailSender = emailSender; + } + + public override async Task OnPostAsync() { if (ModelState.IsValid) { diff --git a/src/UI/Areas/Identity/Pages/Account/ForgotPasswordConfirmation.cshtml.cs b/src/UI/Areas/Identity/Pages/Account/ForgotPasswordConfirmation.cshtml.cs index 4004302446..77bdaa352c 100644 --- a/src/UI/Areas/Identity/Pages/Account/ForgotPasswordConfirmation.cshtml.cs +++ b/src/UI/Areas/Identity/Pages/Account/ForgotPasswordConfirmation.cshtml.cs @@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Mvc.RazorPages; -namespace Microsoft.AspNetCore.Identity.UI.Pages.Account +namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal { public class ForgotPasswordConfirmation : PageModel { diff --git a/src/UI/Areas/Identity/Pages/Account/Lockout.cshtml.cs b/src/UI/Areas/Identity/Pages/Account/Lockout.cshtml.cs index e9cd1274ad..33a13a0ced 100644 --- a/src/UI/Areas/Identity/Pages/Account/Lockout.cshtml.cs +++ b/src/UI/Areas/Identity/Pages/Account/Lockout.cshtml.cs @@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Mvc.RazorPages; -namespace Microsoft.AspNetCore.Identity.UI.Pages.Account +namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal { public class LockoutModel : PageModel { diff --git a/src/UI/Areas/Identity/Pages/Account/Login.cshtml.cs b/src/UI/Areas/Identity/Pages/Account/Login.cshtml.cs index 3df8a0e1e6..d8a55a740a 100644 --- a/src/UI/Areas/Identity/Pages/Account/Login.cshtml.cs +++ b/src/UI/Areas/Identity/Pages/Account/Login.cshtml.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.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; @@ -10,19 +11,11 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.Logging; -namespace Microsoft.AspNetCore.Identity.UI.Pages.Account +namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal { - public class LoginModel : PageModel + [IdentityDefaultUI(typeof(LoginModel<>))] + public abstract class LoginModel : PageModel { - private readonly SignInManager _signInManager; - private readonly ILogger _logger; - - public LoginModel(SignInManager signInManager, ILogger logger) - { - _signInManager = signInManager; - _logger = logger; - } - [BindProperty] public InputModel Input { get; set; } @@ -47,7 +40,23 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account public bool RememberMe { get; set; } } - public async Task OnGetAsync(string returnUrl = null) + public virtual Task OnGetAsync(string returnUrl = null) => throw new NotImplementedException(); + + public virtual Task OnPostAsync(string returnUrl = null) => throw new NotImplementedException(); + } + + internal class LoginModel : LoginModel where TUser : IdentityUser + { + private readonly SignInManager _signInManager; + private readonly ILogger _logger; + + public LoginModel(SignInManager signInManager, ILogger logger) + { + _signInManager = signInManager; + _logger = logger; + } + + public override async Task OnGetAsync(string returnUrl = null) { if (!string.IsNullOrEmpty(ErrorMessage)) { @@ -64,7 +73,7 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account ReturnUrl = returnUrl; } - public async Task OnPostAsync(string returnUrl = null) + public override async Task OnPostAsync(string returnUrl = null) { returnUrl = returnUrl ?? Url.Content("~/"); diff --git a/src/UI/Areas/Identity/Pages/Account/LoginWith2fa.cshtml.cs b/src/UI/Areas/Identity/Pages/Account/LoginWith2fa.cshtml.cs index fa2a15e5c8..cd605793fd 100644 --- a/src/UI/Areas/Identity/Pages/Account/LoginWith2fa.cshtml.cs +++ b/src/UI/Areas/Identity/Pages/Account/LoginWith2fa.cshtml.cs @@ -8,19 +8,11 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.Logging; -namespace Microsoft.AspNetCore.Identity.UI.Pages.Account +namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal { - public class LoginWith2faModel : PageModel + [IdentityDefaultUI(typeof(LoginWith2faModel<>))] + public abstract class LoginWith2faModel : PageModel { - private readonly SignInManager _signInManager; - private readonly ILogger _logger; - - public LoginWith2faModel(SignInManager signInManager, ILogger logger) - { - _signInManager = signInManager; - _logger = logger; - } - [BindProperty] public InputModel Input { get; set; } @@ -40,7 +32,23 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account public bool RememberMachine { get; set; } } - public async Task OnGetAsync(bool rememberMe, string returnUrl = null) + public virtual Task OnGetAsync(bool rememberMe, string returnUrl = null) => throw new NotImplementedException(); + + public virtual Task OnPostAsync(bool rememberMe, string returnUrl = null) => throw new NotImplementedException(); + } + + internal class LoginWith2faModel : LoginWith2faModel where TUser : IdentityUser + { + private readonly SignInManager _signInManager; + private readonly ILogger _logger; + + public LoginWith2faModel(SignInManager signInManager, ILogger logger) + { + _signInManager = signInManager; + _logger = logger; + } + + public override async Task OnGetAsync(bool rememberMe, string returnUrl = null) { // Ensure the user has gone through the username & password screen first var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); @@ -56,7 +64,7 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account return Page(); } - public async Task OnPostAsync(bool rememberMe, string returnUrl = null) + public override async Task OnPostAsync(bool rememberMe, string returnUrl = null) { if (!ModelState.IsValid) { @@ -91,6 +99,6 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account ModelState.AddModelError(string.Empty, "Invalid authenticator code."); return Page(); } - } + } } } diff --git a/src/UI/Areas/Identity/Pages/Account/LoginWithRecoveryCode.cshtml.cs b/src/UI/Areas/Identity/Pages/Account/LoginWithRecoveryCode.cshtml.cs index 449ab13526..128771ae78 100644 --- a/src/UI/Areas/Identity/Pages/Account/LoginWithRecoveryCode.cshtml.cs +++ b/src/UI/Areas/Identity/Pages/Account/LoginWithRecoveryCode.cshtml.cs @@ -8,19 +8,11 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.Logging; -namespace Microsoft.AspNetCore.Identity.UI.Pages.Account +namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal { - public class LoginWithRecoveryCodeModel : PageModel + [IdentityDefaultUI(typeof(LoginWithRecoveryCodeModel<>))] + public abstract class LoginWithRecoveryCodeModel : PageModel { - private readonly SignInManager _signInManager; - private readonly ILogger _logger; - - public LoginWithRecoveryCodeModel(SignInManager signInManager, ILogger logger) - { - _signInManager = signInManager; - _logger = logger; - } - [BindProperty] public InputModel Input { get; set; } @@ -35,7 +27,23 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account public string RecoveryCode { get; set; } } - public async Task OnGetAsync(string returnUrl = null) + public virtual Task OnGetAsync(string returnUrl = null) => throw new NotImplementedException(); + + public virtual Task OnPostAsync(string returnUrl = null) => throw new NotImplementedException(); + } + + internal class LoginWithRecoveryCodeModel : LoginWithRecoveryCodeModel where TUser : IdentityUser + { + private readonly SignInManager _signInManager; + private readonly ILogger _logger; + + public LoginWithRecoveryCodeModel(SignInManager signInManager, ILogger logger) + { + _signInManager = signInManager; + _logger = logger; + } + + public override async Task OnGetAsync(string returnUrl = null) { // Ensure the user has gone through the username & password screen first var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); @@ -49,7 +57,7 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account return Page(); } - public async Task OnPostAsync(string returnUrl = null) + public override async Task OnPostAsync(string returnUrl = null) { if (!ModelState.IsValid) { @@ -65,7 +73,7 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account var recoveryCode = Input.RecoveryCode.Replace(" ", string.Empty); var result = await _signInManager.TwoFactorRecoveryCodeSignInAsync(recoveryCode); - + if (result.Succeeded) { _logger.LogInformation("User with ID '{UserId}' logged in with a recovery code.", user.Id); diff --git a/src/UI/Areas/Identity/Pages/Account/Logout.cshtml.cs b/src/UI/Areas/Identity/Pages/Account/Logout.cshtml.cs index 672bc9b8b2..1ca9dbee37 100644 --- a/src/UI/Areas/Identity/Pages/Account/Logout.cshtml.cs +++ b/src/UI/Areas/Identity/Pages/Account/Logout.cshtml.cs @@ -1,29 +1,36 @@ // 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.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.Logging; -namespace Microsoft.AspNetCore.Identity.UI.Pages.Account +namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal { - public class LogoutModel : PageModel + [IdentityDefaultUI(typeof(LogoutModel<>))] + public abstract class LogoutModel : PageModel { - private readonly SignInManager _signInManager; + public void OnGet() + { + } + + public virtual Task OnPost(string returnUrl = null) => throw new NotImplementedException(); + } + + internal class LogoutModel : LogoutModel where TUser : IdentityUser + { + private readonly SignInManager _signInManager; private readonly ILogger _logger; - public LogoutModel(SignInManager signInManager, ILogger logger) + public LogoutModel(SignInManager signInManager, ILogger logger) { _signInManager = signInManager; _logger = logger; } - public void OnGet() - { - } - - public async Task OnPost(string returnUrl = null) + public override async Task OnPost(string returnUrl = null) { await _signInManager.SignOutAsync(); _logger.LogInformation("User logged out."); diff --git a/src/UI/Areas/Identity/Pages/Account/Manage/ChangePassword.cshtml.cs b/src/UI/Areas/Identity/Pages/Account/Manage/ChangePassword.cshtml.cs index 025cbad728..c44118ae9c 100644 --- a/src/UI/Areas/Identity/Pages/Account/Manage/ChangePassword.cshtml.cs +++ b/src/UI/Areas/Identity/Pages/Account/Manage/ChangePassword.cshtml.cs @@ -8,24 +8,11 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.Logging; -namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage +namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal { - public class ChangePasswordModel : PageModel + [IdentityDefaultUI(typeof(ChangePasswordModel<>))] + public abstract class ChangePasswordModel : PageModel { - private readonly UserManager _userManager; - private readonly SignInManager _signInManager; - private readonly ILogger _logger; - - public ChangePasswordModel( - UserManager userManager, - SignInManager signInManager, - ILogger logger) - { - _userManager = userManager; - _signInManager = signInManager; - _logger = logger; - } - [BindProperty] public InputModel Input { get; set; } @@ -51,7 +38,28 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage public string ConfirmPassword { get; set; } } - public async Task OnGetAsync() + public virtual Task OnGetAsync() => throw new NotImplementedException(); + + public virtual Task OnPostAsync() => throw new NotImplementedException(); + } + + internal class ChangePasswordModel : ChangePasswordModel where TUser : IdentityUser + { + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; + private readonly ILogger _logger; + + public ChangePasswordModel( + UserManager userManager, + SignInManager signInManager, + ILogger logger) + { + _userManager = userManager; + _signInManager = signInManager; + _logger = logger; + } + + public override async Task OnGetAsync() { var user = await _userManager.GetUserAsync(User); if (user == null) @@ -68,7 +76,7 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage return Page(); } - public async Task OnPostAsync() + public override async Task OnPostAsync() { if (!ModelState.IsValid) { diff --git a/src/UI/Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml.cs b/src/UI/Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml.cs index 12b227de58..7d7b074d2b 100644 --- a/src/UI/Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml.cs +++ b/src/UI/Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml.cs @@ -8,24 +8,11 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.Logging; -namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage +namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal { - public class DeletePersonalDataModel : PageModel + [IdentityDefaultUI(typeof(DeletePersonalDataModel<>))] + public abstract class DeletePersonalDataModel : PageModel { - private readonly UserManager _userManager; - private readonly SignInManager _signInManager; - private readonly ILogger _logger; - - public DeletePersonalDataModel( - UserManager userManager, - SignInManager signInManager, - ILogger logger) - { - _userManager = userManager; - _signInManager = signInManager; - _logger = logger; - } - [BindProperty] public InputModel Input { get; set; } @@ -38,7 +25,28 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage public bool RequirePassword { get; set; } - public async Task OnGet() + public virtual Task OnGet() => throw new NotImplementedException(); + + public virtual Task OnPostAsync() => throw new NotImplementedException(); + } + + internal class DeletePersonalDataModel : DeletePersonalDataModel where TUser : IdentityUser + { + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; + private readonly ILogger _logger; + + public DeletePersonalDataModel( + UserManager userManager, + SignInManager signInManager, + ILogger logger) + { + _userManager = userManager; + _signInManager = signInManager; + _logger = logger; + } + + public override async Task OnGet() { var user = await _userManager.GetUserAsync(User); if (user == null) @@ -50,7 +58,7 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage return Page(); } - public async Task OnPostAsync() + public override async Task OnPostAsync() { var user = await _userManager.GetUserAsync(User); if (user == null) diff --git a/src/UI/Areas/Identity/Pages/Account/Manage/Disable2fa.cshtml.cs b/src/UI/Areas/Identity/Pages/Account/Manage/Disable2fa.cshtml.cs index da017909d3..787f11195f 100644 --- a/src/UI/Areas/Identity/Pages/Account/Manage/Disable2fa.cshtml.cs +++ b/src/UI/Areas/Identity/Pages/Account/Manage/Disable2fa.cshtml.cs @@ -7,22 +7,30 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.Logging; -namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage +namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal { - public class Disable2faModel : PageModel + [IdentityDefaultUI(typeof(Disable2faModel<>))] + public abstract class Disable2faModel : PageModel { - private readonly UserManager _userManager; + public virtual Task OnGet() => throw new NotImplementedException(); + + public virtual Task OnPostAsync() => throw new NotImplementedException(); + } + + internal class Disable2faModel : Disable2faModel where TUser : IdentityUser + { + private readonly UserManager _userManager; private readonly ILogger _logger; public Disable2faModel( - UserManager userManager, + UserManager userManager, ILogger logger) { _userManager = userManager; _logger = logger; } - public async Task OnGet() + public override async Task OnGet() { var user = await _userManager.GetUserAsync(User); if (user == null) @@ -38,7 +46,7 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage return Page(); } - public async Task OnPostAsync() + public override async Task OnPostAsync() { var user = await _userManager.GetUserAsync(User); if (user == null) diff --git a/src/UI/Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtml.cs b/src/UI/Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtml.cs index 14cf888d8c..bfce9032ca 100644 --- a/src/UI/Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtml.cs +++ b/src/UI/Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtml.cs @@ -10,22 +10,28 @@ using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.Logging; using Newtonsoft.Json; -namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage +namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal { - public class DownloadPersonalDataModel : PageModel + [IdentityDefaultUI(typeof(DownloadPersonalDataModel<>))] + public abstract class DownloadPersonalDataModel : PageModel { - private readonly UserManager _userManager; + public virtual Task OnPostAsync() => throw new NotImplementedException(); + } + + internal class DownloadPersonalDataModel : DownloadPersonalDataModel where TUser : IdentityUser + { + private readonly UserManager _userManager; private readonly ILogger _logger; public DownloadPersonalDataModel( - UserManager userManager, + UserManager userManager, ILogger logger) { _userManager = userManager; _logger = logger; } - public async Task OnPostAsync() + public override async Task OnPostAsync() { var user = await _userManager.GetUserAsync(User); if (user == null) diff --git a/src/UI/Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml.cs b/src/UI/Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml.cs index 51ceaa5491..3a5cc99a8f 100644 --- a/src/UI/Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml.cs +++ b/src/UI/Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml.cs @@ -11,26 +11,11 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.Logging; -namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage +namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal { + [IdentityDefaultUI(typeof(EnableAuthenticatorModel<>))] public class EnableAuthenticatorModel : PageModel { - private readonly UserManager _userManager; - private readonly ILogger _logger; - private readonly UrlEncoder _urlEncoder; - - private const string AuthenticatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6"; - - public EnableAuthenticatorModel( - UserManager userManager, - ILogger logger, - UrlEncoder urlEncoder) - { - _userManager = userManager; - _logger = logger; - _urlEncoder = urlEncoder; - } - public string SharedKey { get; set; } public string AuthenticatorUri { get; set; } @@ -50,7 +35,30 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage public string Code { get; set; } } - public async Task OnGetAsync() + public virtual Task OnGetAsync() => throw new NotImplementedException(); + + public virtual Task OnPostAsync() => throw new NotImplementedException(); + } + + internal class EnableAuthenticatorModel : EnableAuthenticatorModel where TUser : IdentityUser + { + private readonly UserManager _userManager; + private readonly ILogger _logger; + private readonly UrlEncoder _urlEncoder; + + private const string AuthenticatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6"; + + public EnableAuthenticatorModel( + UserManager userManager, + ILogger logger, + UrlEncoder urlEncoder) + { + _userManager = userManager; + _logger = logger; + _urlEncoder = urlEncoder; + } + + public override async Task OnGetAsync() { var user = await _userManager.GetUserAsync(User); if (user == null) @@ -63,7 +71,7 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage return Page(); } - public async Task OnPostAsync() + public override async Task OnPostAsync() { var user = await _userManager.GetUserAsync(User); if (user == null) @@ -98,7 +106,7 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage return RedirectToPage("./ShowRecoveryCodes"); } - private async Task LoadSharedKeyAndQrCodeUriAsync(IdentityUser user) + private async Task LoadSharedKeyAndQrCodeUriAsync(TUser user) { // Load the authenticator key & QR code URI to display on the form var unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user); diff --git a/src/UI/Areas/Identity/Pages/Account/Manage/ExternalLogins.cshtml.cs b/src/UI/Areas/Identity/Pages/Account/Manage/ExternalLogins.cshtml.cs index 210f56cf0c..b03f2f9dcc 100644 --- a/src/UI/Areas/Identity/Pages/Account/Manage/ExternalLogins.cshtml.cs +++ b/src/UI/Areas/Identity/Pages/Account/Manage/ExternalLogins.cshtml.cs @@ -9,21 +9,11 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; -namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage +namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal { - public class ExternalLoginsModel : PageModel + [IdentityDefaultUI(typeof(ExternalLoginsModel<>))] + public abstract class ExternalLoginsModel : PageModel { - private readonly UserManager _userManager; - private readonly SignInManager _signInManager; - - public ExternalLoginsModel( - UserManager userManager, - SignInManager signInManager) - { - _userManager = userManager; - _signInManager = signInManager; - } - public IList CurrentLogins { get; set; } public IList OtherLogins { get; set; } @@ -33,7 +23,29 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage [TempData] public string StatusMessage { get; set; } - public async Task OnGetAsync() + public virtual Task OnGetAsync() => throw new NotImplementedException(); + + public virtual Task OnPostRemoveLoginAsync(string loginProvider, string providerKey) => throw new NotImplementedException(); + + public virtual Task OnPostLinkLoginAsync(string provider) => throw new NotImplementedException(); + + public virtual Task OnGetLinkLoginCallbackAsync() => throw new NotImplementedException(); + } + + internal class ExternalLoginsModel : ExternalLoginsModel where TUser : IdentityUser + { + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; + + public ExternalLoginsModel( + UserManager userManager, + SignInManager signInManager) + { + _userManager = userManager; + _signInManager = signInManager; + } + + public override async Task OnGetAsync() { var user = await _userManager.GetUserAsync(User); if (user == null) @@ -49,7 +61,7 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage return Page(); } - public async Task OnPostRemoveLoginAsync(string loginProvider, string providerKey) + public override async Task OnPostRemoveLoginAsync(string loginProvider, string providerKey) { var user = await _userManager.GetUserAsync(User); if (user == null) @@ -68,7 +80,7 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage return RedirectToPage(); } - public async Task OnPostLinkLoginAsync(string provider) + public override async Task OnPostLinkLoginAsync(string provider) { // Clear the existing external cookie to ensure a clean login process await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme); @@ -79,7 +91,7 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage return new ChallengeResult(provider, properties); } - public async Task OnGetLinkLoginCallbackAsync() + public override async Task OnGetLinkLoginCallbackAsync() { var user = await _userManager.GetUserAsync(User); if (user == null) diff --git a/src/UI/Areas/Identity/Pages/Account/Manage/GenerateRecoveryCodes.cshtml.cs b/src/UI/Areas/Identity/Pages/Account/Manage/GenerateRecoveryCodes.cshtml.cs index 4ff6551ca0..eca7846e3f 100644 --- a/src/UI/Areas/Identity/Pages/Account/Manage/GenerateRecoveryCodes.cshtml.cs +++ b/src/UI/Areas/Identity/Pages/Account/Manage/GenerateRecoveryCodes.cshtml.cs @@ -8,25 +8,33 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.Logging; -namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage +namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal { - public class GenerateRecoveryCodesModel : PageModel + [IdentityDefaultUI(typeof(GenerateRecoveryCodesModel<>))] + public abstract class GenerateRecoveryCodesModel : PageModel { - private readonly UserManager _userManager; + [TempData] + public string[] RecoveryCodes { get; set; } + + public virtual Task OnGetAsync() => throw new NotImplementedException(); + + public virtual Task OnPostAsync() => throw new NotImplementedException(); + } + + internal class GenerateRecoveryCodesModel : GenerateRecoveryCodesModel where TUser : IdentityUser + { + private readonly UserManager _userManager; private readonly ILogger _logger; public GenerateRecoveryCodesModel( - UserManager userManager, + UserManager userManager, ILogger logger) { _userManager = userManager; _logger = logger; } - [TempData] - public string[] RecoveryCodes { get; set; } - - public async Task OnGetAsync() + public override async Task OnGetAsync() { var user = await _userManager.GetUserAsync(User); if (user == null) @@ -42,7 +50,7 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage return Page(); } - public async Task OnPostAsync() + public override async Task OnPostAsync() { var user = await _userManager.GetUserAsync(User); if (user == null) diff --git a/src/UI/Areas/Identity/Pages/Account/Manage/Index.cshtml.cs b/src/UI/Areas/Identity/Pages/Account/Manage/Index.cshtml.cs index e6927873c7..914452d207 100644 --- a/src/UI/Areas/Identity/Pages/Account/Manage/Index.cshtml.cs +++ b/src/UI/Areas/Identity/Pages/Account/Manage/Index.cshtml.cs @@ -9,24 +9,11 @@ using Microsoft.AspNetCore.Identity.UI.Services; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; -namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage +namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal { - public partial class IndexModel : PageModel + [IdentityDefaultUI(typeof(IndexModel<>))] + public abstract class IndexModel : PageModel { - private readonly UserManager _userManager; - private readonly SignInManager _signInManager; - private readonly IEmailSender _emailSender; - - public IndexModel( - UserManager userManager, - SignInManager signInManager, - IEmailSender emailSender) - { - _userManager = userManager; - _signInManager = signInManager; - _emailSender = emailSender; - } - public string Username { get; set; } public bool IsEmailConfirmed { get; set; } @@ -48,7 +35,30 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage public string PhoneNumber { get; set; } } - public async Task OnGetAsync() + public virtual Task OnGetAsync() => throw new NotImplementedException(); + + public virtual Task OnPostAsync() => throw new NotImplementedException(); + + public virtual Task OnPostSendVerificationEmailAsync() => throw new NotImplementedException(); + } + + internal class IndexModel : IndexModel where TUser : IdentityUser + { + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; + private readonly IEmailSender _emailSender; + + public IndexModel( + UserManager userManager, + SignInManager signInManager, + IEmailSender emailSender) + { + _userManager = userManager; + _signInManager = signInManager; + _emailSender = emailSender; + } + + public override async Task OnGetAsync() { var user = await _userManager.GetUserAsync(User); if (user == null) @@ -69,7 +79,7 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage return Page(); } - public async Task OnPostAsync() + public override async Task OnPostAsync() { if (!ModelState.IsValid) { @@ -103,7 +113,8 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage StatusMessage = "Your profile has been updated"; return RedirectToPage(); } - public async Task OnPostSendVerificationEmailAsync() + + public override async Task OnPostSendVerificationEmailAsync() { if (!ModelState.IsValid) { diff --git a/src/UI/Areas/Identity/Pages/Account/Manage/ManageNavPages.cs b/src/UI/Areas/Identity/Pages/Account/Manage/ManageNavPages.cs index 49a721ac60..5f44b7fe95 100644 --- a/src/UI/Areas/Identity/Pages/Account/Manage/ManageNavPages.cs +++ b/src/UI/Areas/Identity/Pages/Account/Manage/ManageNavPages.cs @@ -4,7 +4,7 @@ using System; using Microsoft.AspNetCore.Mvc.Rendering; -namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage +namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal { public static class ManageNavPages { diff --git a/src/UI/Areas/Identity/Pages/Account/Manage/PersonalData.cshtml.cs b/src/UI/Areas/Identity/Pages/Account/Manage/PersonalData.cshtml.cs index 432371ff6d..11e0e8ba4f 100644 --- a/src/UI/Areas/Identity/Pages/Account/Manage/PersonalData.cshtml.cs +++ b/src/UI/Areas/Identity/Pages/Account/Manage/PersonalData.cshtml.cs @@ -1,27 +1,34 @@ // 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.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.Logging; -namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage +namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal { - public class PersonalDataModel : PageModel + [IdentityDefaultUI(typeof(PersonalDataModel<>))] + public abstract class PersonalDataModel : PageModel { - private readonly UserManager _userManager; + public virtual Task OnGet() => throw new NotImplementedException(); + } + + internal class PersonalDataModel : PersonalDataModel where TUser : IdentityUser + { + private readonly UserManager _userManager; private readonly ILogger _logger; public PersonalDataModel( - UserManager userManager, + UserManager userManager, ILogger logger) { _userManager = userManager; _logger = logger; } - public async Task OnGet() + public override async Task OnGet() { var user = await _userManager.GetUserAsync(User); if (user == null) diff --git a/src/UI/Areas/Identity/Pages/Account/Manage/ResetAuthenticator.cshtml.cs b/src/UI/Areas/Identity/Pages/Account/Manage/ResetAuthenticator.cshtml.cs index fe447f832e..0dce8b5c34 100644 --- a/src/UI/Areas/Identity/Pages/Account/Manage/ResetAuthenticator.cshtml.cs +++ b/src/UI/Areas/Identity/Pages/Account/Manage/ResetAuthenticator.cshtml.cs @@ -7,21 +7,30 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.Logging; -namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage +namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal { - public class ResetAuthenticatorModel : PageModel + [IdentityDefaultUI(typeof(ResetAuthenticatorModel<>))] + public abstract class ResetAuthenticatorModel : PageModel { - UserManager _userManager; + public virtual Task OnGet() => throw new NotImplementedException(); + + public virtual Task OnPostAsync() => throw new NotImplementedException(); + } + + internal class ResetAuthenticatorModel : ResetAuthenticatorModel where TUser : IdentityUser + { + UserManager _userManager; ILogger _logger; public ResetAuthenticatorModel( - UserManager userManager, + UserManager userManager, ILogger logger) { _userManager = userManager; _logger = logger; } - public async Task OnGet() + + public override async Task OnGet() { var user = await _userManager.GetUserAsync(User); if (user == null) @@ -32,7 +41,7 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage return Page(); } - public async Task OnPostAsync() + public override async Task OnPostAsync() { var user = await _userManager.GetUserAsync(User); if (user == null) diff --git a/src/UI/Areas/Identity/Pages/Account/Manage/SetPassword.cshtml.cs b/src/UI/Areas/Identity/Pages/Account/Manage/SetPassword.cshtml.cs index d53964dd76..6478d5685b 100644 --- a/src/UI/Areas/Identity/Pages/Account/Manage/SetPassword.cshtml.cs +++ b/src/UI/Areas/Identity/Pages/Account/Manage/SetPassword.cshtml.cs @@ -7,21 +7,11 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; -namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage +namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal { - public class SetPasswordModel : PageModel + [IdentityDefaultUI(typeof(SetPasswordModel<>))] + public abstract class SetPasswordModel : PageModel { - private readonly UserManager _userManager; - private readonly SignInManager _signInManager; - - public SetPasswordModel( - UserManager userManager, - SignInManager signInManager) - { - _userManager = userManager; - _signInManager = signInManager; - } - [BindProperty] public InputModel Input { get; set; } @@ -42,7 +32,25 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage public string ConfirmPassword { get; set; } } - public async Task OnGetAsync() + public virtual Task OnGetAsync() => throw new NotImplementedException(); + + public virtual Task OnPostAsync() => throw new NotImplementedException(); + } + + internal class SetPasswordModel : SetPasswordModel where TUser : IdentityUser + { + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; + + public SetPasswordModel( + UserManager userManager, + SignInManager signInManager) + { + _userManager = userManager; + _signInManager = signInManager; + } + + public override async Task OnGetAsync() { var user = await _userManager.GetUserAsync(User); if (user == null) @@ -60,7 +68,7 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage return Page(); } - public async Task OnPostAsync() + public override async Task OnPostAsync() { if (!ModelState.IsValid) { diff --git a/src/UI/Areas/Identity/Pages/Account/Manage/ShowRecoveryCodes.cshtml.cs b/src/UI/Areas/Identity/Pages/Account/Manage/ShowRecoveryCodes.cshtml.cs index c865922898..c00106d821 100644 --- a/src/UI/Areas/Identity/Pages/Account/Manage/ShowRecoveryCodes.cshtml.cs +++ b/src/UI/Areas/Identity/Pages/Account/Manage/ShowRecoveryCodes.cshtml.cs @@ -8,7 +8,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.Logging; -namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage +namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal { public class ShowRecoveryCodesModel : PageModel { diff --git a/src/UI/Areas/Identity/Pages/Account/Manage/TwoFactorAuthentication.cshtml.cs b/src/UI/Areas/Identity/Pages/Account/Manage/TwoFactorAuthentication.cshtml.cs index c1c179daf6..7972d0df0e 100644 --- a/src/UI/Areas/Identity/Pages/Account/Manage/TwoFactorAuthentication.cshtml.cs +++ b/src/UI/Areas/Identity/Pages/Account/Manage/TwoFactorAuthentication.cshtml.cs @@ -7,24 +7,11 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.Logging; -namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage +namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal { - public class TwoFactorAuthenticationModel : PageModel + [IdentityDefaultUI(typeof(TwoFactorAuthenticationModel<>))] + public abstract class TwoFactorAuthenticationModel : PageModel { - private readonly UserManager _userManager; - private readonly SignInManager _signInManager; - private readonly ILogger _logger; - - public TwoFactorAuthenticationModel( - UserManager userManager, - SignInManager signInManager, - ILogger logger) - { - _userManager = userManager; - _signInManager = signInManager; - _logger = logger; - } - public bool HasAuthenticator { get; set; } public int RecoveryCodesLeft { get; set; } @@ -32,7 +19,24 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage [BindProperty] public bool Is2faEnabled { get; set; } - public async Task OnGet() + public virtual Task OnGet() => throw new NotImplementedException(); + } + + internal class TwoFactorAuthenticationModel : TwoFactorAuthenticationModel where TUser : IdentityUser + { + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; + private readonly ILogger _logger; + + public TwoFactorAuthenticationModel( + UserManager userManager, SignInManager signInManager, ILogger logger) + { + _userManager = userManager; + _signInManager = signInManager; + _logger = logger; + } + + public override async Task OnGet() { var user = await _userManager.GetUserAsync(User); if (user == null) diff --git a/src/UI/Areas/Identity/Pages/Account/Manage/_ManageNav.cshtml b/src/UI/Areas/Identity/Pages/Account/Manage/_ManageNav.cshtml index 5ba75b6646..3b9ecceb24 100644 --- a/src/UI/Areas/Identity/Pages/Account/Manage/_ManageNav.cshtml +++ b/src/UI/Areas/Identity/Pages/Account/Manage/_ManageNav.cshtml @@ -1,12 +1,7 @@ -@inject SignInManager SignInManager -@{ - var hasExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).Any(); -} - -