@ -1,49 +1,181 @@
using System ;
using System.Collections.Generic ;
using System.ComponentModel.DataAnnotations ;
using System.Diagnostics ;
using System.Linq ;
using System.Security.Claims ;
using System.Security.Principal ;
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.Identity ;
using Microsoft.AspNetCore.Mvc ;
using Microsoft.Extensions.Logging ;
using Microsoft.Extensions.Options ;
using Volo.Abp.Identity ;
using Volo.Abp.MultiTenancy ;
using Volo.Abp.Security.Claims ;
using Volo.Abp.Uow ;
using Volo.Abp.Validation ;
namespace Volo.Abp.Account.Web.Pages.Account
{
public class LoginModel : AccountPageModel
{
[HiddenInput]
[BindProperty(SupportsGet = true)]
public string ReturnUrl { get ; set ; }
[HiddenInput]
[BindProperty(SupportsGet = true)]
public string ReturnUrlHash { get ; set ; }
[BindProperty]
public PostInput Input { get ; set ; }
public LoginInputModel Login Input { 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 ( )
{
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
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 ( ) ;
await ReplaceEmailToUsernameOfInputIfNeeds ( ) ;
var result = await SignInManager . PasswordSignInAsync (
Input . UserNameOrEmailAddress ,
Input . Password ,
Input . RememberMe ,
Login Input. UserNameOrEmailAddress ,
Login Input. Password ,
Login Input. RememberMe ,
true
) ;
if ( result . RequiresTwoFactor )
{
return RedirectToPage ( "./SendSecurityCode" , new
{
returnUrl = ReturnUrl ,
returnUrlHash = ReturnUrlHash ,
rememberMe = LoginInput . RememberMe
} ) ;
}
if ( result . IsLockedOut )
{
Alerts . Warning ( L [ "UserLockedOutMessage" ] ) ;
@ -67,22 +199,39 @@ namespace Volo.Abp.Account.Web.Pages.Account
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 ) ;
}
[UnitOfWork] //TODO: Will be removed when we implement action filter
public virtual IActionResult OnPostExternalLogin ( string provider , string returnUrl = "" , string returnUrlHash = "" )
[UnitOfWork]
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 ) ;
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 )
{
//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 )
{
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!" ) ;
}
//TODO: Handle other cases
if ( result . Succeeded )
{
return RedirectSafely ( returnUrl , returnUrlHash ) ;
}
//TODO: Handle other cases for result!
// Get the information about the user from the external login provider
var info = await SignInManager . GetExternalLoginInfoAsync ( ) ;
if ( info = = null )
@ -141,18 +290,88 @@ namespace Volo.Abp.Account.Web.Pages.Account
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]
[StringLength(255)]
[StringLength(IdentityUserConsts.MaxEmailLength )]
public string UserNameOrEmailAddress { get ; set ; }
[Required]
[StringLength(32)]
[StringLength(IdentityUserConsts.MaxPasswordLength )]
[DataType(DataType.Password)]
public string Password { get ; set ; }
public bool RememberMe { get ; set ; }
}
public class ExternalProviderModel
{
public string DisplayName { get ; set ; }
public string AuthenticationScheme { get ; set ; }
}
}
}