You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
197 lines
6.6 KiB
197 lines
6.6 KiB
using Bit.Core.Vault.Enums; |
|
|
|
namespace Bit.Seeder.Models; |
|
|
|
/// <summary> |
|
/// Normalized, strongly-typed plaintext for a single cipher before encryption. |
|
/// Constructed from fixture JSON via FromSeedItem() or programmatically via CipherComposer. |
|
/// </summary> |
|
internal record CipherSeed |
|
{ |
|
/// <summary> |
|
/// Determines which type-specific DTO (Login, Card, Identity, SecureNote, SshKey) |
|
/// the factory reads. Exactly one matching DTO must be non-null. |
|
/// </summary> |
|
public required CipherType Type { get; init; } |
|
|
|
/// <summary> |
|
/// Plaintext cipher name (will be encrypted by the factory). |
|
/// </summary> |
|
public required string Name { get; init; } |
|
|
|
/// <summary> |
|
/// Symmetric key (org key or user key) used for Rust FFI encryption. |
|
/// </summary> |
|
public string? EncryptionKey { get; init; } |
|
|
|
/// <summary> |
|
/// Optional plaintext notes (will be encrypted by the factory). |
|
/// </summary> |
|
public string? Notes { get; init; } |
|
|
|
/// <summary> |
|
/// Optional custom fields (will be encrypted by the factory). |
|
/// </summary> |
|
public List<FieldViewDto>? Fields { get; init; } |
|
|
|
/// <summary> |
|
/// Master-password re-prompt (0 = None, 1 = Password). |
|
/// </summary> |
|
public CipherRepromptType Reprompt { get; init; } |
|
|
|
/// <summary> |
|
/// Owning organization. Null for personal vault ciphers. |
|
/// </summary> |
|
public Guid? OrganizationId { get; init; } |
|
|
|
/// <summary> |
|
/// Owning user for personal vault ciphers. Null for organization ciphers. |
|
/// </summary> |
|
public Guid? UserId { get; init; } |
|
|
|
/// <summary> |
|
/// Plaintext login data (username, password, URIs). Non-null when Type is Login. |
|
/// </summary> |
|
public LoginViewDto? Login { get; init; } |
|
|
|
/// <summary> |
|
/// Plaintext card data (cardholder, number, expiry). Non-null when Type is Card. |
|
/// </summary> |
|
public CardViewDto? Card { get; init; } |
|
|
|
/// <summary> |
|
/// Plaintext identity data (name, address, documents). Non-null when Type is Identity. |
|
/// </summary> |
|
public IdentityViewDto? Identity { get; init; } |
|
|
|
/// <summary> |
|
/// Secure note type marker. Non-null when Type is SecureNote. |
|
/// The actual note content is carried by the Notes property, not this DTO. |
|
/// </summary> |
|
public SecureNoteViewDto? SecureNote { get; init; } |
|
|
|
/// <summary> |
|
/// Plaintext SSH key data (private key, public key, fingerprint). Non-null when Type is SSHKey. |
|
/// </summary> |
|
public SshKeyViewDto? SshKey { get; init; } |
|
|
|
/// <summary> |
|
/// Validates that required fields are set before factory consumption. |
|
/// Call after populating EncryptionKey via <c>with</c>. |
|
/// </summary> |
|
internal void Validate() |
|
{ |
|
ArgumentException.ThrowIfNullOrEmpty(EncryptionKey); |
|
} |
|
|
|
/// <summary> |
|
/// Maps a deserialized <see cref="SeedVaultItem"/> into a <see cref="CipherSeed"/>, |
|
/// converting Seed* models to their ViewDto counterparts. |
|
/// EncryptionKey, OrganizationId, and UserId are left null — callers set them via <c>with</c>. |
|
/// </summary> |
|
internal static CipherSeed FromSeedItem(SeedVaultItem item) => new() |
|
{ |
|
Type = MapCipherType(item.Type), |
|
Name = item.Name, |
|
Notes = item.Notes, |
|
Reprompt = item.Reprompt == 1 ? CipherRepromptType.Password : CipherRepromptType.None, |
|
Fields = MapFields(item.Fields), |
|
Login = MapLogin(item.Login), |
|
Card = MapCard(item.Card), |
|
Identity = MapIdentity(item.Identity), |
|
SecureNote = item.Type == "secureNote" ? new SecureNoteViewDto { Type = 0 } : null, |
|
SshKey = MapSshKey(item.SshKey) |
|
}; |
|
|
|
private static CipherType MapCipherType(string type) => type switch |
|
{ |
|
"login" => CipherType.Login, |
|
"card" => CipherType.Card, |
|
"identity" => CipherType.Identity, |
|
"secureNote" => CipherType.SecureNote, |
|
"sshKey" => CipherType.SSHKey, |
|
_ => throw new ArgumentException($"Unknown cipher type: '{type}'", nameof(type)) |
|
}; |
|
|
|
private static List<FieldViewDto>? MapFields(List<SeedField>? fields) => |
|
fields?.Select(f => new FieldViewDto |
|
{ |
|
Name = f.Name, |
|
Value = f.Value, |
|
Type = MapFieldType(f.Type) |
|
}).ToList(); |
|
|
|
private static int MapFieldType(string type) => type switch |
|
{ |
|
"hidden" => 1, |
|
"boolean" => 2, |
|
"linked" => 3, |
|
"text" => 0, |
|
_ => throw new ArgumentException($"Unknown field type: '{type}'", nameof(type)) |
|
}; |
|
|
|
private static LoginViewDto? MapLogin(SeedLogin? login) => |
|
login == null ? null : new LoginViewDto |
|
{ |
|
Username = login.Username, |
|
Password = login.Password, |
|
Totp = login.Totp, |
|
Uris = login.Uris?.Select(u => new LoginUriViewDto |
|
{ |
|
Uri = u.Uri, |
|
Match = MapUriMatchType(u.Match) |
|
}).ToList() |
|
}; |
|
|
|
private static int MapUriMatchType(string match) => match switch |
|
{ |
|
"host" => 1, |
|
"startsWith" => 2, |
|
"exact" => 3, |
|
"regex" => 4, |
|
"never" => 5, |
|
"domain" => 0, |
|
_ => throw new ArgumentException($"Unknown URI match type: '{match}'", nameof(match)) |
|
}; |
|
|
|
private static CardViewDto? MapCard(SeedCard? card) => |
|
card == null ? null : new CardViewDto |
|
{ |
|
CardholderName = card.CardholderName, |
|
Brand = card.Brand, |
|
Number = card.Number, |
|
ExpMonth = card.ExpMonth, |
|
ExpYear = card.ExpYear, |
|
Code = card.Code |
|
}; |
|
|
|
private static IdentityViewDto? MapIdentity(SeedIdentity? identity) => |
|
identity == null ? null : new IdentityViewDto |
|
{ |
|
FirstName = identity.FirstName, |
|
MiddleName = identity.MiddleName, |
|
LastName = identity.LastName, |
|
Address1 = identity.Address1, |
|
Address2 = identity.Address2, |
|
Address3 = identity.Address3, |
|
City = identity.City, |
|
State = identity.State, |
|
PostalCode = identity.PostalCode, |
|
Country = identity.Country, |
|
Company = identity.Company, |
|
Email = identity.Email, |
|
Phone = identity.Phone, |
|
SSN = identity.Ssn, |
|
Username = identity.Username, |
|
PassportNumber = identity.PassportNumber, |
|
LicenseNumber = identity.LicenseNumber |
|
}; |
|
|
|
private static SshKeyViewDto? MapSshKey(SeedSshKey? sshKey) => |
|
sshKey == null ? null : new SshKeyViewDto |
|
{ |
|
PrivateKey = sshKey.PrivateKey, |
|
PublicKey = sshKey.PublicKey, |
|
Fingerprint = sshKey.KeyFingerprint |
|
}; |
|
}
|
|
|