|
|
|
@ -64,141 +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; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
user = await UserManager.FindByNameAsync(context.UserName); |
|
|
|
string errorDescription; |
|
|
|
if (user != null) |
|
|
|
{ |
|
|
|
await IdentityOptions.SetAsync(); |
|
|
|
var result = await SignInManager.CheckPasswordSignInAsync(user, context.Password, true); |
|
|
|
if (result.Succeeded) |
|
|
|
{ |
|
|
|
if (await IsTfaEnabled(user)) |
|
|
|
if (result.IsLockedOut) |
|
|
|
{ |
|
|
|
var twoFactorProvider = context.Request?.Raw?["TwoFactorProvider"]; |
|
|
|
var twoFactorCode = context.Request?.Raw?["TwoFactorCode"]; |
|
|
|
if (!twoFactorProvider.IsNullOrWhiteSpace() && !twoFactorCode.IsNullOrWhiteSpace()) |
|
|
|
{ |
|
|
|
var providers = await UserManager.GetValidTwoFactorProvidersAsync(user); |
|
|
|
if (providers.Contains(twoFactorProvider) && |
|
|
|
await UserManager.VerifyTwoFactorTokenAsync(user, twoFactorProvider, twoFactorCode)) |
|
|
|
{ |
|
|
|
await SetSuccessResultAsync(); |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
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} |
|
|
|
}); |
|
|
|
return; |
|
|
|
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 SetSuccessResultAsync(); |
|
|
|
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"]; |
|
|
|
await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext |
|
|
|
{ |
|
|
|
Identity = IdentityServerSecurityLogIdentityConsts.IdentityServer, |
|
|
|
Action = result.ToIdentitySecurityLogAction(), |
|
|
|
UserName = context.UserName, |
|
|
|
ClientId = await FindClientIdAsync(context) |
|
|
|
}); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
Logger.LogInformation("Authentication failed for username: {username}, reason: invalid credentials", context.UserName); |
|
|
|
errorDescription = Localizer["InvalidUserNameOrPassword"]; |
|
|
|
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) |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext |
|
|
|
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, errorDescription); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
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()) |
|
|
|
{ |
|
|
|
var providers = await UserManager.GetValidTwoFactorProvidersAsync(user); |
|
|
|
if (providers.Contains(twoFactorProvider) && await UserManager.VerifyTwoFactorTokenAsync(user, twoFactorProvider, twoFactorCode)) |
|
|
|
{ |
|
|
|
Identity = IdentityServerSecurityLogIdentityConsts.IdentityServer, |
|
|
|
Action = result.ToIdentitySecurityLogAction(), |
|
|
|
UserName = context.UserName, |
|
|
|
ClientId = clientId |
|
|
|
}); |
|
|
|
await SetSuccessResultAsync(context, user); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
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>(); |
|
|
|
|
|
|
|
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, errorDescription); |
|
|
|
await AddCustomClaimsAsync(additionalClaims, user, context); |
|
|
|
|
|
|
|
context.Result = new GrantValidationResult( |
|
|
|
sub, |
|
|
|
OidcConstants.AuthenticationMethods.Password, |
|
|
|
additionalClaims.ToArray() |
|
|
|
); |
|
|
|
|
|
|
|
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) |
|
|
|
@ -223,7 +242,12 @@ namespace Volo.Abp.IdentityServer.AspNetIdentity |
|
|
|
context.UserName = userByEmail.UserName; |
|
|
|
} |
|
|
|
|
|
|
|
protected virtual async Task<bool> IsTfaEnabled(IdentityUser user) |
|
|
|
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; |
|
|
|
|