|
|
|
@ -1,6 +1,7 @@
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Resources;
|
|
|
|
|
using System.Security.Claims;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using IdentityModel;
|
|
|
|
@ -63,115 +64,160 @@ namespace Volo.Abp.IdentityServer.AspNetIdentity
|
|
|
|
|
[UnitOfWork]
|
|
|
|
|
public virtual async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
|
|
|
|
|
{
|
|
|
|
|
var clientId = context.Request?.Client?.ClientId;
|
|
|
|
|
using var scope = ServiceScopeFactory.CreateScope();
|
|
|
|
|
|
|
|
|
|
await ReplaceEmailToUsernameOfInputIfNeeds(context);
|
|
|
|
|
|
|
|
|
|
IdentityUser user = null;
|
|
|
|
|
|
|
|
|
|
async Task SetSuccessResultAsync()
|
|
|
|
|
using (var scope = ServiceScopeFactory.CreateScope())
|
|
|
|
|
{
|
|
|
|
|
var sub = await UserManager.GetUserIdAsync(user);
|
|
|
|
|
await ReplaceEmailToUsernameOfInputIfNeeds(context);
|
|
|
|
|
|
|
|
|
|
Logger.LogInformation("Credentials validated for username: {username}", context.UserName);
|
|
|
|
|
IdentityUser user = null;
|
|
|
|
|
|
|
|
|
|
var additionalClaims = new List<Claim>();
|
|
|
|
|
|
|
|
|
|
await AddCustomClaimsAsync(additionalClaims, user, context);
|
|
|
|
|
if (AbpIdentityOptions.ExternalLoginProviders.Any())
|
|
|
|
|
{
|
|
|
|
|
foreach (var externalLoginProviderInfo in AbpIdentityOptions.ExternalLoginProviders.Values)
|
|
|
|
|
{
|
|
|
|
|
var externalLoginProvider = (IExternalLoginProvider) scope.ServiceProvider
|
|
|
|
|
.GetRequiredService(externalLoginProviderInfo.Type);
|
|
|
|
|
|
|
|
|
|
context.Result = new GrantValidationResult(
|
|
|
|
|
sub,
|
|
|
|
|
OidcConstants.AuthenticationMethods.Password,
|
|
|
|
|
additionalClaims.ToArray()
|
|
|
|
|
);
|
|
|
|
|
if (await externalLoginProvider.TryAuthenticateAsync(context.UserName, context.Password))
|
|
|
|
|
{
|
|
|
|
|
user = await UserManager.FindByNameAsync(context.UserName);
|
|
|
|
|
if (user == null)
|
|
|
|
|
{
|
|
|
|
|
user = await externalLoginProvider.CreateUserAsync(context.UserName, externalLoginProviderInfo.Name);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
await externalLoginProvider.UpdateUserAsync(user, externalLoginProviderInfo.Name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await IdentitySecurityLogManager.SaveAsync(
|
|
|
|
|
new IdentitySecurityLogContext
|
|
|
|
|
{
|
|
|
|
|
Identity = IdentityServerSecurityLogIdentityConsts.IdentityServer,
|
|
|
|
|
Action = IdentityServerSecurityLogActionConsts.LoginSucceeded,
|
|
|
|
|
UserName = context.UserName,
|
|
|
|
|
ClientId = clientId
|
|
|
|
|
await SetSuccessResultAsync(context, user);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (AbpIdentityOptions.ExternalLoginProviders.Any())
|
|
|
|
|
{
|
|
|
|
|
foreach (var externalLoginProviderInfo in AbpIdentityOptions.ExternalLoginProviders.Values)
|
|
|
|
|
user = await UserManager.FindByNameAsync(context.UserName);
|
|
|
|
|
string errorDescription;
|
|
|
|
|
if (user != null)
|
|
|
|
|
{
|
|
|
|
|
var externalLoginProvider = (IExternalLoginProvider) scope.ServiceProvider
|
|
|
|
|
.GetRequiredService(externalLoginProviderInfo.Type);
|
|
|
|
|
|
|
|
|
|
if (await externalLoginProvider.TryAuthenticateAsync(context.UserName, context.Password))
|
|
|
|
|
await IdentityOptions.SetAsync();
|
|
|
|
|
var result = await SignInManager.CheckPasswordSignInAsync(user, context.Password, true);
|
|
|
|
|
if (result.Succeeded)
|
|
|
|
|
{
|
|
|
|
|
user = await UserManager.FindByNameAsync(context.UserName);
|
|
|
|
|
if (user == null)
|
|
|
|
|
if (await IsTfaEnabledAsync(user))
|
|
|
|
|
{
|
|
|
|
|
user = await externalLoginProvider.CreateUserAsync(context.UserName, externalLoginProviderInfo.Name);
|
|
|
|
|
await HandleTwoFactorLoginAsync(context, user);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
await externalLoginProvider.UpdateUserAsync(user, externalLoginProviderInfo.Name);
|
|
|
|
|
await SetSuccessResultAsync(context, user);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await SetSuccessResultAsync();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (result.IsLockedOut)
|
|
|
|
|
{
|
|
|
|
|
Logger.LogInformation("Authentication failed for username: {username}, reason: locked out", context.UserName);
|
|
|
|
|
errorDescription = Localizer["UserLockedOut"];
|
|
|
|
|
}
|
|
|
|
|
else if (result.IsNotAllowed)
|
|
|
|
|
{
|
|
|
|
|
Logger.LogInformation("Authentication failed for username: {username}, reason: not allowed", context.UserName);
|
|
|
|
|
errorDescription = Localizer["LoginIsNotAllowed"];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Logger.LogInformation("Authentication failed for username: {username}, reason: invalid credentials", context.UserName);
|
|
|
|
|
errorDescription = Localizer["InvalidUserNameOrPassword"];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext
|
|
|
|
|
{
|
|
|
|
|
Identity = IdentityServerSecurityLogIdentityConsts.IdentityServer,
|
|
|
|
|
Action = result.ToIdentitySecurityLogAction(),
|
|
|
|
|
UserName = context.UserName,
|
|
|
|
|
ClientId = await FindClientIdAsync(context)
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Logger.LogInformation("No user found matching username: {username}", context.UserName);
|
|
|
|
|
errorDescription = Localizer["InvalidUsername"];
|
|
|
|
|
|
|
|
|
|
await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext()
|
|
|
|
|
{
|
|
|
|
|
Identity = IdentityServerSecurityLogIdentityConsts.IdentityServer,
|
|
|
|
|
Action = IdentityServerSecurityLogActionConsts.LoginInvalidUserName,
|
|
|
|
|
UserName = context.UserName,
|
|
|
|
|
ClientId = await FindClientIdAsync(context)
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, errorDescription);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
user = await UserManager.FindByNameAsync(context.UserName);
|
|
|
|
|
string errorDescription;
|
|
|
|
|
if (user != null)
|
|
|
|
|
protected virtual async Task HandleTwoFactorLoginAsync(ResourceOwnerPasswordValidationContext context, IdentityUser user)
|
|
|
|
|
{
|
|
|
|
|
var twoFactorProvider = context.Request?.Raw?["TwoFactorProvider"];
|
|
|
|
|
var twoFactorCode = context.Request?.Raw?["TwoFactorCode"];
|
|
|
|
|
if (!twoFactorProvider.IsNullOrWhiteSpace() && !twoFactorCode.IsNullOrWhiteSpace())
|
|
|
|
|
{
|
|
|
|
|
await IdentityOptions.SetAsync();
|
|
|
|
|
var result = await SignInManager.CheckPasswordSignInAsync(user, context.Password, true);
|
|
|
|
|
if (result.Succeeded)
|
|
|
|
|
var providers = await UserManager.GetValidTwoFactorProvidersAsync(user);
|
|
|
|
|
if (providers.Contains(twoFactorProvider) && await UserManager.VerifyTwoFactorTokenAsync(user, twoFactorProvider, twoFactorCode))
|
|
|
|
|
{
|
|
|
|
|
await SetSuccessResultAsync();
|
|
|
|
|
await SetSuccessResultAsync(context, user);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
else if (result.IsLockedOut)
|
|
|
|
|
{
|
|
|
|
|
Logger.LogInformation("Authentication failed for username: {username}, reason: locked out", context.UserName);
|
|
|
|
|
errorDescription = Localizer["UserLockedOut"];
|
|
|
|
|
}
|
|
|
|
|
else if (result.IsNotAllowed)
|
|
|
|
|
{
|
|
|
|
|
Logger.LogInformation("Authentication failed for username: {username}, reason: not allowed", context.UserName);
|
|
|
|
|
errorDescription = Localizer["LoginIsNotAllowed"];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Logger.LogInformation("Authentication failed for username: {username}, reason: invalid credentials", context.UserName);
|
|
|
|
|
errorDescription = Localizer["InvalidUserNameOrPassword"];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext
|
|
|
|
|
{
|
|
|
|
|
Identity = IdentityServerSecurityLogIdentityConsts.IdentityServer,
|
|
|
|
|
Action = result.ToIdentitySecurityLogAction(),
|
|
|
|
|
UserName = context.UserName,
|
|
|
|
|
ClientId = clientId
|
|
|
|
|
});
|
|
|
|
|
Logger.LogInformation("Authentication failed for username: {username}, reason: InvalidAuthenticatorCode", context.UserName);
|
|
|
|
|
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, Localizer["InvalidAuthenticatorCode"]);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Logger.LogInformation("No user found matching username: {username}", context.UserName);
|
|
|
|
|
errorDescription = Localizer["InvalidUsername"];
|
|
|
|
|
Logger.LogInformation("Authentication failed for username: {username}, reason: RequiresTwoFactor", context.UserName);
|
|
|
|
|
var twoFactorToken = await UserManager.GenerateUserTokenAsync(user, TokenOptions.DefaultProvider, nameof(SignInResult.RequiresTwoFactor));
|
|
|
|
|
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, nameof(SignInResult.RequiresTwoFactor),
|
|
|
|
|
new Dictionary<string, object>()
|
|
|
|
|
{
|
|
|
|
|
{"userId", user.Id},
|
|
|
|
|
{"twoFactorToken", twoFactorToken}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext()
|
|
|
|
|
await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext
|
|
|
|
|
{
|
|
|
|
|
Identity = IdentityServerSecurityLogIdentityConsts.IdentityServer,
|
|
|
|
|
Action = IdentityServerSecurityLogActionConsts.LoginInvalidUserName,
|
|
|
|
|
Action = IdentityServerSecurityLogActionConsts.LoginRequiresTwoFactor,
|
|
|
|
|
UserName = context.UserName,
|
|
|
|
|
ClientId = clientId
|
|
|
|
|
ClientId = await FindClientIdAsync(context)
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected virtual async Task SetSuccessResultAsync(ResourceOwnerPasswordValidationContext context, IdentityUser user)
|
|
|
|
|
{
|
|
|
|
|
var sub = await UserManager.GetUserIdAsync(user);
|
|
|
|
|
|
|
|
|
|
Logger.LogInformation("Credentials validated for username: {username}", context.UserName);
|
|
|
|
|
|
|
|
|
|
var additionalClaims = new List<Claim>();
|
|
|
|
|
|
|
|
|
|
await AddCustomClaimsAsync(additionalClaims, user, context);
|
|
|
|
|
|
|
|
|
|
context.Result = new GrantValidationResult(
|
|
|
|
|
sub,
|
|
|
|
|
OidcConstants.AuthenticationMethods.Password,
|
|
|
|
|
additionalClaims.ToArray()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, errorDescription);
|
|
|
|
|
await IdentitySecurityLogManager.SaveAsync(
|
|
|
|
|
new IdentitySecurityLogContext
|
|
|
|
|
{
|
|
|
|
|
Identity = IdentityServerSecurityLogIdentityConsts.IdentityServer,
|
|
|
|
|
Action = IdentityServerSecurityLogActionConsts.LoginSucceeded,
|
|
|
|
|
UserName = context.UserName,
|
|
|
|
|
ClientId = await FindClientIdAsync(context)
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected virtual async Task ReplaceEmailToUsernameOfInputIfNeeds(ResourceOwnerPasswordValidationContext context)
|
|
|
|
@ -196,6 +242,16 @@ namespace Volo.Abp.IdentityServer.AspNetIdentity
|
|
|
|
|
context.UserName = userByEmail.UserName;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected virtual Task<string> FindClientIdAsync(ResourceOwnerPasswordValidationContext context)
|
|
|
|
|
{
|
|
|
|
|
return Task.FromResult(context.Request?.Client?.ClientId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected virtual async Task<bool> IsTfaEnabledAsync(IdentityUser user)
|
|
|
|
|
=> UserManager.SupportsUserTwoFactor &&
|
|
|
|
|
await UserManager.GetTwoFactorEnabledAsync(user) &&
|
|
|
|
|
(await UserManager.GetValidTwoFactorProvidersAsync(user)).Count > 0;
|
|
|
|
|
|
|
|
|
|
protected virtual Task AddCustomClaimsAsync(List<Claim> customClaims, IdentityUser user, ResourceOwnerPasswordValidationContext context)
|
|
|
|
|
{
|
|
|
|
|
if (user.TenantId.HasValue)
|
|
|
|
|