Skip to content

[PM-31107] Seeder: blob-migration preset + fido2/password-history/linked-field fixture support#7809

Open
shane-melton wants to merge 2 commits into
mainfrom
vault/pm-31107/seeder-blob-migration-preset
Open

[PM-31107] Seeder: blob-migration preset + fido2/password-history/linked-field fixture support#7809
shane-melton wants to merge 2 commits into
mainfrom
vault/pm-31107/seeder-blob-migration-preset

Conversation

@shane-melton

@shane-melton shane-melton commented Jun 12, 2026

Copy link
Copy Markdown
Member

🎟️ 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 field linkedId, 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/SeedPasswordHistory records
  • CipherSeed.cs — maps password history, linked-field IDs, and passkeys (key material is synthesized via the existing CreateFido2Credential helper; the fixture only supplies rp/user identifiers)
  • cipher.schema.json — simplified fido2Credential to the honored identifiers

Any fixture can now seed passkeys, password history, and linked fields.

2. New individual.blob-migration preset + blob-migration cipher 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's type_data + payload branches.

Usage: dotnet run -- preset --name individual.blob-migration → login blobmigration@individual.example / asdfasdfasdf.

@github-actions

github-actions Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

🤖 Bitwarden Claude Code Review

Overall Assessment: APPROVE

This PR is dev/test tooling under util/Seeder/ that wires up three fixture fields the schema already advertised (fido2Credentials, passwordHistory, field linkedId) and adds a new individual.blob-migration preset + cipher fixture to drive end-to-end coverage of the SDK V1→V2 blob-encryption migration. Reviewed CipherSeed.cs mapping logic, SeedModels.cs deserialization records, the simplified fido2Credential schema definition, and the new fixture/preset JSON files. The mapping correctly delegates passkey key material to the existing LoginCipherSeeder.CreateFido2Credential helper, parses ISO-8601 dates with DateTimeStyles.RoundtripKind, and places passwordHistory on LoginViewDto where the data model expects it. No security, correctness, or breaking-change concerns surfaced — all fixture values are clearly fake test data (standard test card number, fake* prefixes, 000-00-0000 SSN) and the preset follows the same shape as the existing individual/premium.json.

@shane-melton shane-melton requested a review from theMickster June 12, 2026 22:17
@codecov

codecov Bot commented Jun 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 61.12%. Comparing base (ae6affa) to head (c1743fa).
⚠️ Report is 15 commits behind head on main.

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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@sonarqubecloud

Copy link
Copy Markdown

@theMickster theMickster left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

  1. The SeedLogin class should have the PasswordHistory list(it moved from SeedVaultItem).
public List<SeedPasswordHistory>? PasswordHistory { get; init; }
  1. 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)
  1. Seeds/schemas/cipher.schema.json — move the property out of item and into the login $def. (Moved from ~lines 65-70 to under the fido2Credentials around line ~104)

Comment on lines 192 to 193
"passwordHistory": {
"type": "object",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ 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.

Suggested change
"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) =>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ / ♻️ Brief architectural note on the enhancements to 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()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants