Merge pull request #9176 from abpframework/Password-Flow-TwoFactor

Return 2FA info in AbpResourceOwnerPasswordValidator.
pull/9226/head
Mehmet Erim 4 years ago committed by GitHub
commit 42966c1125
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -9,6 +9,7 @@
"InvalidUserNameOrPassword": "Invalid username or password!",
"LoginIsNotAllowed": "You are not allowed to login! You need to confirm your email/phone number.",
"InvalidUsername": "Invalid username or password!",
"InvalidAuthenticatorCode": "Invalid authenticator code!",
"TheTargetUserIsNotLinkedToYou": "The target user is not linked to you!"
}
}

@ -8,6 +8,7 @@
"InvalidUserNameOrPassword": "Kullanıcı adı ya da şifre geçersiz!",
"LoginIsNotAllowed": "Giriş yapamazsınız! E-posta adresinizi ya da telefon numaranızı doğrulamanız gerekiyor.",
"InvalidUsername": "Kullanıcı adı ya da şifre geçersiz!",
"InvalidAuthenticatorCode": "Geçersiz kimlik doğrulama kodu!",
"TheTargetUserIsNotLinkedToYou": "Hedef kullanıcı sizinle bağlantılı değil!"
}
}

@ -9,6 +9,7 @@
"InvalidUserNameOrPassword": "用户名或密码错误!",
"LoginIsNotAllowed": "无法登录!你需要验证邮箱地址/手机号.",
"InvalidUsername": "用户名或密码错误!",
"InvalidAuthenticatorCode": "验证码无效!",
"TheTargetUserIsNotLinkedToYou": "目标用户未和你有关联!"
}
}

@ -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)

Loading…
Cancel
Save