[PM-31107] Seeder: blob-migration preset + fido2/password-history/linked-field fixture support#7809
[PM-31107] Seeder: blob-migration preset + fido2/password-history/linked-field fixture support#7809shane-melton wants to merge 2 commits into
Conversation
🤖 Bitwarden Claude Code ReviewOverall Assessment: APPROVE This PR is dev/test tooling under |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #7809 +/- ##
==========================================
+ Coverage 61.11% 61.12% +0.01%
==========================================
Files 2173 2175 +2
Lines 96704 96856 +152
Branches 8716 8739 +23
==========================================
+ Hits 59103 59207 +104
- Misses 35491 35539 +48
Partials 2110 2110 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
There was a problem hiding this comment.
Couple items to add/adjust to the PR
I think that we place a new document that briefly outlines the scenario in to util/Seeder/Seeds/docs/scenarios/. The document can be short and direct to start and then we link to it in the util/Seeder/Seeds/docs/scenarios/README.md. That allows for both humans & humans working with Claude can easily know what scenario to utilize.
To add a new preset, we need to round out the work by updating util/Seeder/Seeds/docs/presets.md. The following appears to be what we'll need.
| blob-migration | Premium (1GB) | — | 7 (fixture) | — |
This pairs along with this comment
We need to nest the passwordHistory under login and not the seedVaultItem.
- The
SeedLoginclass should have the PasswordHistory list(it moved fromSeedVaultItem).
public List<SeedPasswordHistory>? PasswordHistory { get; init; }- CipherSeed.cs — MapLogin back to one arg and then password history reads from the login
private static LoginViewDto? MapLogin(SeedLogin? login) =>
PasswordHistory = MapPasswordHistory(login.PasswordHistory)Seeds/schemas/cipher.schema.json— move the property out of item and into the login $def. (Moved from ~lines 65-70 to under thefido2Credentialsaround line ~104)
| "passwordHistory": { | ||
| "type": "object", |
There was a problem hiding this comment.
♻️ We have added a new model with public required string Password { get; init; } being required. Let's round of the refactor by updating the schema to enforce the same rule.
| "passwordHistory": { | |
| "type": "object", | |
| "additionalProperties": false, | |
| "required": ["password"], | |
| "properties": { | |
| "password": { "type": "string" }, | |
| "lastUsedDate": { "type": "string", "description": "ISO date when password was last used." } | |
| } | |
| } |
| }; | ||
|
|
||
| private static LoginViewDto? MapLogin(SeedLogin? login) => | ||
| private static LoginViewDto? MapLogin(SeedLogin? login, List<SeedPasswordHistory>? passwordHistory) => |
There was a problem hiding this comment.
MapLogin that I think we should change.
That new using Bit.Seeder.Factories; is the first time anything in Models/ reaches up into Factories/. Right now it's one-way: factories depend on Models, not the other way around. This flips that and drags ECDSA key synthesis into FromSeedItem, which is intended to only be a plain Seed → ViewDto map.
I think that this is a straightforward fix.. CreateFido2Credential returns a Fido2CredentialViewDto (a Models type) and only needs BCL crypto + CoreHelpers —nothing from Factories. Let's move it down into Models and the using goes away.
LoginCipherScene and CipherComposer already depend on Models, so they're fine.
A potential refactor vibed with Claude
New file — util/Seeder/Data/Generators/Fido2CredentialGenerator.cs:
using System.Security.Cryptography;
using System.Text.Json;
using Bit.Core.Utilities;
using Bit.Seeder.Models;
namespace Bit.Seeder.Data.Generators;
/// <summary>
/// Generates a discoverable FIDO2 passkey with real ECDSA P-256 key material for test vault data.
/// Callers supply only the relying-party and user identifiers; all key material is synthesized.
/// </summary>
internal static class Fido2CredentialGenerator
{
internal static Fido2CredentialViewDto Generate(string rpId, string rpName, string userName)
{
// ECDSA P-256 private key in PKCS#8 format
using var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
var keyValue = CoreHelpers.Base64UrlEncode(ecdsa.ExportPkcs8PrivateKey());
// 16-byte random user handle, unpadded base64url
var userHandleBytes = new byte[16];
RandomNumberGenerator.Fill(userHandleBytes);
var userHandle = CoreHelpers.Base64UrlEncode(userHandleBytes);
return new Fido2CredentialViewDto
{
Discoverable = JsonSerializer.Serialize(true),
CredentialId = JsonSerializer.Serialize(Guid.NewGuid()),
KeyValue = keyValue,
Counter = "0",
RpId = rpId,
RpName = rpName,
UserHandle = userHandle,
UserName = userName,
UserDisplayName = userName,
};
}
}Then in Models/CipherSeed.cs — drops the Factories using, stop synthesizing, carry the identifiers through
Then in Factories/LoginCipherSeeder.cs — delete the moved method + its now-dead usings, synthesize in Create:
// Create(CipherSeed options), after building cipherView
if (options.Login is not null && options.Fido2Credentials is { Count: > 0 })
{
cipherView.Login!.Fido2Credentials = options.Fido2Credentials
.Select(f => Fido2CredentialGenerator.Generate(
f.RpId ?? "example.com",
f.RpName ?? f.RpId ?? "Example",
f.UserName ?? options.Login.Username ?? "user"))
.ToList();
}The other two callers — already in Factories/Scenes, both can see Data.Generators (CipherComposer already imports it; the Scene needs the using added):
Factories/CipherComposer.cs:56
? new List<Fido2CredentialViewDto> { Fido2CredentialGenerator.Generate(company.Domain, company.Name, username) }Scenes/LoginCipherScene.cs (+ add: using Bit.Seeder.Data.Generators;)
.Select(p => Fido2CredentialGenerator.Generate(p.RpId, p.RpName, p.UserName)).ToList()


🎟️ Tracking
PM-31107
📔 Objective
Adds Seeder support for testing the SDK V1→V2 blob-encryption key-rotation migration end-to-end.
Two parts:
1. Fixture loader enhancements — the cipher fixture schema already advertised
fido2Credentials,passwordHistory, and fieldlinkedId, but the deserialization model silently dropped them. The encryption DTOs already supported all three; this wires the fixture-side mapping so they're honored:SeedModels.cs— added the fields +SeedFido2Credential/SeedPasswordHistoryrecordsCipherSeed.cs— maps password history, linked-field IDs, and passkeys (key material is synthesized via the existingCreateFido2Credentialhelper; the fixture only supplies rp/user identifiers)cipher.schema.json— simplifiedfido2Credentialto the honored identifiersAny fixture can now seed passkeys, password history, and linked fields.
2. New
individual.blob-migrationpreset +blob-migrationcipher fixture — a premium V1 user whose personal vault holds one of every cipher type (login, card, identity, secure note, SSH key) plus a passkey, password history, custom fields (text/hidden/boolean/linked), a reprompt item, and a favorite. This maximizes coverage of the blob conversion'stype_data+ payload branches.Usage:
dotnet run -- preset --name individual.blob-migration→ loginblobmigration@individual.example/asdfasdfasdf.