mirror of
https://github.com/bitwarden/server
synced 2026-01-02 08:33:48 +00:00
feat(auth-validator): [PM-22975] Client Version Validator - initial implementation
This commit is contained in:
@@ -40,6 +40,7 @@ public abstract class BaseRequestValidator<T> where T : class
|
||||
private readonly IUserRepository _userRepository;
|
||||
private readonly IAuthRequestRepository _authRequestRepository;
|
||||
private readonly IMailService _mailService;
|
||||
private readonly IClientVersionValidator _clientVersionValidator;
|
||||
|
||||
protected ICurrentContext CurrentContext { get; }
|
||||
protected IPolicyService PolicyService { get; }
|
||||
@@ -68,7 +69,8 @@ public abstract class BaseRequestValidator<T> where T : class
|
||||
IPolicyRequirementQuery policyRequirementQuery,
|
||||
IAuthRequestRepository authRequestRepository,
|
||||
IMailService mailService,
|
||||
IUserAccountKeysQuery userAccountKeysQuery
|
||||
IUserAccountKeysQuery userAccountKeysQuery,
|
||||
IClientVersionValidator clientVersionValidator
|
||||
)
|
||||
{
|
||||
_userManager = userManager;
|
||||
@@ -89,6 +91,7 @@ public abstract class BaseRequestValidator<T> where T : class
|
||||
_authRequestRepository = authRequestRepository;
|
||||
_mailService = mailService;
|
||||
_accountKeysQuery = userAccountKeysQuery;
|
||||
_clientVersionValidator = clientVersionValidator;
|
||||
}
|
||||
|
||||
protected async Task ValidateAsync(T context, ValidatedTokenRequest request,
|
||||
@@ -259,6 +262,7 @@ public abstract class BaseRequestValidator<T> where T : class
|
||||
return
|
||||
[
|
||||
() => ValidateMasterPasswordAsync(context, validatorContext),
|
||||
() => ValidateClientVersionAsync(context, validatorContext),
|
||||
() => ValidateTwoFactorAsync(context, request, validatorContext),
|
||||
() => ValidateSsoAsync(context, request, validatorContext),
|
||||
() => ValidateNewDeviceAsync(context, request, validatorContext),
|
||||
@@ -272,6 +276,7 @@ public abstract class BaseRequestValidator<T> where T : class
|
||||
return
|
||||
[
|
||||
() => ValidateMasterPasswordAsync(context, validatorContext),
|
||||
() => ValidateClientVersionAsync(context, validatorContext),
|
||||
() => ValidateSsoAsync(context, request, validatorContext),
|
||||
() => ValidateTwoFactorAsync(context, request, validatorContext),
|
||||
() => ValidateNewDeviceAsync(context, request, validatorContext),
|
||||
@@ -323,6 +328,24 @@ public abstract class BaseRequestValidator<T> where T : class
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates whether the client version is compatible for the user attempting to authenticate.
|
||||
/// New authentications only; refresh/device grants are handled elsewhere.
|
||||
/// </summary>
|
||||
/// <returns>true if the scheme successfully passed validation, otherwise false.</returns>
|
||||
private async Task<bool> ValidateClientVersionAsync(T context, CustomValidatorRequestContext validatorContext)
|
||||
{
|
||||
var ok = await _clientVersionValidator.ValidateAsync(validatorContext.User, validatorContext);
|
||||
if (ok)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
SetValidationErrorResult(context, validatorContext);
|
||||
await LogFailedLoginEvent(validatorContext.User, EventType.User_FailedLogIn);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the user's Master Password hash.
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.KeyManagement.Queries.Interfaces;
|
||||
using Bit.Core.Models.Api;
|
||||
using Duende.IdentityServer.Validation;
|
||||
|
||||
namespace Bit.Identity.IdentityServer.RequestValidators;
|
||||
|
||||
public interface IClientVersionValidator
|
||||
{
|
||||
Task<bool> ValidateAsync(User user, CustomValidatorRequestContext requestContext);
|
||||
}
|
||||
|
||||
public class ClientVersionValidator(ICurrentContext currentContext,
|
||||
IGetMinimumClientVersionForUserQuery getMinimumClientVersionForUserQuery)
|
||||
: IClientVersionValidator
|
||||
{
|
||||
private static readonly string UpgradeMessage = "Please update your app to continue using Bitwarden";
|
||||
|
||||
public async Task<bool> ValidateAsync(User? user, CustomValidatorRequestContext requestContext)
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var clientVersion = currentContext.ClientVersion;
|
||||
var minVersion = await getMinimumClientVersionForUserQuery.Run(user);
|
||||
|
||||
// Fail-open if headers are missing or no restriction
|
||||
if (minVersion == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (clientVersion < minVersion)
|
||||
{
|
||||
requestContext.ValidationErrorResult = new ValidationResult
|
||||
{
|
||||
Error = "invalid_grant",
|
||||
ErrorDescription = UpgradeMessage,
|
||||
IsError = true
|
||||
};
|
||||
requestContext.CustomResponse = new Dictionary<string, object>
|
||||
{
|
||||
{ "ErrorModel", new ErrorResponseModel(UpgradeMessage) }
|
||||
};
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,8 @@ public class CustomTokenRequestValidator : BaseRequestValidator<CustomTokenReque
|
||||
IPolicyRequirementQuery policyRequirementQuery,
|
||||
IAuthRequestRepository authRequestRepository,
|
||||
IMailService mailService,
|
||||
IUserAccountKeysQuery userAccountKeysQuery)
|
||||
IUserAccountKeysQuery userAccountKeysQuery,
|
||||
IClientVersionValidator clientVersionValidator)
|
||||
: base(
|
||||
userManager,
|
||||
userService,
|
||||
@@ -68,7 +69,8 @@ public class CustomTokenRequestValidator : BaseRequestValidator<CustomTokenReque
|
||||
policyRequirementQuery,
|
||||
authRequestRepository,
|
||||
mailService,
|
||||
userAccountKeysQuery)
|
||||
userAccountKeysQuery,
|
||||
clientVersionValidator)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_updateInstallationCommand = updateInstallationCommand;
|
||||
|
||||
@@ -43,7 +43,8 @@ public class ResourceOwnerPasswordValidator : BaseRequestValidator<ResourceOwner
|
||||
IUserDecryptionOptionsBuilder userDecryptionOptionsBuilder,
|
||||
IPolicyRequirementQuery policyRequirementQuery,
|
||||
IMailService mailService,
|
||||
IUserAccountKeysQuery userAccountKeysQuery)
|
||||
IUserAccountKeysQuery userAccountKeysQuery,
|
||||
IClientVersionValidator clientVersionValidator)
|
||||
: base(
|
||||
userManager,
|
||||
userService,
|
||||
@@ -62,7 +63,8 @@ public class ResourceOwnerPasswordValidator : BaseRequestValidator<ResourceOwner
|
||||
policyRequirementQuery,
|
||||
authRequestRepository,
|
||||
mailService,
|
||||
userAccountKeysQuery)
|
||||
userAccountKeysQuery,
|
||||
clientVersionValidator)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_currentContext = currentContext;
|
||||
|
||||
@@ -52,7 +52,8 @@ public class WebAuthnGrantValidator : BaseRequestValidator<ExtensionGrantValidat
|
||||
IPolicyRequirementQuery policyRequirementQuery,
|
||||
IAuthRequestRepository authRequestRepository,
|
||||
IMailService mailService,
|
||||
IUserAccountKeysQuery userAccountKeysQuery)
|
||||
IUserAccountKeysQuery userAccountKeysQuery,
|
||||
IClientVersionValidator clientVersionValidator)
|
||||
: base(
|
||||
userManager,
|
||||
userService,
|
||||
@@ -71,7 +72,8 @@ public class WebAuthnGrantValidator : BaseRequestValidator<ExtensionGrantValidat
|
||||
policyRequirementQuery,
|
||||
authRequestRepository,
|
||||
mailService,
|
||||
userAccountKeysQuery)
|
||||
userAccountKeysQuery,
|
||||
clientVersionValidator)
|
||||
{
|
||||
_assertionOptionsDataProtector = assertionOptionsDataProtector;
|
||||
_assertWebAuthnLoginCredentialCommand = assertWebAuthnLoginCredentialCommand;
|
||||
|
||||
Reference in New Issue
Block a user