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.
122 lines
4.4 KiB
122 lines
4.4 KiB
using Bit.Core.Entities; |
|
using Bit.Core.Vault.Entities; |
|
using Bit.Core.Vault.Enums; |
|
using Bit.Seeder.Factories; |
|
using Bit.Seeder.Models; |
|
using Bit.Seeder.Pipeline; |
|
|
|
namespace Bit.Seeder.Steps; |
|
|
|
/// <summary> |
|
/// Loads cipher items from a fixture and creates encrypted cipher entities. |
|
/// Supports both organization ciphers (encrypted with org key, assigned to collections) |
|
/// and personal ciphers (encrypted with user key, no collections). |
|
/// </summary> |
|
internal sealed class CreateCiphersStep : IStep |
|
{ |
|
private readonly string _fixtureName; |
|
private readonly bool _skipCollectionAssignment; |
|
private readonly bool _personal; |
|
|
|
private CreateCiphersStep(string fixtureName, bool skipCollectionAssignment, bool personal) |
|
{ |
|
_fixtureName = fixtureName; |
|
_skipCollectionAssignment = skipCollectionAssignment; |
|
_personal = personal; |
|
} |
|
|
|
internal static CreateCiphersStep ForOrganization(string fixtureName, bool skipCollectionAssignment = false) => |
|
new(fixtureName, skipCollectionAssignment, personal: false); |
|
|
|
internal static CreateCiphersStep ForPersonalVault(string fixtureName) => |
|
new(fixtureName, skipCollectionAssignment: true, personal: true); |
|
|
|
public void Execute(SeederContext context) |
|
{ |
|
string encryptionKey; |
|
Guid? organizationId = null; |
|
Guid? userId = null; |
|
|
|
if (_personal) |
|
{ |
|
var userDigest = context.Registry.UserDigests[0]; |
|
encryptionKey = userDigest.SymmetricKey; |
|
userId = userDigest.UserId; |
|
} |
|
else |
|
{ |
|
organizationId = context.RequireOrgId(); |
|
encryptionKey = context.RequireOrgKey(); |
|
} |
|
|
|
var seedFile = context.GetSeedReader().Read<SeedFile>($"ciphers.{_fixtureName}"); |
|
var collectionIds = context.Registry.CollectionIds; |
|
|
|
var ciphers = new List<Cipher>(seedFile.Items.Count); |
|
var collectionCiphers = new List<CollectionCipher>(); |
|
|
|
for (var i = 0; i < seedFile.Items.Count; i++) |
|
{ |
|
var item = seedFile.Items[i]; |
|
var options = CipherSeed.FromSeedItem(item) with |
|
{ |
|
EncryptionKey = encryptionKey, |
|
OrganizationId = organizationId, |
|
UserId = userId |
|
}; |
|
options.Validate(); |
|
var cipher = options.Type switch |
|
{ |
|
CipherType.Login => LoginCipherSeeder.Create(options), |
|
CipherType.Card => CardCipherSeeder.Create(options), |
|
CipherType.Identity => IdentityCipherSeeder.Create(options), |
|
CipherType.SecureNote => SecureNoteCipherSeeder.Create(options), |
|
CipherType.SSHKey => SshKeyCipherSeeder.Create(options), |
|
_ => throw new ArgumentOutOfRangeException(nameof(options), $"Unsupported cipher type: {options.Type}") |
|
}; |
|
|
|
if (item.Favorite == true && userId.HasValue) |
|
{ |
|
cipher.Favorites = CipherComposer.BuildFavoritesJson([userId.Value]); |
|
} |
|
|
|
cipher.Reprompt = options.Reprompt; |
|
|
|
ciphers.Add(cipher); |
|
|
|
if (context.Registry.FixtureCipherNameToId.ContainsKey(item.Name)) |
|
{ |
|
throw new InvalidOperationException( |
|
$"Duplicate cipher name '{item.Name}' in fixture '{_fixtureName}'. " + |
|
"Cipher names must be unique for folder and favorite assignments."); |
|
} |
|
|
|
context.Registry.FixtureCipherNameToId[item.Name] = cipher.Id; |
|
|
|
// Collection assignment (round-robin, skipped for personal vaults or when collectionAssignments handles it) |
|
if (_skipCollectionAssignment || collectionIds.Count <= 0) |
|
{ |
|
continue; |
|
} |
|
|
|
collectionCiphers.Add(new CollectionCipher |
|
{ |
|
CipherId = cipher.Id, |
|
CollectionId = collectionIds[i % collectionIds.Count] |
|
}); |
|
|
|
// Every 3rd cipher gets assigned to an additional collection |
|
if (i % 3 == 0 && collectionIds.Count > 1) |
|
{ |
|
collectionCiphers.Add(new CollectionCipher |
|
{ |
|
CipherId = cipher.Id, |
|
CollectionId = collectionIds[(i + 1) % collectionIds.Count] |
|
}); |
|
} |
|
} |
|
|
|
context.Ciphers.AddRange(ciphers); |
|
context.CollectionCiphers.AddRange(collectionCiphers); |
|
} |
|
}
|
|
|