mirror of
https://github.com/bitwarden/server.git
synced 2026-01-31 14:13:18 +08:00
feat(register): [PM-27084] Account Register Uses New Data Types (#6715)
* feat(register): [PM-27084] Account Register Uses New Data Types - Implementation * test(register): [PM-27084] Account Register Uses New Data Types - Added tests
This commit is contained in:
committed by
GitHub
parent
c7e364a39c
commit
8cb8030534
@@ -3,10 +3,13 @@
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Auth.Models.Api.Request.Accounts;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.KeyManagement.Models.Api.Request;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Identity;
|
||||
using Bit.Test.Common.Helpers;
|
||||
@@ -23,6 +26,7 @@ public class IdentityApplicationFactory : WebApplicationFactoryBase<Startup>
|
||||
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=";
|
||||
|
||||
/// <summary>
|
||||
/// A dictionary to store registration tokens for email verification. We cannot substitute the IMailService more than once, so
|
||||
@@ -195,6 +199,68 @@ public class IdentityApplicationFactory : WebApplicationFactoryBase<Startup>
|
||||
RegisterFinishRequestModel requestModel,
|
||||
bool marketingEmails = true)
|
||||
{
|
||||
// Ensure required fields for registration finish are present.
|
||||
// Prefer legacy-path defaults (root fields) to minimize changes to tests.
|
||||
// PM-28143 - When MasterPasswordAuthenticationData is required, delete all handling of MasterPasswordHash.
|
||||
requestModel.MasterPasswordHash ??= DefaultUserPasswordHash;
|
||||
// PM-28143 - When KDF is sourced exclusively from MasterPasswordUnlockData, delete the root Kdf defaults below.
|
||||
requestModel.Kdf ??= KdfType.PBKDF2_SHA256;
|
||||
requestModel.KdfIterations ??= AuthConstants.PBKDF2_ITERATIONS.Default;
|
||||
// Ensure a symmetric key is provided when no unlock data is present
|
||||
// PM-28143 - When MasterPasswordUnlockData is required, delete the UserSymmetricKey fallback block below.
|
||||
if (requestModel.MasterPasswordUnlock == null && string.IsNullOrWhiteSpace(requestModel.UserSymmetricKey))
|
||||
{
|
||||
requestModel.UserSymmetricKey = "user_symmetric_key";
|
||||
}
|
||||
|
||||
// Align unlock/auth data KDF with root KDF so login uses the provided master password hash.
|
||||
// PM-28143 - After removing root Kdf fields, build KDF exclusively from MasterPasswordUnlockData.Kdf and delete this alignment section.
|
||||
var effectiveKdfType = requestModel.Kdf ?? KdfType.PBKDF2_SHA256;
|
||||
var effectiveIterations = requestModel.KdfIterations ?? AuthConstants.PBKDF2_ITERATIONS.Default;
|
||||
int? effectiveMemory = null;
|
||||
int? effectiveParallelism = null;
|
||||
if (effectiveKdfType == KdfType.Argon2id)
|
||||
{
|
||||
effectiveIterations = AuthConstants.ARGON2_ITERATIONS.InsideRange(effectiveIterations)
|
||||
? effectiveIterations
|
||||
: AuthConstants.ARGON2_ITERATIONS.Default;
|
||||
effectiveMemory = AuthConstants.ARGON2_MEMORY.Default;
|
||||
effectiveParallelism = AuthConstants.ARGON2_PARALLELISM.Default;
|
||||
}
|
||||
|
||||
var alignedKdf = new KdfRequestModel
|
||||
{
|
||||
KdfType = effectiveKdfType,
|
||||
Iterations = effectiveIterations,
|
||||
Memory = effectiveMemory,
|
||||
Parallelism = effectiveParallelism
|
||||
};
|
||||
|
||||
if (requestModel.MasterPasswordUnlock != null)
|
||||
{
|
||||
var unlock = requestModel.MasterPasswordUnlock;
|
||||
// Always force a valid encrypted string for tests to avoid model validation failures.
|
||||
requestModel.MasterPasswordUnlock = new MasterPasswordUnlockDataRequestModel
|
||||
{
|
||||
Kdf = alignedKdf,
|
||||
MasterKeyWrappedUserKey = unlock.MasterKeyWrappedUserKey,
|
||||
Salt = string.IsNullOrWhiteSpace(unlock.Salt) ? requestModel.Email : unlock.Salt
|
||||
};
|
||||
}
|
||||
|
||||
if (requestModel.MasterPasswordAuthentication != null)
|
||||
{
|
||||
// Ensure registration uses the same hash the tests will provide at login.
|
||||
// PM-28143 - When MasterPasswordAuthenticationData is the only source of the auth hash,
|
||||
// stop overriding it from MasterPasswordHash and delete this whole reassignment block.
|
||||
requestModel.MasterPasswordAuthentication = new MasterPasswordAuthenticationDataRequestModel
|
||||
{
|
||||
Kdf = alignedKdf,
|
||||
MasterPasswordAuthenticationHash = requestModel.MasterPasswordHash,
|
||||
Salt = requestModel.Email
|
||||
};
|
||||
}
|
||||
|
||||
var sendVerificationEmailReqModel = new RegisterSendVerificationEmailRequestModel
|
||||
{
|
||||
Email = requestModel.Email,
|
||||
@@ -211,8 +277,11 @@ public class IdentityApplicationFactory : WebApplicationFactoryBase<Startup>
|
||||
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
|
||||
@@ -222,4 +291,32 @@ public class IdentityApplicationFactory : WebApplicationFactoryBase<Startup>
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
private static async Task<string> ReadResponseBodyAsync(HttpContext ctx)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (ctx?.Response?.Body == null)
|
||||
{
|
||||
return "<no body>";
|
||||
}
|
||||
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) ? "<empty body>" : text;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return $"<error reading body: {ex.Message}>";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user