From 4d27fbffd813a19cc7fdb25c0389a613557e1734 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 1 Mar 2018 10:34:25 +0300 Subject: [PATCH] Refactored login page. --- .../MicroservicesAuthServerModule.cs | 17 +- .../AbpAccountWebIdentityServerModule.cs | 8 +- .../Pages/Account/Login.cshtml | 61 +++++ .../Pages/Account/Login.cshtml.cs | 228 ++++++++++++++++++ .../Pages/Account/_ViewImports.cshtml | 2 + ...Volo.Abp.Account.Web.IdentityServer.csproj | 6 +- src/Volo.Abp.Account.Web/AbpAccountOptions.cs | 13 + .../AbpAccountWebModule.cs | 4 +- .../Pages/Account/_ViewImports.cshtml | 2 + .../Volo.Abp.Account.Web.csproj | 8 + 10 files changed, 342 insertions(+), 7 deletions(-) create mode 100644 src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml create mode 100644 src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs create mode 100644 src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/_ViewImports.cshtml create mode 100644 src/Volo.Abp.Account.Web/AbpAccountOptions.cs create mode 100644 src/Volo.Abp.Account.Web/Pages/Account/_ViewImports.cshtml diff --git a/src/MicroserviceDemo/MicroserviceDemo.AuthServer/MicroservicesAuthServerModule.cs b/src/MicroserviceDemo/MicroserviceDemo.AuthServer/MicroservicesAuthServerModule.cs index 28837b8d08..1fe43dc686 100644 --- a/src/MicroserviceDemo/MicroserviceDemo.AuthServer/MicroservicesAuthServerModule.cs +++ b/src/MicroserviceDemo/MicroserviceDemo.AuthServer/MicroservicesAuthServerModule.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Builder; +using System.IO; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; @@ -6,12 +7,15 @@ using Microsoft.Extensions.DependencyInjection; using Volo.Abp; using Volo.Abp.Account.Web; using Volo.Abp.AspNetCore.Modularity; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap; using Volo.Abp.Autofac; using Volo.Abp.Data; using Volo.Abp.EntityFrameworkCore; using Volo.Abp.Identity.EntityFrameworkCore; using Volo.Abp.IdentityServer.EntityFrameworkCore; using Volo.Abp.Modularity; +using Volo.Abp.VirtualFileSystem; namespace MicroserviceDemo.AuthServer { @@ -43,6 +47,17 @@ namespace MicroserviceDemo.AuthServer }); }); + if (hostingEnvironment.IsDevelopment()) + { + services.Configure(options => + { + options.FileSets.ReplaceEmbeddedByPyhsical(Path.Combine(hostingEnvironment.ContentRootPath, "..\\..\\Volo.Abp.AspNetCore.Mvc.UI")); + options.FileSets.ReplaceEmbeddedByPyhsical(Path.Combine(hostingEnvironment.ContentRootPath, "..\\..\\Volo.Abp.AspNetCore.Mvc.UI.Bootstrap")); + options.FileSets.ReplaceEmbeddedByPyhsical(Path.Combine(hostingEnvironment.ContentRootPath, "..\\..\\Volo.Abp.Account.Web")); + options.FileSets.ReplaceEmbeddedByPyhsical(Path.Combine(hostingEnvironment.ContentRootPath, "..\\..\\Volo.Abp.Account.Web.IdentityServer")); + }); + } + services.AddAssemblyOf(); } diff --git a/src/Volo.Abp.Account.Web.IdentityServer/AbpAccountWebIdentityServerModule.cs b/src/Volo.Abp.Account.Web.IdentityServer/AbpAccountWebIdentityServerModule.cs index 14e5100045..21ec67aa81 100644 --- a/src/Volo.Abp.Account.Web.IdentityServer/AbpAccountWebIdentityServerModule.cs +++ b/src/Volo.Abp.Account.Web.IdentityServer/AbpAccountWebIdentityServerModule.cs @@ -1,20 +1,22 @@ using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.IdentityServer; using Volo.Abp.Modularity; using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.Account.Web { [DependsOn(typeof(AbpAccountWebModule))] + [DependsOn(typeof(AbpIdentityServerDomainModule))] public class AbpAccountWebIdentityServerModule : AbpModule { public override void ConfigureServices(IServiceCollection services) { - services.AddAssemblyOf(); - services.Configure(options => { - options.FileSets.AddEmbedded("Volo.Abp.Account.Web"); + options.FileSets.AddEmbedded("Volo.Abp.Account.Web"); }); + + services.AddAssemblyOf(); } } } diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml new file mode 100644 index 0000000000..75cef4ee5e --- /dev/null +++ b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml @@ -0,0 +1,61 @@ +@page +@model Volo.Abp.Account.Web.Pages.Account.IdsLoginModel +
+ + @if (Model.EnableLocalLogin) + { +
+
+
+ + +
+ + + +
+
+ + + +
+
+ +
+ + +
+ +
+ Register +
+
+
+ } + + @if (Model.VisibleExternalProviders.Any()) + { +
+

Use another service to log in.

+
+
+ @foreach (var provider in Model.VisibleExternalProviders) + { + + } +
+
+
+ } + + @if (!Model.EnableLocalLogin && !Model.VisibleExternalProviders.Any()) + { +
+ Invalid login request + There are no login schemes configured for this client. +
+ } +
\ No newline at end of file diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs new file mode 100644 index 0000000000..e71030e3b8 --- /dev/null +++ b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs @@ -0,0 +1,228 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using IdentityServer4.Services; +using IdentityServer4.Stores; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Volo.Abp.Identity; +using Volo.Abp.Ui; +using Volo.Abp.Uow; + +namespace Volo.Abp.Account.Web.Pages.Account +{ + public class IdsLoginModel : AccountModelBase + { + [HiddenInput] + [BindProperty(SupportsGet = true)] + public string ReturnUrl { get; set; } + + [HiddenInput] + [BindProperty(SupportsGet = true)] + public string ReturnUrlHash { get; set; } + + [BindProperty] + public LoginInputModel LoginInput { get; set; } + + public bool EnableLocalLogin { get; set; } + + //TODO: Why there is an ExternalProviders if only the VisibleExternalProviders is used. + public IEnumerable ExternalProviders { get; set; } + public IEnumerable VisibleExternalProviders => ExternalProviders.Where(x => !String.IsNullOrWhiteSpace(x.DisplayName)); + + public bool IsExternalLoginOnly => EnableLocalLogin == false && ExternalProviders?.Count() == 1; + public string ExternalLoginScheme => IsExternalLoginOnly ? ExternalProviders?.SingleOrDefault()?.AuthenticationScheme : null; + + private readonly SignInManager _signInManager; + private readonly IdentityUserManager _userManager; + private readonly IIdentityServerInteractionService _interaction; + private readonly IAuthenticationSchemeProvider _schemeProvider; + private readonly AbpAccountOptions _accountOptions; + private readonly IClientStore _clientStore; + + public IdsLoginModel( + SignInManager signInManager, + IdentityUserManager userManager, + IIdentityServerInteractionService interaction, + IAuthenticationSchemeProvider schemeProvider, + IOptions accountOptions, + IClientStore clientStore) + { + _signInManager = signInManager; + _userManager = userManager; + _interaction = interaction; + _schemeProvider = schemeProvider; + _clientStore = clientStore; + _accountOptions = accountOptions.Value; + } + + public async Task OnGetAsync() + { + LoginInput = new LoginInputModel(); + + var context = await _interaction.GetAuthorizationContextAsync(ReturnUrl); + LoginInput.UserNameOrEmailAddress = context?.LoginHint; + + if (context?.IdP != null) + { + LoginInput.UserNameOrEmailAddress = context.LoginHint; + ExternalProviders = new[] {new ExternalProviderModel {AuthenticationScheme = context.IdP}}; + return; + } + + var schemes = await _schemeProvider.GetAllSchemesAsync(); + + var providers = schemes + .Where(x => x.DisplayName != null || x.Name.Equals(_accountOptions.WindowsAuthenticationSchemeName, StringComparison.OrdinalIgnoreCase)) + .Select(x => new ExternalProviderModel + { + DisplayName = x.DisplayName, + AuthenticationScheme = x.Name + }) + .ToList(); + + EnableLocalLogin = true; //TODO: We can get default from a setting? + if (context?.ClientId != null) + { + var client = await _clientStore.FindEnabledClientByIdAsync(context.ClientId); + if (client != null) + { + EnableLocalLogin = client.EnableLocalLogin; + + if (client.IdentityProviderRestrictions != null && client.IdentityProviderRestrictions.Any()) + { + providers = providers.Where(provider => client.IdentityProviderRestrictions.Contains(provider.AuthenticationScheme)).ToList(); + } + } + } + + ExternalProviders = providers.ToArray(); + + if (IsExternalLoginOnly) + { + //return await ExternalLogin(vm.ExternalLoginScheme, returnUrl); + throw new NotImplementedException(); + } + } + + [UnitOfWork] //TODO: Will be removed when we implement action filter + public virtual async Task OnPostAsync() + { + ValidateModel(); + + var result = await _signInManager.PasswordSignInAsync( + LoginInput.UserNameOrEmailAddress, + LoginInput.Password, + LoginInput.RememberMe, + true + ); + + if (!result.Succeeded) + { + throw new UserFriendlyException("Login failed!"); //TODO: Handle other cases, do not throw exception + } + + return RedirectSafely(ReturnUrl, ReturnUrlHash); + } + + [UnitOfWork] + public virtual IActionResult OnPostExternalLogin(string provider, string returnUrl = "", string returnUrlHash = "") + { + var redirectUrl = Url.Page("./Login", pageHandler: "ExternalLoginCallback", values: new { returnUrl, returnUrlHash }); + + var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl); + + return new ChallengeResult(provider, properties); + } + + [UnitOfWork] + public virtual async Task OnGetExternalLoginCallbackAsync(string returnUrl = "", string returnUrlHash = "", string remoteError = null) + { + if (remoteError != null) + { + Logger.LogWarning($"External login callback error: {remoteError}"); + return RedirectToPage("./Login"); + } + + var loginInfo = await _signInManager.GetExternalLoginInfoAsync(); + if (loginInfo == null) + { + Logger.LogWarning("External login info is not available"); + return RedirectToPage("./Login"); + } + + var result = await _signInManager.ExternalLoginSignInAsync( + loginInfo.LoginProvider, + loginInfo.ProviderKey, + isPersistent: false, + bypassTwoFactor: true + ); + + if (result.IsLockedOut) + { + throw new UserFriendlyException("Cannot proceed because user is locked out!"); + } + + //TODO: Handle other cases + + if (result.Succeeded) + { + return RedirectSafely(returnUrl, returnUrlHash); + } + + // Get the information about the user from the external login provider + var info = await _signInManager.GetExternalLoginInfoAsync(); + if (info == null) + { + throw new ApplicationException("Error loading external login information during confirmation."); + } + + var user = await CreateExternalUserAsync(info); + + await _signInManager.SignInAsync(user, false); + return RedirectSafely(returnUrl, returnUrlHash); + } + + private async Task CreateExternalUserAsync(ExternalLoginInfo info) + { + var emailAddress = info.Principal.FindFirstValue(ClaimTypes.Email); + + var user = new IdentityUser(GuidGenerator.Create(), emailAddress, CurrentTenant.Id); + + CheckIdentityErrors(await _userManager.CreateAsync(user)); + CheckIdentityErrors(await _userManager.SetEmailAsync(user, emailAddress)); + CheckIdentityErrors(await _userManager.AddLoginAsync(user, info)); + + return user; + } + + public class LoginInputModel + { + [Required] + [StringLength(255)] + [Display(Name = "UserNameOrEmailAddress")] + public string UserNameOrEmailAddress { get; set; } + + [Required] + [StringLength(32)] + [DataType(DataType.Password)] + [Display(Name = "Password")] + public string Password { get; set; } + + [Display(Name = "RememberMe")] + public bool RememberMe { get; set; } + } + + public class ExternalProviderModel + { + public string DisplayName { get; set; } + public string AuthenticationScheme { get; set; } + } + } +} diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/_ViewImports.cshtml b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/_ViewImports.cshtml new file mode 100644 index 0000000000..d1ac64721f --- /dev/null +++ b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/_ViewImports.cshtml @@ -0,0 +1,2 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap \ No newline at end of file diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj b/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj index 5bedcf9979..fa6ac31d6d 100644 --- a/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj +++ b/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj @@ -19,13 +19,17 @@ + + + + - + diff --git a/src/Volo.Abp.Account.Web/AbpAccountOptions.cs b/src/Volo.Abp.Account.Web/AbpAccountOptions.cs new file mode 100644 index 0000000000..e814ee44f5 --- /dev/null +++ b/src/Volo.Abp.Account.Web/AbpAccountOptions.cs @@ -0,0 +1,13 @@ +namespace Volo.Abp.Account.Web +{ + public class AbpAccountOptions + { + public string WindowsAuthenticationSchemeName { get; set; } + + public AbpAccountOptions() + { + //TODO: This makes us depend on the Microsoft.AspNetCore.Server.IISIntegration package. + WindowsAuthenticationSchemeName = Microsoft.AspNetCore.Server.IISIntegration.IISDefaults.AuthenticationScheme; + } + } +} diff --git a/src/Volo.Abp.Account.Web/AbpAccountWebModule.cs b/src/Volo.Abp.Account.Web/AbpAccountWebModule.cs index a0df66f747..e35f0050ed 100644 --- a/src/Volo.Abp.Account.Web/AbpAccountWebModule.cs +++ b/src/Volo.Abp.Account.Web/AbpAccountWebModule.cs @@ -12,12 +12,12 @@ namespace Volo.Abp.Account.Web { public override void ConfigureServices(IServiceCollection services) { - services.AddAssemblyOf(); - services.Configure(options => { options.FileSets.AddEmbedded("Volo.Abp.Account.Web"); }); + + services.AddAssemblyOf(); } } } diff --git a/src/Volo.Abp.Account.Web/Pages/Account/_ViewImports.cshtml b/src/Volo.Abp.Account.Web/Pages/Account/_ViewImports.cshtml new file mode 100644 index 0000000000..d1ac64721f --- /dev/null +++ b/src/Volo.Abp.Account.Web/Pages/Account/_ViewImports.cshtml @@ -0,0 +1,2 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap \ No newline at end of file diff --git a/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj b/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj index 6073769fc4..e844ba9f73 100644 --- a/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj +++ b/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj @@ -19,6 +19,14 @@ + + + + + + + +