Browse Source

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

Return 2FA info in AbpResourceOwnerPasswordValidator.
pull/9226/head
Mehmet Erim 5 years ago
committed by GitHub
parent
commit
42966c1125
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Localization/Resources/en.json
  2. 1
      modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Localization/Resources/tr.json
  3. 1
      modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Localization/Resources/zh-Hans.json
  4. 206
      modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpResourceOwnerPasswordValidator.cs

1
modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Localization/Resources/en.json

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

1
modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Localization/Resources/tr.json

@ -8,6 +8,7 @@
"InvalidUserNameOrPassword": "Kullanıcı adı ya da şifre geçersiz!", "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.", "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!", "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!" "TheTargetUserIsNotLinkedToYou": "Hedef kullanıcı sizinle bağlantılı değil!"
} }
} }

1
modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Localization/Resources/zh-Hans.json

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

206
modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpResourceOwnerPasswordValidator.cs

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

Loading…
Cancel
Save