mirror of
https://github.com/bitwarden/server.git
synced 2025-04-24 14:26:38 -05:00
Remove captcha enforcement and issuing/verification of bypass token
This commit is contained in:
parent
4ca39ab0b7
commit
1b11496d33
@ -1,5 +1,4 @@
|
|||||||
using Bit.Core.Auth.Models.Business;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Entities;
|
|
||||||
using Duende.IdentityServer.Validation;
|
using Duende.IdentityServer.Validation;
|
||||||
|
|
||||||
namespace Bit.Identity.IdentityServer;
|
namespace Bit.Identity.IdentityServer;
|
||||||
@ -9,7 +8,7 @@ public class CustomValidatorRequestContext
|
|||||||
public User User { get; set; }
|
public User User { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This is the device that the user is using to authenticate. It can be either known or unknown.
|
/// This is the device that the user is using to authenticate. It can be either known or unknown.
|
||||||
/// We set it here since the ResourceOwnerPasswordValidator needs the device to know if CAPTCHA is required.
|
/// We set it here since the ResourceOwnerPasswordValidator needs the device to do device validation.
|
||||||
/// The option to set it here saves a trip to the database.
|
/// The option to set it here saves a trip to the database.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Device Device { get; set; }
|
public Device Device { get; set; }
|
||||||
@ -39,5 +38,4 @@ public class CustomValidatorRequestContext
|
|||||||
/// This will be null if the authentication request is successful.
|
/// This will be null if the authentication request is successful.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Dictionary<string, object> CustomResponse { get; set; }
|
public Dictionary<string, object> CustomResponse { get; set; }
|
||||||
public CaptchaResponse CaptchaResponse { get; set; }
|
|
||||||
}
|
}
|
||||||
|
@ -77,23 +77,12 @@ public abstract class BaseRequestValidator<T> where T : class
|
|||||||
protected async Task ValidateAsync(T context, ValidatedTokenRequest request,
|
protected async Task ValidateAsync(T context, ValidatedTokenRequest request,
|
||||||
CustomValidatorRequestContext validatorContext)
|
CustomValidatorRequestContext validatorContext)
|
||||||
{
|
{
|
||||||
// 1. We need to check if the user is a bot and if their master password hash is correct.
|
// 1. We need to check if the user's master password hash is correct.
|
||||||
var isBot = validatorContext.CaptchaResponse?.IsBot ?? false;
|
|
||||||
var valid = await ValidateContextAsync(context, validatorContext);
|
|
||||||
var user = validatorContext.User;
|
var user = validatorContext.User;
|
||||||
if (!valid || isBot)
|
var valid = await ValidateContextAsync(context, validatorContext);
|
||||||
|
if (!valid)
|
||||||
{
|
{
|
||||||
if (isBot)
|
await UpdateFailedAuthDetailsAsync(user, false, !validatorContext.KnownDevice);
|
||||||
{
|
|
||||||
_logger.LogInformation(Constants.BypassFiltersEventId,
|
|
||||||
"Login attempt for {UserName} detected as a captcha bot with score {CaptchaScore}.",
|
|
||||||
request.UserName, validatorContext.CaptchaResponse.Score);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!valid)
|
|
||||||
{
|
|
||||||
await UpdateFailedAuthDetailsAsync(user, false, !validatorContext.KnownDevice);
|
|
||||||
}
|
|
||||||
|
|
||||||
await BuildErrorResultAsync("Username or password is incorrect. Try again.", false, context, user);
|
await BuildErrorResultAsync("Username or password is incorrect. Try again.", false, context, user);
|
||||||
return;
|
return;
|
||||||
|
@ -85,37 +85,8 @@ public class ResourceOwnerPasswordValidator : BaseRequestValidator<ResourceOwner
|
|||||||
Device = knownDevice ?? requestDevice,
|
Device = knownDevice ?? requestDevice,
|
||||||
};
|
};
|
||||||
|
|
||||||
string bypassToken = null;
|
|
||||||
if (!validatorContext.KnownDevice &&
|
|
||||||
_captchaValidationService.RequireCaptchaValidation(_currentContext, user))
|
|
||||||
{
|
|
||||||
var captchaResponse = context.Request.Raw["captchaResponse"]?.ToString();
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(captchaResponse))
|
|
||||||
{
|
|
||||||
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "Captcha required.",
|
|
||||||
new Dictionary<string, object>
|
|
||||||
{
|
|
||||||
{ _captchaValidationService.SiteKeyResponseKeyName, _captchaValidationService.SiteKey },
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
validatorContext.CaptchaResponse = await _captchaValidationService.ValidateCaptchaResponseAsync(
|
|
||||||
captchaResponse, _currentContext.IpAddress, user);
|
|
||||||
if (!validatorContext.CaptchaResponse.Success)
|
|
||||||
{
|
|
||||||
await BuildErrorResultAsync("Captcha is invalid. Please refresh and try again", false, context, null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
bypassToken = _captchaValidationService.GenerateCaptchaBypassToken(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
await ValidateAsync(context, context.Request, validatorContext);
|
await ValidateAsync(context, context.Request, validatorContext);
|
||||||
if (context.Result.CustomResponse != null && bypassToken != null)
|
|
||||||
{
|
|
||||||
context.Result.CustomResponse["CaptchaBypassToken"] = bypassToken;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async override Task<bool> ValidateContextAsync(ResourceOwnerPasswordValidationContext context,
|
protected async override Task<bool> ValidateContextAsync(ResourceOwnerPasswordValidationContext context,
|
||||||
|
@ -80,36 +80,6 @@ public class BaseRequestValidatorTests
|
|||||||
_userDecryptionOptionsBuilder);
|
_userDecryptionOptionsBuilder);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Logic path
|
|
||||||
* ValidateAsync -> _Logger.LogInformation
|
|
||||||
* |-> BuildErrorResultAsync -> _eventService.LogUserEventAsync
|
|
||||||
* |-> SetErrorResult
|
|
||||||
*/
|
|
||||||
[Theory, BitAutoData]
|
|
||||||
public async Task ValidateAsync_IsBot_UserNotNull_ShouldBuildErrorResult_ShouldLogFailedLoginEvent(
|
|
||||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
|
||||||
CustomValidatorRequestContext requestContext,
|
|
||||||
GrantValidationResult grantResult)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
|
||||||
|
|
||||||
context.CustomValidatorRequestContext.CaptchaResponse.IsBot = true;
|
|
||||||
_sut.isValid = true;
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await _sut.ValidateAsync(context);
|
|
||||||
|
|
||||||
var errorResponse = (ErrorResponseModel)context.GrantResult.CustomResponse["ErrorModel"];
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
await _eventService.Received(1)
|
|
||||||
.LogUserEventAsync(context.CustomValidatorRequestContext.User.Id,
|
|
||||||
EventType.User_FailedLogIn);
|
|
||||||
Assert.True(context.GrantResult.IsError);
|
|
||||||
Assert.Equal("Username or password is incorrect. Try again.", errorResponse.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Logic path
|
/* Logic path
|
||||||
* ValidateAsync -> UpdateFailedAuthDetailsAsync -> _mailService.SendFailedLoginAttemptsEmailAsync
|
* ValidateAsync -> UpdateFailedAuthDetailsAsync -> _mailService.SendFailedLoginAttemptsEmailAsync
|
||||||
* |-> BuildErrorResultAsync -> _eventService.LogUserEventAsync
|
* |-> BuildErrorResultAsync -> _eventService.LogUserEventAsync
|
||||||
@ -124,7 +94,6 @@ public class BaseRequestValidatorTests
|
|||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||||
context.CustomValidatorRequestContext.CaptchaResponse.IsBot = false;
|
|
||||||
_globalSettings.Captcha.Returns(new GlobalSettings.CaptchaSettings());
|
_globalSettings.Captcha.Returns(new GlobalSettings.CaptchaSettings());
|
||||||
_globalSettings.SelfHosted = true;
|
_globalSettings.SelfHosted = true;
|
||||||
_sut.isValid = false;
|
_sut.isValid = false;
|
||||||
@ -152,7 +121,6 @@ public class BaseRequestValidatorTests
|
|||||||
// Arrange
|
// Arrange
|
||||||
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||||
|
|
||||||
context.CustomValidatorRequestContext.CaptchaResponse.IsBot = false;
|
|
||||||
// This needs to be n-1 of the max failed login attempts
|
// This needs to be n-1 of the max failed login attempts
|
||||||
context.CustomValidatorRequestContext.User.FailedLoginCount = 2;
|
context.CustomValidatorRequestContext.User.FailedLoginCount = 2;
|
||||||
context.CustomValidatorRequestContext.KnownDevice = false;
|
context.CustomValidatorRequestContext.KnownDevice = false;
|
||||||
@ -185,7 +153,6 @@ public class BaseRequestValidatorTests
|
|||||||
// Arrange
|
// Arrange
|
||||||
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||||
// 1 -> to pass
|
// 1 -> to pass
|
||||||
context.CustomValidatorRequestContext.CaptchaResponse.IsBot = false;
|
|
||||||
_sut.isValid = true;
|
_sut.isValid = true;
|
||||||
|
|
||||||
// 2 -> will result to false with no extra configuration
|
// 2 -> will result to false with no extra configuration
|
||||||
@ -222,7 +189,6 @@ public class BaseRequestValidatorTests
|
|||||||
// Arrange
|
// Arrange
|
||||||
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||||
// 1 -> to pass
|
// 1 -> to pass
|
||||||
context.CustomValidatorRequestContext.CaptchaResponse.IsBot = false;
|
|
||||||
_sut.isValid = true;
|
_sut.isValid = true;
|
||||||
|
|
||||||
// 2 -> will result to false with no extra configuration
|
// 2 -> will result to false with no extra configuration
|
||||||
@ -259,7 +225,6 @@ public class BaseRequestValidatorTests
|
|||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||||
context.CustomValidatorRequestContext.CaptchaResponse.IsBot = false;
|
|
||||||
_sut.isValid = true;
|
_sut.isValid = true;
|
||||||
|
|
||||||
context.ValidatedTokenRequest.GrantType = grantType;
|
context.ValidatedTokenRequest.GrantType = grantType;
|
||||||
@ -290,7 +255,6 @@ public class BaseRequestValidatorTests
|
|||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||||
context.CustomValidatorRequestContext.CaptchaResponse.IsBot = false;
|
|
||||||
_sut.isValid = true;
|
_sut.isValid = true;
|
||||||
|
|
||||||
context.ValidatedTokenRequest.GrantType = grantType;
|
context.ValidatedTokenRequest.GrantType = grantType;
|
||||||
@ -328,7 +292,6 @@ public class BaseRequestValidatorTests
|
|||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||||
context.CustomValidatorRequestContext.CaptchaResponse.IsBot = false;
|
|
||||||
_sut.isValid = true;
|
_sut.isValid = true;
|
||||||
|
|
||||||
context.ValidatedTokenRequest.GrantType = grantType;
|
context.ValidatedTokenRequest.GrantType = grantType;
|
||||||
@ -366,7 +329,6 @@ public class BaseRequestValidatorTests
|
|||||||
var user = context.CustomValidatorRequestContext.User;
|
var user = context.CustomValidatorRequestContext.User;
|
||||||
user.Key = null;
|
user.Key = null;
|
||||||
|
|
||||||
context.CustomValidatorRequestContext.CaptchaResponse.IsBot = false;
|
|
||||||
context.ValidatedTokenRequest.ClientId = "Not Web";
|
context.ValidatedTokenRequest.ClientId = "Not Web";
|
||||||
_sut.isValid = true;
|
_sut.isValid = true;
|
||||||
_twoFactorAuthenticationValidator
|
_twoFactorAuthenticationValidator
|
||||||
|
Loading…
x
Reference in New Issue
Block a user