From 9a1f91f2dd2cef8a332ec456a9929b4c12022c45 Mon Sep 17 00:00:00 2001 From: Patrick Pimentel Date: Fri, 2 Jan 2026 16:44:58 -0500 Subject: [PATCH] fix(auth-validator): [PM-22975] Client Version Validator - Fixed tests. --- .../Login/ClientVersionGateTests.cs | 23 +++++++ .../Factories/IdentityApplicationFactory.cs | 61 ++++++++++++------- 2 files changed, 61 insertions(+), 23 deletions(-) diff --git a/test/Identity.IntegrationTest/Login/ClientVersionGateTests.cs b/test/Identity.IntegrationTest/Login/ClientVersionGateTests.cs index 55db8cf82a..a05e187c89 100644 --- a/test/Identity.IntegrationTest/Login/ClientVersionGateTests.cs +++ b/test/Identity.IntegrationTest/Login/ClientVersionGateTests.cs @@ -13,6 +13,7 @@ namespace Bit.Identity.IntegrationTest.Login; public class ClientVersionGateTests : IClassFixture { private readonly IdentityApplicationFactory _factory; + private const string DefaultEncryptedString = "2.3Uk+WNBIoU5xzmVFNcoWzz==|1MsPIYuRfdOHfu/0uY6H2Q==|/98sp4wb6pHP1VTZ9JcNCYgQjEUMFPlqJgCwRk1YXKg="; public ClientVersionGateTests(IdentityApplicationFactory factory) { @@ -23,6 +24,7 @@ public class ClientVersionGateTests : IClassFixture [Theory, BitAutoData, RegisterFinishRequestModelCustomize] public async Task TokenEndpoint_GrantTypePassword_V2User_OnOldClientVersion_Blocked(RegisterFinishRequestModel requestModel) { + NormalizeEncryptedStrings(requestModel); var localFactory = new IdentityApplicationFactory { UseMockClientVersionValidator = false @@ -72,6 +74,7 @@ public class ClientVersionGateTests : IClassFixture [Theory, BitAutoData, RegisterFinishRequestModelCustomize] public async Task TokenEndpoint_GrantTypePassword_V2User_OnMinClientVersion_Succeeds(RegisterFinishRequestModel requestModel) { + NormalizeEncryptedStrings(requestModel); var localFactory = new IdentityApplicationFactory { UseMockClientVersionValidator = false @@ -123,4 +126,24 @@ public class ClientVersionGateTests : IClassFixture databaseContext.Users.RemoveRange(databaseContext.Users); databaseContext.SaveChanges(); } + + private static void NormalizeEncryptedStrings(RegisterFinishRequestModel requestModel) + { + var accountKeys = requestModel.UserAsymmetricKeys.AccountKeys; + if (accountKeys == null) + { + return; + } + + accountKeys.UserKeyEncryptedAccountPrivateKey = DefaultEncryptedString; + if (accountKeys.PublicKeyEncryptionKeyPair != null) + { + accountKeys.PublicKeyEncryptionKeyPair.WrappedPrivateKey = DefaultEncryptedString; + } + if (accountKeys.SignatureKeyPair != null) + { + accountKeys.SignatureKeyPair.WrappedSigningKey = DefaultEncryptedString; + accountKeys.SignatureKeyPair.SignatureAlgorithm = "ed25519"; + } + } } diff --git a/test/IntegrationTestCommon/Factories/IdentityApplicationFactory.cs b/test/IntegrationTestCommon/Factories/IdentityApplicationFactory.cs index fc6829e451..298fe7e4ad 100644 --- a/test/IntegrationTestCommon/Factories/IdentityApplicationFactory.cs +++ b/test/IntegrationTestCommon/Factories/IdentityApplicationFactory.cs @@ -3,6 +3,7 @@ using System.Collections.Concurrent; using System.Net.Http.Json; +using System.Text; using System.Text.Json; using Bit.Core.Auth.Models.Api.Request.Accounts; using Bit.Core.Entities; @@ -25,6 +26,7 @@ public class IdentityApplicationFactory : WebApplicationFactoryBase public const string DefaultDeviceIdentifier = "92b9d953-b9b6-4eaf-9d3e-11d57144dfeb"; public const string DefaultUserEmail = "DefaultEmail@bitwarden.com"; public const string DefaultUserPasswordHash = "default_password_hash"; + private const string DefaultEncryptedString = "2.3Uk+WNBIoU5xzmVFNcoWzz==|1MsPIYuRfdOHfu/0uY6H2Q==|/98sp4wb6pHP1VTZ9JcNCYgQjEUMFPlqJgCwRk1YXKg="; public bool UseMockClientVersionValidator { get; set; } = true; /// @@ -88,20 +90,8 @@ public class IdentityApplicationFactory : WebApplicationFactoryBase var context = await ContextFromPasswordAsync( username, password, deviceIdentifier, clientId, deviceType, deviceName); - // Provide clearer diagnostics on failure - if (context.Response.StatusCode != StatusCodes.Status200OK) - { - var contentType = context.Response.ContentType ?? string.Empty; - if (context.Response.Body.CanSeek) - { - context.Response.Body.Position = 0; - } - string rawBody = await new StreamReader(context.Response.Body).ReadToEndAsync(); - throw new Xunit.Sdk.XunitException($"Login failed: status={context.Response.StatusCode}, contentType='{contentType}', body='{rawBody}'"); - } - - using var jsonDoc = await AssertHelper.AssertResponseTypeIs(context); - var root = jsonDoc.RootElement; + using var body = await AssertHelper.AssertResponseTypeIs(context); + var root = body.RootElement; return (root.GetProperty("access_token").GetString(), root.GetProperty("refresh_token").GetString()); } @@ -124,13 +114,7 @@ public class IdentityApplicationFactory : WebApplicationFactoryBase { "grant_type", "password" }, { "username", username }, { "password", password }, - }), - http => - { - // Ensure JSON content negotiation for errors and set a sane client version - http.Request.Headers.Append("Accept", "application/json"); - http.Request.Headers.Append("Bitwarden-Client-Version", "2025.11.0"); - }); + })); return context; } @@ -242,8 +226,11 @@ public class IdentityApplicationFactory : WebApplicationFactoryBase requestModel.EmailVerificationToken = RegistrationTokens[requestModel.Email]; var postRegisterFinishHttpContext = await PostRegisterFinishAsync(requestModel); - - Assert.Equal(StatusCodes.Status200OK, postRegisterFinishHttpContext.Response.StatusCode); + if (postRegisterFinishHttpContext.Response.StatusCode != StatusCodes.Status200OK) + { + var body = await ReadResponseBodyAsync(postRegisterFinishHttpContext); + Assert.Fail($"register/finish failed (status {postRegisterFinishHttpContext.Response.StatusCode}). Body: {body}"); + } var database = GetDatabaseContext(); var user = await database.Users @@ -253,4 +240,32 @@ public class IdentityApplicationFactory : WebApplicationFactoryBase return user; } + + private static async Task ReadResponseBodyAsync(HttpContext ctx) + { + try + { + if (ctx?.Response.Body == null) + { + return ""; + } + var stream = ctx.Response.Body; + if (stream.CanSeek) + { + stream.Seek(0, SeekOrigin.Begin); + } + using var reader = new StreamReader(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks: false, leaveOpen: true); + var text = await reader.ReadToEndAsync(); + if (stream.CanSeek) + { + stream.Seek(0, SeekOrigin.Begin); + } + return string.IsNullOrWhiteSpace(text) ? "" : text; + } + catch (Exception ex) + { + return $""; + } + } + }