mirror of
https://github.com/bitwarden/server
synced 2026-01-02 08:33:48 +00:00
fix(auth-validator): [PM-22975] Client Version Validator - Updated with removal of cqrs approach in favor of static user checks. Also fixed tests
This commit is contained in:
@@ -212,14 +212,31 @@ public class User : ITableObject<Guid>, IStorableSubscriber, IRevisable, ITwoFac
|
||||
return SecurityVersion ?? 1;
|
||||
}
|
||||
|
||||
public bool IsSetupForV2Encryption()
|
||||
/// <summary>
|
||||
/// Evaluates user state to determine if they are currently in a v2 encryption state.
|
||||
/// </summary>
|
||||
/// <returns>If the shape of their private key is v2 as well as has the proper security version then true, otherwise false</returns>
|
||||
public bool HasV2Encryption()
|
||||
{
|
||||
return HasV2KeyShape() && IsSecurityVersionTwo();
|
||||
}
|
||||
|
||||
private bool HasV2KeyShape()
|
||||
{
|
||||
return EncryptionParsing.GetEncryptionType(PrivateKey) == EncryptionType.XChaCha20Poly1305_B64;
|
||||
if (string.IsNullOrEmpty(PrivateKey))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return EncryptionParsing.GetEncryptionType(PrivateKey) == EncryptionType.XChaCha20Poly1305_B64;
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
// Invalid encryption string format - treat as not v2
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -26,6 +26,5 @@ public static class KeyManagementServiceCollectionExtensions
|
||||
private static void AddKeyManagementQueries(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IUserAccountKeysQuery, UserAccountKeysQuery>();
|
||||
services.AddScoped<IGetMinimumClientVersionForUserQuery, GetMinimumClientVersionForUserQuery>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.KeyManagement.Queries.Interfaces;
|
||||
|
||||
namespace Bit.Core.KeyManagement.Queries;
|
||||
|
||||
public class GetMinimumClientVersionForUserQuery()
|
||||
: IGetMinimumClientVersionForUserQuery
|
||||
{
|
||||
public Task<Version?> Run(User? user)
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
return Task.FromResult<Version?>(null);
|
||||
}
|
||||
|
||||
if (user.IsSetupForV2Encryption())
|
||||
{
|
||||
return Task.FromResult(Constants.MinimumClientVersionForV2Encryption)!;
|
||||
}
|
||||
|
||||
return Task.FromResult<Version?>(null);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
using Bit.Core.Entities;
|
||||
|
||||
namespace Bit.Core.KeyManagement.Queries.Interfaces;
|
||||
|
||||
public interface IGetMinimumClientVersionForUserQuery
|
||||
{
|
||||
Task<Version?> Run(User? user);
|
||||
}
|
||||
@@ -359,12 +359,11 @@ public abstract class BaseRequestValidator<T> where T : class
|
||||
|
||||
/// <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);
|
||||
var ok = _clientVersionValidator.ValidateAsync(validatorContext.User, validatorContext);
|
||||
if (ok)
|
||||
{
|
||||
return true;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.KeyManagement.Queries.Interfaces;
|
||||
using Bit.Core.KeyManagement;
|
||||
using Bit.Core.Models.Api;
|
||||
using Duende.IdentityServer.Validation;
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Bit.Identity.IdentityServer.RequestValidators;
|
||||
|
||||
public interface IClientVersionValidator
|
||||
{
|
||||
Task<bool> ValidateAsync(User user, CustomValidatorRequestContext requestContext);
|
||||
bool ValidateAsync(User user, CustomValidatorRequestContext requestContext);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -22,24 +22,34 @@ public interface IClientVersionValidator
|
||||
/// If the header is omitted, then the validator returns that this request is valid.
|
||||
/// </summary>
|
||||
public class ClientVersionValidator(
|
||||
ICurrentContext currentContext,
|
||||
IGetMinimumClientVersionForUserQuery getMinimumClientVersionForUserQuery)
|
||||
ICurrentContext currentContext)
|
||||
: IClientVersionValidator
|
||||
{
|
||||
private const string _upgradeMessage = "Please update your app to continue using Bitwarden";
|
||||
private const string _noUserMessage = "No user found while trying to validate client version";
|
||||
|
||||
public async Task<bool> ValidateAsync(User? user, CustomValidatorRequestContext requestContext)
|
||||
public bool ValidateAsync(User? user, CustomValidatorRequestContext requestContext)
|
||||
{
|
||||
// Do this nullish check because the base request validator currently is not
|
||||
// strict null checking. Once that gets fixed then we can see about making
|
||||
// the user not nullish checked. If they are null then the validator should fail.
|
||||
if (user == null)
|
||||
{
|
||||
requestContext.ValidationErrorResult = new ValidationResult
|
||||
{
|
||||
Error = "no_user",
|
||||
ErrorDescription = _noUserMessage,
|
||||
IsError = true
|
||||
};
|
||||
requestContext.CustomResponse = new Dictionary<string, object>
|
||||
{
|
||||
{ "ErrorModel", new ErrorResponseModel(_noUserMessage) }
|
||||
};
|
||||
return false;
|
||||
}
|
||||
|
||||
Version? clientVersion = currentContext.ClientVersion;
|
||||
Version? minVersion = await getMinimumClientVersionForUserQuery.Run(user);
|
||||
Version? minVersion = user.HasV2Encryption() ? Constants.MinimumClientVersionForV2Encryption : null;
|
||||
|
||||
// Allow through if headers are missing.
|
||||
// The minVersion should never be null because of where this validator is run. The user would
|
||||
|
||||
Reference in New Issue
Block a user