Browse Source

Resolved #744: Add IdentityServer4 to the Account Module.

pull/748/head
Halil ibrahim Kalkan 7 years ago
parent
commit
5d6631328f
  1. 8
      modules/account/src/Volo.Abp.Account.Web/AbpAccountWebModule.cs
  2. 73
      modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml
  3. 255
      modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs
  4. 1
      modules/account/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj

8
modules/account/src/Volo.Abp.Account.Web/AbpAccountWebModule.cs

@ -6,6 +6,7 @@ using Volo.Abp.AspNetCore.Mvc.Localization;
using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared;
using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Toolbars; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Toolbars;
using Volo.Abp.Identity.AspNetCore; using Volo.Abp.Identity.AspNetCore;
using Volo.Abp.IdentityServer;
using Volo.Abp.Localization; using Volo.Abp.Localization;
using Volo.Abp.Localization.Resources.AbpValidation; using Volo.Abp.Localization.Resources.AbpValidation;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
@ -15,8 +16,11 @@ using Volo.Abp.VirtualFileSystem;
namespace Volo.Abp.Account.Web namespace Volo.Abp.Account.Web
{ {
[DependsOn(typeof(AbpIdentityAspNetCoreModule))] [DependsOn(
[DependsOn(typeof(AbpAspNetCoreMvcUiThemeSharedModule))] typeof(AbpIdentityAspNetCoreModule),
typeof(AbpAspNetCoreMvcUiThemeSharedModule),
typeof(AbpIdentityServerDomainModule)
)]
public class AbpAccountWebModule : AbpModule public class AbpAccountWebModule : AbpModule
{ {
public override void PreConfigureServices(ServiceConfigurationContext context) public override void PreConfigureServices(ServiceConfigurationContext context)

73
modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml

@ -1,30 +1,59 @@
@page @page
@model Volo.Abp.Account.Web.Pages.Account.LoginModel
@using Volo.Abp.Account.Web.Settings @using Volo.Abp.Account.Web.Settings
@inherits Volo.Abp.Account.Web.Pages.Account.AccountPage @model Volo.Abp.Account.Web.Pages.Account.LoginModel
@inject Volo.Abp.Settings.ISettingManager SettingManager @inject Volo.Abp.Settings.ISettingManager SettingManager
<h2>@L["Login"]</h2> @if (Model.EnableLocalLogin)
{
<form method="post">
<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>
<abp-button type="button" button-type="Secondary" name="Action" value="Cancel">Cancel</abp-button> @* TODO: Only show if identity server is used *@
<abp-button type="submit" button-type="Primary" name="Action" value="Login">Login</abp-button>
</form>
<form method="post"> <div style="padding-top: 20px">
<abp-input asp-for="Input.UserNameOrEmailAddress" auto-focus="true" /> @if (string.Equals(await SettingManager.GetOrNullAsync(AccountSettingNames.IsSelfRegistrationEnabled), "true", StringComparison.OrdinalIgnoreCase))
<abp-input asp-for="Input.Password" /> {
<abp-input asp-for="Input.RememberMe" class="mb-3" /> <a href="@Url.Page("./Register", new {returnUrl = Model.ReturnUrl, returnUrlHash = Model.ReturnUrlHash})">Register</a><text> | </text>
<abp-button button-type="Primary" type="submit">@L["Login"]</abp-button> }
@if (string.Equals(await SettingManager.GetOrNullAsync(AccountSettingNames.IsSelfRegistrationEnabled), "true", StringComparison.OrdinalIgnoreCase)) </div>
{ }
<a abp-button="Secondary" href="@Url.Page("./Register", new {returnUrl = Model.ReturnUrl, returnUrlHash = Model.ReturnUrlHash})">@L["Register"]</a>
}
</form>
@if (Model.ExternalLogins.Any()) @if (Model.VisibleExternalProviders.Any())
{ {
<h4>Use another service to log in.</h4> <div class="col-md-6">
<form asp-page="./Login" asp-page-handler="ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" asp-route-returnUrlHash="@Model.ReturnUrlHash" method="post"> <h4>Use another service to log in.</h4>
<div> <form asp-page="./Login" asp-page-handler="ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" asp-route-returnUrlHash="@Model.ReturnUrlHash" method="post">
@foreach (var provider in Model.ExternalLogins) <input asp-for="ReturnUrl" />
<input asp-for="ReturnUrlHash" />
@foreach (var provider in Model.VisibleExternalProviders)
{ {
<abp-button button-type="Primary" type="submit" name="provider" value="@provider.Name">@provider.DisplayName</abp-button> <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>
</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>
}

255
modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs

@ -1,49 +1,181 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Security.Claims; using System.Security.Claims;
using System.Security.Principal;
using System.Threading.Tasks; using System.Threading.Tasks;
using IdentityModel;
using IdentityServer4.Events;
using IdentityServer4.Models;
using IdentityServer4.Services;
using IdentityServer4.Stores;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Volo.Abp.Identity; using Volo.Abp.Identity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Security.Claims; using Volo.Abp.Security.Claims;
using Volo.Abp.Uow; using Volo.Abp.Uow;
using Volo.Abp.Validation;
namespace Volo.Abp.Account.Web.Pages.Account namespace Volo.Abp.Account.Web.Pages.Account
{ {
public class LoginModel : AccountPageModel public class LoginModel : AccountPageModel
{ {
[HiddenInput]
[BindProperty(SupportsGet = true)] [BindProperty(SupportsGet = true)]
public string ReturnUrl { get; set; } public string ReturnUrl { get; set; }
[HiddenInput]
[BindProperty(SupportsGet = true)] [BindProperty(SupportsGet = true)]
public string ReturnUrlHash { get; set; } public string ReturnUrlHash { get; set; }
[BindProperty] [BindProperty]
public PostInput Input { get; set; } public LoginInputModel LoginInput { get; set; }
public IList<AuthenticationScheme> ExternalLogins { get; set; } public bool EnableLocalLogin { get; set; }
public IList<AuthenticationScheme> ExternalLogins { get; set; } //TODO: Used?
//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 IIdentityServerInteractionService _interaction;
private readonly IAuthenticationSchemeProvider _schemeProvider;
private readonly AbpAccountOptions _accountOptions;
private readonly IClientStore _clientStore;
private readonly IEventService _identityServerEvents;
public LoginModel(
IIdentityServerInteractionService interaction,
IAuthenticationSchemeProvider schemeProvider,
IOptions<AbpAccountOptions> accountOptions,
IClientStore clientStore,
IEventService identityServerEvents)
{
_interaction = interaction;
_schemeProvider = schemeProvider;
_clientStore = clientStore;
_identityServerEvents = identityServerEvents;
_accountOptions = accountOptions.Value;
}
public async Task OnGetAsync() public async Task OnGetAsync()
{ {
ExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).ToList(); LoginInput = new LoginInputModel();
var context = await _interaction.GetAuthorizationContextAsync(ReturnUrl);
if (context != null)
{
LoginInput.UserNameOrEmailAddress = context.LoginHint;
//TODO: Reference AspNetCore MultiTenancy module and use options to get the tenant key!
var tenant = context.Parameters[TenantResolverConsts.DefaultTenantKey];
if (string.IsNullOrEmpty(tenant))
{
if (Request.Cookies.ContainsKey(TenantResolverConsts.DefaultTenantKey))
{
CurrentTenant.Change(null);
Response.Cookies.Delete(TenantResolverConsts.DefaultTenantKey);
}
}
else
{
CurrentTenant.Change(Guid.Parse(tenant));
Response.Cookies.Append(TenantResolverConsts.DefaultTenantKey, tenant);
}
}
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 [UnitOfWork] //TODO: Will be removed when we implement action filter
public virtual async Task<IActionResult> OnPostAsync() public virtual async Task<IActionResult> OnPostAsync(string action)
{ {
EnableLocalLogin = true; //TODO: We can get default from a setting?
if (action == "Cancel")
{
var context = await _interaction.GetAuthorizationContextAsync(ReturnUrl);
if (context == null)
{
return Redirect("~/");
}
await _interaction.GrantConsentAsync(context, ConsentResponse.Denied);
return Redirect(ReturnUrl);
}
ValidateModel(); ValidateModel();
await ReplaceEmailToUsernameOfInputIfNeeds();
var result = await SignInManager.PasswordSignInAsync( var result = await SignInManager.PasswordSignInAsync(
Input.UserNameOrEmailAddress, LoginInput.UserNameOrEmailAddress,
Input.Password, LoginInput.Password,
Input.RememberMe, LoginInput.RememberMe,
true true
); );
if (result.RequiresTwoFactor)
{
return RedirectToPage("./SendSecurityCode", new
{
returnUrl = ReturnUrl,
returnUrlHash = ReturnUrlHash,
rememberMe = LoginInput.RememberMe
});
}
if (result.IsLockedOut) if (result.IsLockedOut)
{ {
Alerts.Warning(L["UserLockedOutMessage"]); Alerts.Warning(L["UserLockedOutMessage"]);
@ -67,22 +199,39 @@ namespace Volo.Abp.Account.Web.Pages.Account
return Page(); return Page();
} }
//TODO: Find a way of getting user's id from the logged in user and do not query it again like that!
var user = await UserManager.FindByNameAsync(LoginInput.UserNameOrEmailAddress) ??
await UserManager.FindByEmailAsync(LoginInput.UserNameOrEmailAddress);
Debug.Assert(user != null, nameof(user) + " != null");
await _identityServerEvents.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.Id.ToString(), user.UserName)); //TODO: Use user's name once implemented
return RedirectSafely(ReturnUrl, ReturnUrlHash); return RedirectSafely(ReturnUrl, ReturnUrlHash);
} }
[UnitOfWork] //TODO: Will be removed when we implement action filter [UnitOfWork]
public virtual IActionResult OnPostExternalLogin(string provider, string returnUrl = "", string returnUrlHash = "") public virtual async Task<IActionResult> OnPostExternalLogin(string provider)
{ {
var redirectUrl = Url.Page("./Login", pageHandler: "ExternalLoginCallback", values: new { returnUrl, returnUrlHash }); if (_accountOptions.WindowsAuthenticationSchemeName == provider)
{
return await ProcessWindowsLoginAsync();
}
var redirectUrl = Url.Page("./Login", pageHandler: "ExternalLoginCallback", values: new { ReturnUrl, ReturnUrlHash });
var properties = SignInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl); var properties = SignInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
properties.Items["scheme"] = provider;
return new ChallengeResult(provider, properties); return Challenge(properties, provider);
} }
[UnitOfWork] //TODO: Will be removed when we implement action filter [UnitOfWork]
public virtual async Task<IActionResult> OnGetExternalLoginCallbackAsync(string returnUrl = "", string returnUrlHash = "", string remoteError = null) public virtual async Task<IActionResult> OnGetExternalLoginCallbackAsync(string returnUrl = "", string returnUrlHash = "", string remoteError = null)
{ {
//TODO: Did not implemented Identity Server 4 sample for this method (see ExternalLoginCallback in Quickstart of IDS4 sample)
/* Also did not implement these:
* - Logout(string logoutId)
*/
if (remoteError != null) if (remoteError != null)
{ {
Logger.LogWarning($"External login callback error: {remoteError}"); Logger.LogWarning($"External login callback error: {remoteError}");
@ -108,13 +257,13 @@ namespace Volo.Abp.Account.Web.Pages.Account
throw new UserFriendlyException("Cannot proceed because user is locked out!"); throw new UserFriendlyException("Cannot proceed because user is locked out!");
} }
//TODO: Handle other cases
if (result.Succeeded) if (result.Succeeded)
{ {
return RedirectSafely(returnUrl, returnUrlHash); return RedirectSafely(returnUrl, returnUrlHash);
} }
//TODO: Handle other cases for result!
// Get the information about the user from the external login provider // Get the information about the user from the external login provider
var info = await SignInManager.GetExternalLoginInfoAsync(); var info = await SignInManager.GetExternalLoginInfoAsync();
if (info == null) if (info == null)
@ -141,18 +290,88 @@ namespace Volo.Abp.Account.Web.Pages.Account
return user; return user;
} }
public class PostInput private async Task ReplaceEmailToUsernameOfInputIfNeeds()
{
if (!ValidationHandler.IsValidEmailAddress(LoginInput.UserNameOrEmailAddress))
{
return;
}
var userByUsername = await UserManager.FindByNameAsync(LoginInput.UserNameOrEmailAddress);
if (userByUsername != null)
{
return;
}
var userByEmail = await UserManager.FindByEmailAsync(LoginInput.UserNameOrEmailAddress);
if (userByEmail == null)
{
return;
}
LoginInput.UserNameOrEmailAddress = userByEmail.UserName;
}
private async Task<IActionResult> ProcessWindowsLoginAsync()
{
var result = await HttpContext.AuthenticateAsync(_accountOptions.WindowsAuthenticationSchemeName);
if (!(result?.Principal is WindowsPrincipal windowsPrincipal))
{
return Challenge(_accountOptions.WindowsAuthenticationSchemeName);
}
var props = new AuthenticationProperties
{
RedirectUri = Url.Page("./Login", pageHandler: "ExternalLoginCallback", values: new { ReturnUrl, ReturnUrlHash }),
Items =
{
{"scheme", _accountOptions.WindowsAuthenticationSchemeName},
}
};
var identity = new ClaimsIdentity(_accountOptions.WindowsAuthenticationSchemeName);
identity.AddClaim(new Claim(JwtClaimTypes.Subject, windowsPrincipal.Identity.Name));
identity.AddClaim(new Claim(JwtClaimTypes.Name, windowsPrincipal.Identity.Name));
//TODO: Consider to add Windows groups the the identity
//if (_accountOptions.IncludeWindowsGroups)
//{
// var windowsIdentity = windowsPrincipal.Identity as WindowsIdentity;
// if (windowsIdentity != null)
// {
// var groups = windowsIdentity.Groups?.Translate(typeof(NTAccount));
// var roles = groups.Select(x => new Claim(JwtClaimTypes.Role, x.Value));
// identity.AddClaims(roles);
// }
//}
await HttpContext.SignInAsync(
IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme,
new ClaimsPrincipal(identity),
props
);
return RedirectSafely(props.RedirectUri);
}
public class LoginInputModel
{ {
[Required] [Required]
[StringLength(255)] [StringLength(IdentityUserConsts.MaxEmailLength)]
public string UserNameOrEmailAddress { get; set; } public string UserNameOrEmailAddress { get; set; }
[Required] [Required]
[StringLength(32)] [StringLength(IdentityUserConsts.MaxPasswordLength)]
[DataType(DataType.Password)] [DataType(DataType.Password)]
public string Password { get; set; } public string Password { get; set; }
public bool RememberMe { get; set; } public bool RememberMe { get; set; }
} }
public class ExternalProviderModel
{
public string DisplayName { get; set; }
public string AuthenticationScheme { get; set; }
}
} }
} }

1
modules/account/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj

@ -32,6 +32,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\..\identity\src\Volo.Abp.Identity.AspNetCore\Volo.Abp.Identity.AspNetCore.csproj" /> <ProjectReference Include="..\..\..\identity\src\Volo.Abp.Identity.AspNetCore\Volo.Abp.Identity.AspNetCore.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared\Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.csproj" /> <ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared\Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.csproj" />
<ProjectReference Include="..\..\..\identityserver\src\Volo.Abp.IdentityServer.Domain\Volo.Abp.IdentityServer.Domain.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

Loading…
Cancel
Save