1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-24 22:32:22 -05:00

Remove captcha enforcement and issuing/verification of bypass token

This commit is contained in:
Todd Martin 2025-04-19 19:39:10 -04:00
parent 4ca39ab0b7
commit 1b11496d33
No known key found for this signature in database
GPG Key ID: 663E7AF5C839BC8F
4 changed files with 7 additions and 87 deletions

View File

@ -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; }
} }

View File

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

View File

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

View File

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