mirror of https://github.com/abpframework/abp.git
10 changed files with 342 additions and 7 deletions
@ -1,20 +1,22 @@ |
|||||
using Microsoft.Extensions.DependencyInjection; |
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.IdentityServer; |
||||
using Volo.Abp.Modularity; |
using Volo.Abp.Modularity; |
||||
using Volo.Abp.VirtualFileSystem; |
using Volo.Abp.VirtualFileSystem; |
||||
|
|
||||
namespace Volo.Abp.Account.Web |
namespace Volo.Abp.Account.Web |
||||
{ |
{ |
||||
[DependsOn(typeof(AbpAccountWebModule))] |
[DependsOn(typeof(AbpAccountWebModule))] |
||||
|
[DependsOn(typeof(AbpIdentityServerDomainModule))] |
||||
public class AbpAccountWebIdentityServerModule : AbpModule |
public class AbpAccountWebIdentityServerModule : AbpModule |
||||
{ |
{ |
||||
public override void ConfigureServices(IServiceCollection services) |
public override void ConfigureServices(IServiceCollection services) |
||||
{ |
{ |
||||
services.AddAssemblyOf<AbpAccountWebModule>(); |
|
||||
|
|
||||
services.Configure<VirtualFileSystemOptions>(options => |
services.Configure<VirtualFileSystemOptions>(options => |
||||
{ |
{ |
||||
options.FileSets.AddEmbedded<AbpAccountWebModule>("Volo.Abp.Account.Web"); |
options.FileSets.AddEmbedded<AbpAccountWebIdentityServerModule>("Volo.Abp.Account.Web"); |
||||
}); |
}); |
||||
|
|
||||
|
services.AddAssemblyOf<AbpAccountWebIdentityServerModule>(); |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -0,0 +1,61 @@ |
|||||
|
@page |
||||
|
@model Volo.Abp.Account.Web.Pages.Account.IdsLoginModel |
||||
|
<div class="row"> |
||||
|
|
||||
|
@if (Model.EnableLocalLogin) |
||||
|
{ |
||||
|
<div class="col-md-6"> |
||||
|
<div class="col-md-4"> |
||||
|
<form method="post" asp-page="/Account/Login"> |
||||
|
<input asp-for="ReturnUrl" /> |
||||
|
<input asp-for="ReturnUrlHash" /> |
||||
|
<div class="form-group"> |
||||
|
<label asp-for="LoginInput.UserNameOrEmailAddress"></label> |
||||
|
<input asp-for="LoginInput.UserNameOrEmailAddress" class="form-control" /> |
||||
|
<span asp-validation-for="LoginInput.UserNameOrEmailAddress" class="text-danger"></span> |
||||
|
</div> |
||||
|
<div class="form-group"> |
||||
|
<label asp-for="LoginInput.Password"></label> |
||||
|
<input asp-for="LoginInput.Password" class="form-control" /> |
||||
|
<span asp-validation-for="LoginInput.Password" class="text-danger"></span> |
||||
|
</div> |
||||
|
<div class="form-check"> |
||||
|
<label asp-for="LoginInput.RememberMe" class="form-check-label"> |
||||
|
<input asp-for="LoginInput.RememberMe" class="form-check-input" /> |
||||
|
@Html.DisplayNameFor(m => m.LoginInput.RememberMe) |
||||
|
</label> |
||||
|
</div> |
||||
|
<button type="submit" class="btn btn-primary" name="Action" value="Login">Login</button> |
||||
|
<button type="button" class="btn btn-secondary" name="Action" value="Cancel">Cancel</button> |
||||
|
</form> |
||||
|
|
||||
|
<div style="padding-top: 20px"> |
||||
|
<a href="@Url.Page("./Register", new {returnUrl = Model.ReturnUrl, returnUrlHash = Model.ReturnUrlHash})">Register</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
} |
||||
|
|
||||
|
@if (Model.VisibleExternalProviders.Any()) |
||||
|
{ |
||||
|
<div class="col-md-6"> |
||||
|
<h4>Use another service to log in.</h4> |
||||
|
<form asp-page="./Login" asp-page-handler="ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" asp-route-returnUrlHash="@Model.ReturnUrlHash" method="post"> |
||||
|
<div> |
||||
|
@foreach (var provider in Model.VisibleExternalProviders) |
||||
|
{ |
||||
|
<button type="submit" class="btn btn-primary" name="provider" value="@provider.AuthenticationScheme" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button> |
||||
|
} |
||||
|
</div> |
||||
|
</form> |
||||
|
</div> |
||||
|
} |
||||
|
|
||||
|
@if (!Model.EnableLocalLogin && !Model.VisibleExternalProviders.Any()) |
||||
|
{ |
||||
|
<div class="alert alert-warning"> |
||||
|
<strong>Invalid login request</strong> |
||||
|
There are no login schemes configured for this client. |
||||
|
</div> |
||||
|
} |
||||
|
</div> |
||||
@ -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<ExternalProviderModel> ExternalProviders { get; set; } |
||||
|
public IEnumerable<ExternalProviderModel> 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<IdentityUser> _signInManager; |
||||
|
private readonly IdentityUserManager _userManager; |
||||
|
private readonly IIdentityServerInteractionService _interaction; |
||||
|
private readonly IAuthenticationSchemeProvider _schemeProvider; |
||||
|
private readonly AbpAccountOptions _accountOptions; |
||||
|
private readonly IClientStore _clientStore; |
||||
|
|
||||
|
public IdsLoginModel( |
||||
|
SignInManager<IdentityUser> signInManager, |
||||
|
IdentityUserManager userManager, |
||||
|
IIdentityServerInteractionService interaction, |
||||
|
IAuthenticationSchemeProvider schemeProvider, |
||||
|
IOptions<AbpAccountOptions> 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<IActionResult> 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<IActionResult> 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<IdentityUser> 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; } |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,2 @@ |
|||||
|
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers |
||||
|
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,2 @@ |
|||||
|
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers |
||||
|
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap |
||||
Loading…
Reference in new issue