mirror of
https://github.com/bitwarden/server.git
synced 2025-04-05 05:00:19 -05:00
Extract logic into seeder classes
This commit is contained in:
parent
923f498941
commit
0abf4da126
@ -1,8 +1,4 @@
|
|||||||
using Bit.Core.Billing.Enums;
|
using Bit.Seeder.Factories;
|
||||||
using Bit.Core.Enums;
|
|
||||||
using Bit.Infrastructure.EntityFramework.AdminConsole.Models;
|
|
||||||
using Bit.Infrastructure.EntityFramework.Models;
|
|
||||||
using Bit.Seeder.Services;
|
|
||||||
using Bit.Seeder.Settings;
|
using Bit.Seeder.Settings;
|
||||||
using Bit.SharedWeb.Utilities;
|
using Bit.SharedWeb.Utilities;
|
||||||
using Microsoft.AspNetCore.DataProtection;
|
using Microsoft.AspNetCore.DataProtection;
|
||||||
@ -23,45 +19,9 @@ public class GenerateCommand
|
|||||||
|
|
||||||
var logger = serviceProvider.GetRequiredService<ILogger<GenerateCommand>>();
|
var logger = serviceProvider.GetRequiredService<ILogger<GenerateCommand>>();
|
||||||
|
|
||||||
var organization = new Organization
|
var organization = OrganizationSeeder.CreateEnterprise(name, domain, users);
|
||||||
{
|
var user = UserSeeder.CreateUser($"admin@{domain}");
|
||||||
Id = Guid.NewGuid(),
|
var orgUser = organization.CreateOrganizationUser(user);
|
||||||
Name = name,
|
|
||||||
BillingEmail = $"billing@{domain}",
|
|
||||||
Plan = "Enterprise (Annually)",
|
|
||||||
PlanType = PlanType.EnterpriseAnnually,
|
|
||||||
Seats = users,
|
|
||||||
|
|
||||||
// Currently hardcoded to the values from https://github.com/bitwarden/sdk-internal/blob/main/crates/bitwarden-core/src/client/test_accounts.rs.
|
|
||||||
// TODO: These should be dynamically generated by the SDK.
|
|
||||||
PublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmIJbGMk6eZqVE7UxhZ46Weu2jKciqOiOkSVYtGvs61rfe9AXxtLaaZEKN4d4DmkZcF6dna2eXNxZmb7U4pwlttye8ksqISe6IUAZQox7auBpjopdCEPhKRg3BD/u8ks9UxSxgWe+fpebjt6gd5hsl1/5HOObn7SeU6EEU04cp3/eH7a4OTdXxB8oN62HGV9kM/ubM1goILgjoSJDbihMK0eb7b8hPHwcA/YOgKKiu/N3FighccdSMD5Pk+HfjacsFNZQa2EsqW09IvvSZ+iL6HQeZ1vwc/6TO1J7EOfJZFQcjoEL9LVI693efYoMZSmrPEWziZ4PvwpOOGo6OObyMQIDAQAB",
|
|
||||||
PrivateKey = "2.6FggyKVyaKQsfohi5yqgbg==|UU2JeafOB41L5UscGmf4kq15JGDf3Bkf67KECiehTODzbWctVLTgyDk0Qco8/6CMN6nZGXjxR2A4r5ExhmwRNsNxd77G+MprkmiJz+7w33ROZ1ouQO5XjD3wbQ3ssqNiTKId6yAUPBvuAZRixVApauTuADc8QWGixqCQcqZzmU7YSBBIPf652/AEYr4Tk64YihoE39pHiK8MRbTLdRt3EF4LSMugPAPM24vCgUv3w1TD3Fj6sDg/6oi3flOV9SJZX4vCiUXbDNEuD/p2aQrEXVbaxweFOHjTe7F4iawjXw3nG3SO8rUBHcxbhDDVx5rjYactbW5QvHWiyla6uLb6o8WHBneg2EjTEwAHOZE/rBjcqmAJb2sVp1E0Kwq8ycGmL69vmqJPC1GqVTohAQvmEkaxIPpfq24Yb9ZPrADA7iEXBKuAQ1FphFUVgJBJGJbd60sOV1Rz1T+gUwS4wCNQ4l3LG1S22+wzUVlEku5DXFnT932tatqTyWEthqPqLCt6dL1+qa94XLpeHagXAx2VGe8n8IlcADtxqS+l8xQ4heT12WO9kC316vqvg1mnsI56faup9hb3eT9ZpKyxSBGYOphlTWfV1Y/v64f5PYvTo4aL0IYHyLY/9Qi72vFmOpPeHBYgD5t3j+H2CsiU1PkYsBggOmD7xW8FDuT6HWVvwhEJqeibVPK0Lhyj6tgvlSIAvFUaSMFPlmwFNmwfj/AHUhr9KuTfsBFTZ10yy9TZVgf+EofwnrxHBaWUgdD40aHoY1VjfG33iEuajb6buxG3pYFyPNhJNzeLZisUKIDRMQpUHrsE22EyrFFran3tZGdtcyIEK4Q1F0ULYzJ6T9iY25/ZgPy3pEAAMZCtqo3s+GjX295fWIHfMcnjMgNUHPjExjWBHa+ggK9iQXkFpBVyYB1ga/+0eiIhiek3PlgtvpDrqF7TsLK+ROiBw2GJ7uaO3EEXOj2GpNBuEJ5CdodhZkwzhwMcSatgDHkUuNVu0iVbF6/MxVdOxWXKO+jCYM6PZk/vAhLYqpPzu2T2Uyz4nkDs2Tiq61ez6FoCrzdHIiyIxVTzUQH8G9FgSmtaZ7GCbqlhnurYgcMciwPzxg0hpAQT+NZw1tVEii9vFSpJJbGJqNhORKfKh/Mu1P/9LOQq7Y0P2FIR3x/eUVEQ7CGv2jVtO5ryGSmKeq/P9Fr54wTPaNiqN2K+leACUznCdUWw8kZo/AsBcrOe4OkRX6k8LC3oeJXy06DEToatxEvPYemUauhxiXRw8nfNMqc4LyJq2bbT0zCgJHoqpozPdNg6AYWcoIobgAGu7ZQGq+oE1MT3GZxotMPe/NUJiAc5YE9Thb5Yf3gyno71pyqPTVl/6IQuh4SUz7rkgwF/aVHEnr4aUYNoc0PEzd2Me0jElsA3GAneq1I/wngutOWgTViTK4Nptr5uIzMVQs9H1rOMJNorP8b02t1NDu010rSsib9GaaJJq4r4iy46laQOxWoU0ex26arYnk+jw4833WSCTVBIprTgizZ+fKjoY0xwXvI2oOvGNEUCtGFvKFORTaQrlaXZIg1toa2BBVNicyONbwnI3KIu3MgGJ2SlCVXJn8oHFppVHFCdwgN1uDzGiKAhjvr0sZTUtXin2f2CszPTbbo=|fUhbVKrr8CSKE7TZJneXpDGraj5YhRrq9ESo206S+BY=",
|
|
||||||
};
|
|
||||||
|
|
||||||
var user = new User
|
|
||||||
{
|
|
||||||
Id = Guid.NewGuid(),
|
|
||||||
Email = $"admin@{domain}",
|
|
||||||
MasterPassword = "AQAAAAIAAYagAAAAEBATmF66OHMpHuHKc1CsGZQ1ltHUHyhYK+7e4re3bVFi16SOpLpDfzdFswnvFQs2Rg==",
|
|
||||||
SecurityStamp = "4830e359-e150-4eae-be2a-996c81c5e609",
|
|
||||||
Key = "2.z/eLKFhd62qy9RzXu3UHgA==|fF6yNupiCIguFKSDTB3DoqcGR0Xu4j+9VlnMyT5F3PaWIcGhzQKIzxdB95nhslaCQv3c63M7LBnvzVo1J9SUN85RMbP/57bP1HvhhU1nvL8=|IQPtf8v7k83MFZEhazSYXSdu98BBU5rqtvC4keVWyHM=",
|
|
||||||
PublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0Ww2chogqCpaAR7Uw448am4b7vDFXiM5kXjFlGfXBlrAdAqTTggEvTDlMNYqPlCo+mBM6iFmTTUY9rpZBvFskMnKvsvpJ47/fehAH2o2e3Ulv/5NFevaVCMCmpkBDtbMbO1A4a3btdRtCP8DsKWMefHauEpaoLxNTLWnOIZVfCMjsSgx2EvULHAZPTtbFwm4+UVKniM4ds4jvOsD85h4jn2aLs/jWJXFfxN8iVSqEqpC2TBvsPdyHb49xQoWWfF0Z6BiNqeNGKEU9Uos1pjL+kzhEzzSpH31PZT/ufJ/oo4+93wrUt57hb6f0jxiXhwd5yQ+9F6wVwpbfkq0IwhjOwIDAQAB",
|
|
||||||
PrivateKey = "2.yN7l00BOlUE0Sb0M//Q53w==|EwKG/BduQRQ33Izqc/ogoBROIoI5dmgrxSo82sgzgAMIBt3A2FZ9vPRMY+GWT85JiqytDitGR3TqwnFUBhKUpRRAq4x7rA6A1arHrFp5Tp1p21O3SfjtvB3quiOKbqWk6ZaU1Np9HwqwAecddFcB0YyBEiRX3VwF2pgpAdiPbSMuvo2qIgyob0CUoC/h4Bz1be7Qa7B0Xw9/fMKkB1LpOm925lzqosyMQM62YpMGkjMsbZz0uPopu32fxzDWSPr+kekNNyLt9InGhTpxLmq1go/pXR2uw5dfpXc5yuta7DB0EGBwnQ8Vl5HPdDooqOTD9I1jE0mRyuBpWTTI3FRnu3JUh3rIyGBJhUmHqGZvw2CKdqHCIrQeQkkEYqOeJRJVdBjhv5KGJifqT3BFRwX/YFJIChAQpebNQKXe/0kPivWokHWwXlDB7S7mBZzhaAPidZvnuIhalE2qmTypDwHy22FyqV58T8MGGMchcASDi/QXI6kcdpJzPXSeU9o+NC68QDlOIrMVxKFeE7w7PvVmAaxEo0YwmuAzzKy9QpdlK0aab/xEi8V4iXj4hGepqAvHkXIQd+r3FNeiLfllkb61p6WTjr5urcmDQMR94/wYoilpG5OlybHdbhsYHvIzYoLrC7fzl630gcO6t4nM24vdB6Ymg9BVpEgKRAxSbE62Tqacxqnz9AcmgItb48NiR/He3n3ydGjPYuKk/ihZMgEwAEZvSlNxYONSbYrIGDtOY+8Nbt6KiH3l06wjZW8tcmFeVlWv+tWotnTY9IqlAfvNVTjtsobqtQnvsiDjdEVtNy/s2ci5TH+NdZluca2OVEr91Wayxh70kpM6ib4UGbfdmGgCo74gtKvKSJU0rTHakQ5L9JlaSDD5FamBRyI0qfL43Ad9qOUZ8DaffDCyuaVyuqk7cz9HwmEmvWU3VQ+5t06n/5kRDXttcw8w+3qClEEdGo1KeENcnXCB32dQe3tDTFpuAIMLqwXs6FhpawfZ5kPYvLPczGWaqftIs/RXJ/EltGc0ugw2dmTLpoQhCqrcKEBDoYVk0LDZKsnzitOGdi9mOWse7Se8798ib1UsHFUjGzISEt6upestxOeupSTOh0v4+AjXbDzRUyogHww3V+Bqg71bkcMxtB+WM+pn1XNbVTyl9NR040nhP7KEf6e9ruXAtmrBC2ah5cFEpLIot77VFZ9ilLuitSz+7T8n1yAh1IEG6xxXxninAZIzi2qGbH69O5RSpOJuJTv17zTLJQIIc781JwQ2TTwTGnx5wZLbffhCasowJKd2EVcyMJyhz6ru0PvXWJ4hUdkARJs3Xu8dus9a86N8Xk6aAPzBDqzYb1vyFIfBxP0oO8xFHgd30Cgmz8UrSE3qeWRrF8ftrI6xQnFjHBGWD/JWSvd6YMcQED0aVuQkuNW9ST/DzQThPzRfPUoiL10yAmV7Ytu4fR3x2sF0Yfi87YhHFuCMpV/DsqxmUizyiJuD938eRcH8hzR/VO53Qo3UIsqOLcyXtTv6THjSlTopQ+JOLOnHm1w8dzYbLN44OG44rRsbihMUQp+wUZ6bsI8rrOnm9WErzkbQFbrfAINdoCiNa6cimYIjvvnMTaFWNymqY1vZxGztQiMiHiHYwTfwHTXrb9j0uPM=|09J28iXv9oWzYtzK2LBT6Yht4IT4MijEkk0fwFdrVQ4=",
|
|
||||||
ApiKey = "7gp59kKHt9kMlks0BuNC4IjNXYkljR",
|
|
||||||
|
|
||||||
Kdf = KdfType.PBKDF2_SHA256,
|
|
||||||
KdfIterations = 600_000,
|
|
||||||
};
|
|
||||||
|
|
||||||
var orgUser = new OrganizationUser
|
|
||||||
{
|
|
||||||
Id = Guid.NewGuid(),
|
|
||||||
OrganizationId = organization.Id,
|
|
||||||
UserId = user.Id,
|
|
||||||
Key = "4.rY01mZFXHOsBAg5Fq4gyXuklWfm6mQASm42DJpx05a+e2mmp+P5W6r54WU2hlREX0uoTxyP91bKKwickSPdCQQ58J45LXHdr9t2uzOYyjVzpzebFcdMw1eElR9W2DW8wEk9+mvtWvKwu7yTebzND+46y1nRMoFydi5zPVLSlJEf81qZZ4Uh1UUMLwXz+NRWfixnGXgq2wRq1bH0n3mqDhayiG4LJKgGdDjWXC8W8MMXDYx24SIJrJu9KiNEMprJE+XVF9nQVNijNAjlWBqkDpsfaWTUfeVLRLctfAqW1blsmIv4RQ91PupYJZDNc8nO9ZTF3TEVM+2KHoxzDJrLs2Q==",
|
|
||||||
Type = OrganizationUserType.Admin,
|
|
||||||
Status = OrganizationUserStatusType.Confirmed
|
|
||||||
};
|
|
||||||
|
|
||||||
using (var scope = serviceProvider.CreateScope())
|
using (var scope = serviceProvider.CreateScope())
|
||||||
{
|
{
|
||||||
@ -92,11 +52,6 @@ public class GenerateCommand
|
|||||||
services.AddDataProtection()
|
services.AddDataProtection()
|
||||||
.SetApplicationName("Bitwarden");
|
.SetApplicationName("Bitwarden");
|
||||||
|
|
||||||
// Register DatabaseContext and services
|
|
||||||
services.AddTransient<ISeederService, SeederService>();
|
|
||||||
services.AddTransient<IDatabaseService, DatabaseService>();
|
|
||||||
services.AddTransient<IEncryptionService, EncryptionService>();
|
|
||||||
|
|
||||||
services.AddDatabaseRepositories(globalSettings);
|
services.AddDatabaseRepositories(globalSettings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
44
util/Seeder/Factories/OrganizationSeeder.cs
Normal file
44
util/Seeder/Factories/OrganizationSeeder.cs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
using Bit.Core.Billing.Enums;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Infrastructure.EntityFramework.AdminConsole.Models;
|
||||||
|
using Bit.Infrastructure.EntityFramework.Models;
|
||||||
|
|
||||||
|
namespace Bit.Seeder.Factories;
|
||||||
|
|
||||||
|
public class OrganizationSeeder
|
||||||
|
{
|
||||||
|
public static Organization CreateEnterprise(string name, string domain, int seats)
|
||||||
|
{
|
||||||
|
return new Organization
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
Name = name,
|
||||||
|
BillingEmail = $"billing@{domain}",
|
||||||
|
Plan = "Enterprise (Annually)",
|
||||||
|
PlanType = PlanType.EnterpriseAnnually,
|
||||||
|
Seats = seats,
|
||||||
|
|
||||||
|
// Currently hardcoded to the values from https://github.com/bitwarden/sdk-internal/blob/main/crates/bitwarden-core/src/client/test_accounts.rs.
|
||||||
|
// TODO: These should be dynamically generated by the SDK.
|
||||||
|
PublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmIJbGMk6eZqVE7UxhZ46Weu2jKciqOiOkSVYtGvs61rfe9AXxtLaaZEKN4d4DmkZcF6dna2eXNxZmb7U4pwlttye8ksqISe6IUAZQox7auBpjopdCEPhKRg3BD/u8ks9UxSxgWe+fpebjt6gd5hsl1/5HOObn7SeU6EEU04cp3/eH7a4OTdXxB8oN62HGV9kM/ubM1goILgjoSJDbihMK0eb7b8hPHwcA/YOgKKiu/N3FighccdSMD5Pk+HfjacsFNZQa2EsqW09IvvSZ+iL6HQeZ1vwc/6TO1J7EOfJZFQcjoEL9LVI693efYoMZSmrPEWziZ4PvwpOOGo6OObyMQIDAQAB",
|
||||||
|
PrivateKey = "2.6FggyKVyaKQsfohi5yqgbg==|UU2JeafOB41L5UscGmf4kq15JGDf3Bkf67KECiehTODzbWctVLTgyDk0Qco8/6CMN6nZGXjxR2A4r5ExhmwRNsNxd77G+MprkmiJz+7w33ROZ1ouQO5XjD3wbQ3ssqNiTKId6yAUPBvuAZRixVApauTuADc8QWGixqCQcqZzmU7YSBBIPf652/AEYr4Tk64YihoE39pHiK8MRbTLdRt3EF4LSMugPAPM24vCgUv3w1TD3Fj6sDg/6oi3flOV9SJZX4vCiUXbDNEuD/p2aQrEXVbaxweFOHjTe7F4iawjXw3nG3SO8rUBHcxbhDDVx5rjYactbW5QvHWiyla6uLb6o8WHBneg2EjTEwAHOZE/rBjcqmAJb2sVp1E0Kwq8ycGmL69vmqJPC1GqVTohAQvmEkaxIPpfq24Yb9ZPrADA7iEXBKuAQ1FphFUVgJBJGJbd60sOV1Rz1T+gUwS4wCNQ4l3LG1S22+wzUVlEku5DXFnT932tatqTyWEthqPqLCt6dL1+qa94XLpeHagXAx2VGe8n8IlcADtxqS+l8xQ4heT12WO9kC316vqvg1mnsI56faup9hb3eT9ZpKyxSBGYOphlTWfV1Y/v64f5PYvTo4aL0IYHyLY/9Qi72vFmOpPeHBYgD5t3j+H2CsiU1PkYsBggOmD7xW8FDuT6HWVvwhEJqeibVPK0Lhyj6tgvlSIAvFUaSMFPlmwFNmwfj/AHUhr9KuTfsBFTZ10yy9TZVgf+EofwnrxHBaWUgdD40aHoY1VjfG33iEuajb6buxG3pYFyPNhJNzeLZisUKIDRMQpUHrsE22EyrFFran3tZGdtcyIEK4Q1F0ULYzJ6T9iY25/ZgPy3pEAAMZCtqo3s+GjX295fWIHfMcnjMgNUHPjExjWBHa+ggK9iQXkFpBVyYB1ga/+0eiIhiek3PlgtvpDrqF7TsLK+ROiBw2GJ7uaO3EEXOj2GpNBuEJ5CdodhZkwzhwMcSatgDHkUuNVu0iVbF6/MxVdOxWXKO+jCYM6PZk/vAhLYqpPzu2T2Uyz4nkDs2Tiq61ez6FoCrzdHIiyIxVTzUQH8G9FgSmtaZ7GCbqlhnurYgcMciwPzxg0hpAQT+NZw1tVEii9vFSpJJbGJqNhORKfKh/Mu1P/9LOQq7Y0P2FIR3x/eUVEQ7CGv2jVtO5ryGSmKeq/P9Fr54wTPaNiqN2K+leACUznCdUWw8kZo/AsBcrOe4OkRX6k8LC3oeJXy06DEToatxEvPYemUauhxiXRw8nfNMqc4LyJq2bbT0zCgJHoqpozPdNg6AYWcoIobgAGu7ZQGq+oE1MT3GZxotMPe/NUJiAc5YE9Thb5Yf3gyno71pyqPTVl/6IQuh4SUz7rkgwF/aVHEnr4aUYNoc0PEzd2Me0jElsA3GAneq1I/wngutOWgTViTK4Nptr5uIzMVQs9H1rOMJNorP8b02t1NDu010rSsib9GaaJJq4r4iy46laQOxWoU0ex26arYnk+jw4833WSCTVBIprTgizZ+fKjoY0xwXvI2oOvGNEUCtGFvKFORTaQrlaXZIg1toa2BBVNicyONbwnI3KIu3MgGJ2SlCVXJn8oHFppVHFCdwgN1uDzGiKAhjvr0sZTUtXin2f2CszPTbbo=|fUhbVKrr8CSKE7TZJneXpDGraj5YhRrq9ESo206S+BY=",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class OrgnaizationExtensions
|
||||||
|
{
|
||||||
|
public static OrganizationUser CreateOrganizationUser(this Organization organization, User user)
|
||||||
|
{
|
||||||
|
return new OrganizationUser
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
OrganizationId = organization.Id,
|
||||||
|
UserId = user.Id,
|
||||||
|
|
||||||
|
Key = "4.rY01mZFXHOsBAg5Fq4gyXuklWfm6mQASm42DJpx05a+e2mmp+P5W6r54WU2hlREX0uoTxyP91bKKwickSPdCQQ58J45LXHdr9t2uzOYyjVzpzebFcdMw1eElR9W2DW8wEk9+mvtWvKwu7yTebzND+46y1nRMoFydi5zPVLSlJEf81qZZ4Uh1UUMLwXz+NRWfixnGXgq2wRq1bH0n3mqDhayiG4LJKgGdDjWXC8W8MMXDYx24SIJrJu9KiNEMprJE+XVF9nQVNijNAjlWBqkDpsfaWTUfeVLRLctfAqW1blsmIv4RQ91PupYJZDNc8nO9ZTF3TEVM+2KHoxzDJrLs2Q==",
|
||||||
|
Type = OrganizationUserType.Admin,
|
||||||
|
Status = OrganizationUserStatusType.Confirmed
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
25
util/Seeder/Factories/UserSeeder.cs
Normal file
25
util/Seeder/Factories/UserSeeder.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Infrastructure.EntityFramework.Models;
|
||||||
|
|
||||||
|
namespace Bit.Seeder.Factories;
|
||||||
|
|
||||||
|
public class UserSeeder
|
||||||
|
{
|
||||||
|
public static User CreateUser(string email)
|
||||||
|
{
|
||||||
|
return new User
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
Email = email,
|
||||||
|
MasterPassword = "AQAAAAIAAYagAAAAEBATmF66OHMpHuHKc1CsGZQ1ltHUHyhYK+7e4re3bVFi16SOpLpDfzdFswnvFQs2Rg==",
|
||||||
|
SecurityStamp = "4830e359-e150-4eae-be2a-996c81c5e609",
|
||||||
|
Key = "2.z/eLKFhd62qy9RzXu3UHgA==|fF6yNupiCIguFKSDTB3DoqcGR0Xu4j+9VlnMyT5F3PaWIcGhzQKIzxdB95nhslaCQv3c63M7LBnvzVo1J9SUN85RMbP/57bP1HvhhU1nvL8=|IQPtf8v7k83MFZEhazSYXSdu98BBU5rqtvC4keVWyHM=",
|
||||||
|
PublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0Ww2chogqCpaAR7Uw448am4b7vDFXiM5kXjFlGfXBlrAdAqTTggEvTDlMNYqPlCo+mBM6iFmTTUY9rpZBvFskMnKvsvpJ47/fehAH2o2e3Ulv/5NFevaVCMCmpkBDtbMbO1A4a3btdRtCP8DsKWMefHauEpaoLxNTLWnOIZVfCMjsSgx2EvULHAZPTtbFwm4+UVKniM4ds4jvOsD85h4jn2aLs/jWJXFfxN8iVSqEqpC2TBvsPdyHb49xQoWWfF0Z6BiNqeNGKEU9Uos1pjL+kzhEzzSpH31PZT/ufJ/oo4+93wrUt57hb6f0jxiXhwd5yQ+9F6wVwpbfkq0IwhjOwIDAQAB",
|
||||||
|
PrivateKey = "2.yN7l00BOlUE0Sb0M//Q53w==|EwKG/BduQRQ33Izqc/ogoBROIoI5dmgrxSo82sgzgAMIBt3A2FZ9vPRMY+GWT85JiqytDitGR3TqwnFUBhKUpRRAq4x7rA6A1arHrFp5Tp1p21O3SfjtvB3quiOKbqWk6ZaU1Np9HwqwAecddFcB0YyBEiRX3VwF2pgpAdiPbSMuvo2qIgyob0CUoC/h4Bz1be7Qa7B0Xw9/fMKkB1LpOm925lzqosyMQM62YpMGkjMsbZz0uPopu32fxzDWSPr+kekNNyLt9InGhTpxLmq1go/pXR2uw5dfpXc5yuta7DB0EGBwnQ8Vl5HPdDooqOTD9I1jE0mRyuBpWTTI3FRnu3JUh3rIyGBJhUmHqGZvw2CKdqHCIrQeQkkEYqOeJRJVdBjhv5KGJifqT3BFRwX/YFJIChAQpebNQKXe/0kPivWokHWwXlDB7S7mBZzhaAPidZvnuIhalE2qmTypDwHy22FyqV58T8MGGMchcASDi/QXI6kcdpJzPXSeU9o+NC68QDlOIrMVxKFeE7w7PvVmAaxEo0YwmuAzzKy9QpdlK0aab/xEi8V4iXj4hGepqAvHkXIQd+r3FNeiLfllkb61p6WTjr5urcmDQMR94/wYoilpG5OlybHdbhsYHvIzYoLrC7fzl630gcO6t4nM24vdB6Ymg9BVpEgKRAxSbE62Tqacxqnz9AcmgItb48NiR/He3n3ydGjPYuKk/ihZMgEwAEZvSlNxYONSbYrIGDtOY+8Nbt6KiH3l06wjZW8tcmFeVlWv+tWotnTY9IqlAfvNVTjtsobqtQnvsiDjdEVtNy/s2ci5TH+NdZluca2OVEr91Wayxh70kpM6ib4UGbfdmGgCo74gtKvKSJU0rTHakQ5L9JlaSDD5FamBRyI0qfL43Ad9qOUZ8DaffDCyuaVyuqk7cz9HwmEmvWU3VQ+5t06n/5kRDXttcw8w+3qClEEdGo1KeENcnXCB32dQe3tDTFpuAIMLqwXs6FhpawfZ5kPYvLPczGWaqftIs/RXJ/EltGc0ugw2dmTLpoQhCqrcKEBDoYVk0LDZKsnzitOGdi9mOWse7Se8798ib1UsHFUjGzISEt6upestxOeupSTOh0v4+AjXbDzRUyogHww3V+Bqg71bkcMxtB+WM+pn1XNbVTyl9NR040nhP7KEf6e9ruXAtmrBC2ah5cFEpLIot77VFZ9ilLuitSz+7T8n1yAh1IEG6xxXxninAZIzi2qGbH69O5RSpOJuJTv17zTLJQIIc781JwQ2TTwTGnx5wZLbffhCasowJKd2EVcyMJyhz6ru0PvXWJ4hUdkARJs3Xu8dus9a86N8Xk6aAPzBDqzYb1vyFIfBxP0oO8xFHgd30Cgmz8UrSE3qeWRrF8ftrI6xQnFjHBGWD/JWSvd6YMcQED0aVuQkuNW9ST/DzQThPzRfPUoiL10yAmV7Ytu4fR3x2sF0Yfi87YhHFuCMpV/DsqxmUizyiJuD938eRcH8hzR/VO53Qo3UIsqOLcyXtTv6THjSlTopQ+JOLOnHm1w8dzYbLN44OG44rRsbihMUQp+wUZ6bsI8rrOnm9WErzkbQFbrfAINdoCiNa6cimYIjvvnMTaFWNymqY1vZxGztQiMiHiHYwTfwHTXrb9j0uPM=|09J28iXv9oWzYtzK2LBT6Yht4IT4MijEkk0fwFdrVQ4=",
|
||||||
|
ApiKey = "7gp59kKHt9kMlks0BuNC4IjNXYkljR",
|
||||||
|
|
||||||
|
Kdf = KdfType.PBKDF2_SHA256,
|
||||||
|
KdfIterations = 600_000,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -1,85 +0,0 @@
|
|||||||
using Bit.Core.Entities;
|
|
||||||
using Bit.Core.Vault.Entities;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace Bit.Seeder.Services;
|
|
||||||
|
|
||||||
public class DatabaseService : IDatabaseService
|
|
||||||
{
|
|
||||||
private readonly DatabaseContext _dbContext;
|
|
||||||
private readonly ILogger<DatabaseService> _logger;
|
|
||||||
|
|
||||||
public DatabaseService(DatabaseContext dbContext, ILogger<DatabaseService> logger)
|
|
||||||
{
|
|
||||||
_dbContext = dbContext;
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task ClearDatabaseAsync()
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Clearing database...");
|
|
||||||
|
|
||||||
var ciphers = await _dbContext.Ciphers.ToListAsync();
|
|
||||||
_dbContext.Ciphers.RemoveRange(ciphers);
|
|
||||||
|
|
||||||
var users = await _dbContext.Users.ToListAsync();
|
|
||||||
_dbContext.Users.RemoveRange(users);
|
|
||||||
|
|
||||||
await _dbContext.SaveChangesAsync();
|
|
||||||
|
|
||||||
_logger.LogInformation("Database cleared successfully.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task SaveUsersAsync(IEnumerable<User> users)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Saving users to database...");
|
|
||||||
|
|
||||||
foreach (var user in users)
|
|
||||||
{
|
|
||||||
await _dbContext.Users.AddAsync(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
await _dbContext.SaveChangesAsync();
|
|
||||||
|
|
||||||
_logger.LogInformation($"Successfully saved {users.Count()} users to database.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task SaveCiphersAsync(IEnumerable<Cipher> ciphers)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Saving ciphers to database...");
|
|
||||||
|
|
||||||
foreach (var cipher in ciphers)
|
|
||||||
{
|
|
||||||
await _dbContext.Ciphers.AddAsync(cipher);
|
|
||||||
}
|
|
||||||
|
|
||||||
await _dbContext.SaveChangesAsync();
|
|
||||||
|
|
||||||
_logger.LogInformation($"Successfully saved {ciphers.Count()} ciphers to database.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<List<User>> GetUsersAsync()
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Retrieving all users from database...");
|
|
||||||
var users = await _dbContext.Users.ToListAsync();
|
|
||||||
_logger.LogInformation($"Successfully retrieved {users.Count} users from database.");
|
|
||||||
return users;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<List<Cipher>> GetCiphersAsync()
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Retrieving all ciphers from database...");
|
|
||||||
var ciphers = await _dbContext.Ciphers.ToListAsync();
|
|
||||||
_logger.LogInformation($"Successfully retrieved {ciphers.Count} ciphers from database.");
|
|
||||||
return ciphers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<List<Cipher>> GetCiphersByUserIdAsync(Guid userId)
|
|
||||||
{
|
|
||||||
_logger.LogInformation($"Retrieving ciphers for user {userId} from database...");
|
|
||||||
var ciphers = await _dbContext.Ciphers.Where(c => c.UserId == userId).ToListAsync();
|
|
||||||
_logger.LogInformation($"Successfully retrieved {ciphers.Count} ciphers for user {userId}.");
|
|
||||||
return ciphers;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,66 +0,0 @@
|
|||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text;
|
|
||||||
using Bit.Core;
|
|
||||||
using Microsoft.AspNetCore.DataProtection;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace Bit.Seeder.Services;
|
|
||||||
|
|
||||||
public class EncryptionService : IEncryptionService
|
|
||||||
{
|
|
||||||
private readonly ILogger<EncryptionService> _logger;
|
|
||||||
private readonly IDataProtector _dataProtector;
|
|
||||||
|
|
||||||
public EncryptionService(
|
|
||||||
ILogger<EncryptionService> logger,
|
|
||||||
IDataProtectionProvider dataProtectionProvider)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
_dataProtector = dataProtectionProvider.CreateProtector(Constants.DatabaseFieldProtectorPurpose);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string HashPassword(string password)
|
|
||||||
{
|
|
||||||
_logger.LogDebug("Hashing password using Data Protection");
|
|
||||||
|
|
||||||
// The real Bitwarden implementation uses BCrypt first and then protects that value
|
|
||||||
// For simplicity we're just protecting the raw password since this is only for seeding test data
|
|
||||||
var protectedPassword = _dataProtector.Protect(password);
|
|
||||||
|
|
||||||
// Prefix with "P|" to match Bitwarden's password format
|
|
||||||
return string.Concat(Constants.DatabaseFieldProtectedPrefix, protectedPassword);
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] DeriveKey(string password, string salt)
|
|
||||||
{
|
|
||||||
_logger.LogDebug("Deriving key");
|
|
||||||
|
|
||||||
using var pbkdf2 = new Rfc2898DeriveBytes(
|
|
||||||
Encoding.UTF8.GetBytes(password),
|
|
||||||
Encoding.UTF8.GetBytes(salt),
|
|
||||||
100000,
|
|
||||||
HashAlgorithmName.SHA256);
|
|
||||||
|
|
||||||
return pbkdf2.GetBytes(32);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string EncryptString(string plaintext, byte[] key)
|
|
||||||
{
|
|
||||||
_logger.LogDebug("Encrypting string");
|
|
||||||
|
|
||||||
using var aes = Aes.Create();
|
|
||||||
aes.Key = key;
|
|
||||||
aes.GenerateIV();
|
|
||||||
|
|
||||||
using var encryptor = aes.CreateEncryptor();
|
|
||||||
var plaintextBytes = Encoding.UTF8.GetBytes(plaintext);
|
|
||||||
|
|
||||||
var cipherBytes = encryptor.TransformFinalBlock(plaintextBytes, 0, plaintextBytes.Length);
|
|
||||||
|
|
||||||
var result = new byte[aes.IV.Length + cipherBytes.Length];
|
|
||||||
Buffer.BlockCopy(aes.IV, 0, result, 0, aes.IV.Length);
|
|
||||||
Buffer.BlockCopy(cipherBytes, 0, result, aes.IV.Length, cipherBytes.Length);
|
|
||||||
|
|
||||||
return Convert.ToBase64String(result);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
using Bit.Core.Entities;
|
|
||||||
using Bit.Core.Vault.Entities;
|
|
||||||
|
|
||||||
namespace Bit.Seeder.Services;
|
|
||||||
|
|
||||||
public interface IDatabaseService
|
|
||||||
{
|
|
||||||
Task ClearDatabaseAsync();
|
|
||||||
Task SaveUsersAsync(IEnumerable<User> users);
|
|
||||||
Task SaveCiphersAsync(IEnumerable<Cipher> ciphers);
|
|
||||||
Task<List<User>> GetUsersAsync();
|
|
||||||
Task<List<Cipher>> GetCiphersAsync();
|
|
||||||
Task<List<Cipher>> GetCiphersByUserIdAsync(Guid userId);
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
namespace Bit.Seeder.Services;
|
|
||||||
|
|
||||||
public interface IEncryptionService
|
|
||||||
{
|
|
||||||
string HashPassword(string password);
|
|
||||||
byte[] DeriveKey(string password, string salt);
|
|
||||||
string EncryptString(string plaintext, byte[] key);
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
namespace Bit.Seeder.Services;
|
|
||||||
|
|
||||||
public interface ISeederService
|
|
||||||
{
|
|
||||||
Task GenerateSeedsAsync(int userCount, int ciphersPerUser, string seedName);
|
|
||||||
Task LoadSeedsAsync(string seedName, string? timestamp = null);
|
|
||||||
Task GenerateAndLoadSeedsAsync(int userCount, int ciphersPerUser, string seedName);
|
|
||||||
Task ExtractSeedsAsync(string seedName);
|
|
||||||
}
|
|
@ -1,450 +0,0 @@
|
|||||||
using System.Text.Json;
|
|
||||||
using Bit.Core.Entities;
|
|
||||||
using Bit.Core.Enums;
|
|
||||||
using Bit.Core.Vault.Entities;
|
|
||||||
using Bit.Core.Vault.Enums;
|
|
||||||
using Bogus;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace Bit.Seeder.Services;
|
|
||||||
|
|
||||||
public class SeederService : ISeederService
|
|
||||||
{
|
|
||||||
private readonly IEncryptionService _encryptionService;
|
|
||||||
private readonly IDatabaseService _databaseService;
|
|
||||||
private readonly ILogger<SeederService> _logger;
|
|
||||||
private readonly Faker _faker;
|
|
||||||
private readonly string _defaultPassword = "password";
|
|
||||||
|
|
||||||
public SeederService(
|
|
||||||
IEncryptionService encryptionService,
|
|
||||||
IDatabaseService databaseService,
|
|
||||||
ILogger<SeederService> logger)
|
|
||||||
{
|
|
||||||
_encryptionService = encryptionService;
|
|
||||||
_databaseService = databaseService;
|
|
||||||
_logger = logger;
|
|
||||||
_faker = new Faker();
|
|
||||||
|
|
||||||
// Set the random seed to ensure reproducible data
|
|
||||||
Randomizer.Seed = new Random(42);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task GenerateSeedsAsync(int userCount, int ciphersPerUser, string outputName)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Generating seeds: {UserCount} users with {CiphersPerUser} ciphers each", userCount, ciphersPerUser);
|
|
||||||
|
|
||||||
// Create timestamped folder under a named folder in seeds directory
|
|
||||||
var seedsBaseDir = Path.Combine(Directory.GetCurrentDirectory(), "seeds");
|
|
||||||
Directory.CreateDirectory(seedsBaseDir);
|
|
||||||
|
|
||||||
var namedDir = Path.Combine(seedsBaseDir, outputName);
|
|
||||||
Directory.CreateDirectory(namedDir);
|
|
||||||
|
|
||||||
var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
|
|
||||||
var outputDir = Path.Combine(namedDir, timestamp);
|
|
||||||
Directory.CreateDirectory(outputDir);
|
|
||||||
|
|
||||||
// Create users and ciphers subdirectories
|
|
||||||
Directory.CreateDirectory(Path.Combine(outputDir, "users"));
|
|
||||||
Directory.CreateDirectory(Path.Combine(outputDir, "ciphers"));
|
|
||||||
|
|
||||||
_logger.LogInformation("Seed output directory: {OutputDir}", outputDir);
|
|
||||||
|
|
||||||
// Generate users
|
|
||||||
var users = GenerateUsers(userCount);
|
|
||||||
|
|
||||||
// Generate ciphers for each user
|
|
||||||
var allCiphers = new List<Cipher>();
|
|
||||||
foreach (var user in users)
|
|
||||||
{
|
|
||||||
var ciphers = GenerateCiphers(user, ciphersPerUser);
|
|
||||||
allCiphers.AddRange(ciphers);
|
|
||||||
|
|
||||||
// Save each user's ciphers to a file
|
|
||||||
var cipherFilePath = Path.Combine(outputDir, "ciphers", $"{user.Id}.json");
|
|
||||||
await File.WriteAllTextAsync(cipherFilePath, JsonSerializer.Serialize(ciphers, new JsonSerializerOptions
|
|
||||||
{
|
|
||||||
WriteIndented = true
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save users to a file
|
|
||||||
var userFilePath = Path.Combine(outputDir, "users", "users.json");
|
|
||||||
await File.WriteAllTextAsync(userFilePath, JsonSerializer.Serialize(users, new JsonSerializerOptions
|
|
||||||
{
|
|
||||||
WriteIndented = true
|
|
||||||
}));
|
|
||||||
|
|
||||||
_logger.LogInformation("Successfully generated {UserCount} users and {CipherCount} ciphers", users.Count, allCiphers.Count);
|
|
||||||
_logger.LogInformation("Seed data saved to directory: {OutputDir}", outputDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task GenerateAndLoadSeedsAsync(int userCount, int ciphersPerUser, string seedName)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Generating and loading seeds directly: {UserCount} users with {CiphersPerUser} ciphers each",
|
|
||||||
userCount, ciphersPerUser);
|
|
||||||
|
|
||||||
// Generate users directly without saving to files
|
|
||||||
var users = GenerateUsers(userCount);
|
|
||||||
|
|
||||||
// Clear the database first
|
|
||||||
await _databaseService.ClearDatabaseAsync();
|
|
||||||
|
|
||||||
// Save users to database
|
|
||||||
await _databaseService.SaveUsersAsync(users);
|
|
||||||
_logger.LogInformation("Saved {UserCount} users directly to database", users.Count);
|
|
||||||
|
|
||||||
// Generate and save ciphers for each user
|
|
||||||
int totalCiphers = 0;
|
|
||||||
foreach (var user in users)
|
|
||||||
{
|
|
||||||
var ciphers = GenerateCiphers(user, ciphersPerUser);
|
|
||||||
await _databaseService.SaveCiphersAsync(ciphers);
|
|
||||||
totalCiphers += ciphers.Count;
|
|
||||||
_logger.LogInformation("Saved {CipherCount} ciphers for user {UserId} directly to database",
|
|
||||||
ciphers.Count, user.Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogInformation("Successfully generated and loaded {UserCount} users and {CipherCount} ciphers directly to database",
|
|
||||||
users.Count, totalCiphers);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task LoadSeedsAsync(string seedName, string? timestamp = null)
|
|
||||||
{
|
|
||||||
// Construct path to seeds directory
|
|
||||||
var seedsBaseDir = Path.Combine(Directory.GetCurrentDirectory(), "seeds");
|
|
||||||
var namedDir = Path.Combine(seedsBaseDir, seedName);
|
|
||||||
|
|
||||||
if (!Directory.Exists(namedDir))
|
|
||||||
{
|
|
||||||
_logger.LogError("Seed directory not found: {SeedDir}", namedDir);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string seedDir;
|
|
||||||
|
|
||||||
// If timestamp is specified, use that exact directory
|
|
||||||
if (!string.IsNullOrEmpty(timestamp))
|
|
||||||
{
|
|
||||||
seedDir = Path.Combine(namedDir, timestamp);
|
|
||||||
if (!Directory.Exists(seedDir))
|
|
||||||
{
|
|
||||||
_logger.LogError("Timestamp directory not found: {TimestampDir}", seedDir);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Otherwise, find the most recent timestamped directory
|
|
||||||
var timestampDirs = Directory.GetDirectories(namedDir);
|
|
||||||
if (timestampDirs.Length == 0)
|
|
||||||
{
|
|
||||||
_logger.LogError("No seed data found in directory: {SeedDir}", namedDir);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort by directory name (which is a timestamp) in descending order
|
|
||||||
Array.Sort(timestampDirs);
|
|
||||||
Array.Reverse(timestampDirs);
|
|
||||||
|
|
||||||
// Use the most recent one
|
|
||||||
seedDir = timestampDirs[0];
|
|
||||||
_logger.LogInformation("Using most recent seed data from: {SeedDir}", seedDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogInformation("Loading seeds from directory: {SeedDir}", seedDir);
|
|
||||||
|
|
||||||
// Clear database first
|
|
||||||
await _databaseService.ClearDatabaseAsync();
|
|
||||||
|
|
||||||
// Load users
|
|
||||||
var userFilePath = Path.Combine(seedDir, "users", "users.json");
|
|
||||||
if (!File.Exists(userFilePath))
|
|
||||||
{
|
|
||||||
_logger.LogError("User file not found: {UserFilePath}", userFilePath);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var userJson = await File.ReadAllTextAsync(userFilePath);
|
|
||||||
var users = JsonSerializer.Deserialize<List<User>>(userJson, new JsonSerializerOptions
|
|
||||||
{
|
|
||||||
PropertyNameCaseInsensitive = true
|
|
||||||
}) ?? new List<User>();
|
|
||||||
|
|
||||||
if (users.Count == 0)
|
|
||||||
{
|
|
||||||
_logger.LogError("No users found in user file");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save users to database
|
|
||||||
await _databaseService.SaveUsersAsync(users);
|
|
||||||
|
|
||||||
// Load and save ciphers for each user
|
|
||||||
var cipherDir = Path.Combine(seedDir, "ciphers");
|
|
||||||
if (!Directory.Exists(cipherDir))
|
|
||||||
{
|
|
||||||
_logger.LogError("Cipher directory not found: {CipherDir}", cipherDir);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var cipherFiles = Directory.GetFiles(cipherDir, "*.json");
|
|
||||||
foreach (var cipherFile in cipherFiles)
|
|
||||||
{
|
|
||||||
var cipherJson = await File.ReadAllTextAsync(cipherFile);
|
|
||||||
var ciphers = JsonSerializer.Deserialize<List<Cipher>>(cipherJson, new JsonSerializerOptions
|
|
||||||
{
|
|
||||||
PropertyNameCaseInsensitive = true
|
|
||||||
}) ?? new List<Cipher>();
|
|
||||||
|
|
||||||
if (ciphers.Count > 0)
|
|
||||||
{
|
|
||||||
await _databaseService.SaveCiphersAsync(ciphers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogInformation("Successfully loaded seed data into database");
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task ExtractSeedsAsync(string seedName)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Extracting seed data from database for seed name: {SeedName}", seedName);
|
|
||||||
|
|
||||||
// Create timestamped folder under a named folder in seeds directory
|
|
||||||
var seedsBaseDir = Path.Combine(Directory.GetCurrentDirectory(), "seeds");
|
|
||||||
Directory.CreateDirectory(seedsBaseDir);
|
|
||||||
|
|
||||||
var namedDir = Path.Combine(seedsBaseDir, seedName);
|
|
||||||
Directory.CreateDirectory(namedDir);
|
|
||||||
|
|
||||||
var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
|
|
||||||
var outputDir = Path.Combine(namedDir, timestamp);
|
|
||||||
Directory.CreateDirectory(outputDir);
|
|
||||||
|
|
||||||
// Create users and ciphers subdirectories
|
|
||||||
Directory.CreateDirectory(Path.Combine(outputDir, "users"));
|
|
||||||
Directory.CreateDirectory(Path.Combine(outputDir, "ciphers"));
|
|
||||||
|
|
||||||
_logger.LogInformation("Seed output directory: {OutputDir}", outputDir);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Get all users from the database
|
|
||||||
var users = await _databaseService.GetUsersAsync();
|
|
||||||
if (users == null || users.Count == 0)
|
|
||||||
{
|
|
||||||
_logger.LogWarning("No users found in the database");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogInformation("Extracted {Count} users from database", users.Count);
|
|
||||||
|
|
||||||
// Save users to a file
|
|
||||||
var userFilePath = Path.Combine(outputDir, "users", "users.json");
|
|
||||||
await File.WriteAllTextAsync(userFilePath, JsonSerializer.Serialize(users, new JsonSerializerOptions
|
|
||||||
{
|
|
||||||
WriteIndented = true
|
|
||||||
}));
|
|
||||||
|
|
||||||
int totalCiphers = 0;
|
|
||||||
// Get ciphers for each user
|
|
||||||
foreach (var user in users)
|
|
||||||
{
|
|
||||||
var ciphers = await _databaseService.GetCiphersByUserIdAsync(user.Id);
|
|
||||||
if (ciphers != null && ciphers.Count > 0)
|
|
||||||
{
|
|
||||||
// Save ciphers to a file
|
|
||||||
var cipherFilePath = Path.Combine(outputDir, "ciphers", $"{user.Id}.json");
|
|
||||||
await File.WriteAllTextAsync(cipherFilePath, JsonSerializer.Serialize(ciphers, new JsonSerializerOptions
|
|
||||||
{
|
|
||||||
WriteIndented = true
|
|
||||||
}));
|
|
||||||
totalCiphers += ciphers.Count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogInformation("Successfully extracted {UserCount} users and {CipherCount} ciphers", users.Count, totalCiphers);
|
|
||||||
_logger.LogInformation("Seed data saved to directory: {OutputDir}", outputDir);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error extracting seed data: {Message}", ex.Message);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<User> GenerateUsers(int count)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Generating {Count} users", count);
|
|
||||||
|
|
||||||
var users = new List<User>();
|
|
||||||
|
|
||||||
for (int i = 0; i < count; i++)
|
|
||||||
{
|
|
||||||
var userId = Guid.NewGuid();
|
|
||||||
var email = _faker.Internet.Email(provider: "example.com");
|
|
||||||
var name = _faker.Name.FullName();
|
|
||||||
var masterPassword = _encryptionService.HashPassword(_defaultPassword);
|
|
||||||
var masterPasswordHint = "It's the word 'password'";
|
|
||||||
var key = _encryptionService.DeriveKey(_defaultPassword, email);
|
|
||||||
|
|
||||||
var user = new User
|
|
||||||
{
|
|
||||||
Id = userId,
|
|
||||||
Email = email,
|
|
||||||
Name = name,
|
|
||||||
MasterPassword = masterPassword,
|
|
||||||
MasterPasswordHint = masterPasswordHint,
|
|
||||||
SecurityStamp = Guid.NewGuid().ToString(),
|
|
||||||
EmailVerified = true,
|
|
||||||
ApiKey = Guid.NewGuid().ToString("N").Substring(0, 30),
|
|
||||||
Kdf = KdfType.PBKDF2_SHA256,
|
|
||||||
KdfIterations = 100000,
|
|
||||||
CreationDate = DateTime.UtcNow,
|
|
||||||
RevisionDate = DateTime.UtcNow,
|
|
||||||
Key = _encryptionService.EncryptString(Convert.ToBase64String(key), key)
|
|
||||||
};
|
|
||||||
|
|
||||||
users.Add(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
return users;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Cipher> GenerateCiphers(User user, int count)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Generating {Count} ciphers for user {UserId}", count, user.Id);
|
|
||||||
|
|
||||||
var ciphers = new List<Cipher>();
|
|
||||||
var key = _encryptionService.DeriveKey(_defaultPassword, user.Email);
|
|
||||||
|
|
||||||
for (int i = 0; i < count; i++)
|
|
||||||
{
|
|
||||||
var cipherId = Guid.NewGuid();
|
|
||||||
CipherType type;
|
|
||||||
string name;
|
|
||||||
string? notes = null;
|
|
||||||
|
|
||||||
var typeRandom = _faker.Random.Int(1, 4);
|
|
||||||
type = (CipherType)typeRandom;
|
|
||||||
|
|
||||||
switch (type)
|
|
||||||
{
|
|
||||||
case CipherType.Login:
|
|
||||||
name = $"Login - {_faker.Internet.DomainName()}";
|
|
||||||
var loginData = new
|
|
||||||
{
|
|
||||||
Name = name,
|
|
||||||
Notes = notes,
|
|
||||||
Username = _faker.Internet.UserName(),
|
|
||||||
Password = _faker.Internet.Password(),
|
|
||||||
Uris = new[]
|
|
||||||
{
|
|
||||||
new { Uri = $"https://{_faker.Internet.DomainName()}" }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var loginDataJson = JsonSerializer.Serialize(loginData);
|
|
||||||
|
|
||||||
ciphers.Add(new Cipher
|
|
||||||
{
|
|
||||||
Id = cipherId,
|
|
||||||
UserId = user.Id,
|
|
||||||
Type = type,
|
|
||||||
Data = _encryptionService.EncryptString(loginDataJson, key),
|
|
||||||
CreationDate = DateTime.UtcNow,
|
|
||||||
RevisionDate = DateTime.UtcNow,
|
|
||||||
Reprompt = CipherRepromptType.None
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CipherType.SecureNote:
|
|
||||||
name = $"Note - {_faker.Lorem.Word()}";
|
|
||||||
notes = _faker.Lorem.Paragraph();
|
|
||||||
var secureNoteData = new
|
|
||||||
{
|
|
||||||
Name = name,
|
|
||||||
Notes = notes,
|
|
||||||
Type = 0 // Text
|
|
||||||
};
|
|
||||||
|
|
||||||
var secureNoteDataJson = JsonSerializer.Serialize(secureNoteData);
|
|
||||||
|
|
||||||
ciphers.Add(new Cipher
|
|
||||||
{
|
|
||||||
Id = cipherId,
|
|
||||||
UserId = user.Id,
|
|
||||||
Type = type,
|
|
||||||
Data = _encryptionService.EncryptString(secureNoteDataJson, key),
|
|
||||||
CreationDate = DateTime.UtcNow,
|
|
||||||
RevisionDate = DateTime.UtcNow,
|
|
||||||
Reprompt = CipherRepromptType.None
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CipherType.Card:
|
|
||||||
name = $"Card - {_faker.Finance.CreditCardNumber().Substring(0, 4)}";
|
|
||||||
var cardData = new
|
|
||||||
{
|
|
||||||
Name = name,
|
|
||||||
Notes = notes,
|
|
||||||
CardholderName = _faker.Name.FullName(),
|
|
||||||
Number = _faker.Finance.CreditCardNumber(),
|
|
||||||
ExpMonth = _faker.Random.Int(1, 12).ToString(),
|
|
||||||
ExpYear = _faker.Random.Int(DateTime.UtcNow.Year, DateTime.UtcNow.Year + 10).ToString(),
|
|
||||||
Code = _faker.Random.Int(100, 999).ToString()
|
|
||||||
};
|
|
||||||
|
|
||||||
var cardDataJson = JsonSerializer.Serialize(cardData);
|
|
||||||
|
|
||||||
ciphers.Add(new Cipher
|
|
||||||
{
|
|
||||||
Id = cipherId,
|
|
||||||
UserId = user.Id,
|
|
||||||
Type = type,
|
|
||||||
Data = _encryptionService.EncryptString(cardDataJson, key),
|
|
||||||
CreationDate = DateTime.UtcNow,
|
|
||||||
RevisionDate = DateTime.UtcNow,
|
|
||||||
Reprompt = CipherRepromptType.None
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CipherType.Identity:
|
|
||||||
name = $"Identity - {_faker.Name.FullName()}";
|
|
||||||
var identityData = new
|
|
||||||
{
|
|
||||||
Name = name,
|
|
||||||
Notes = notes,
|
|
||||||
Title = _faker.Name.Prefix(),
|
|
||||||
FirstName = _faker.Name.FirstName(),
|
|
||||||
MiddleName = _faker.Name.FirstName(),
|
|
||||||
LastName = _faker.Name.LastName(),
|
|
||||||
Email = _faker.Internet.Email(),
|
|
||||||
Phone = _faker.Phone.PhoneNumber(),
|
|
||||||
Address1 = _faker.Address.StreetAddress(),
|
|
||||||
City = _faker.Address.City(),
|
|
||||||
State = _faker.Address.State(),
|
|
||||||
PostalCode = _faker.Address.ZipCode(),
|
|
||||||
Country = _faker.Address.CountryCode()
|
|
||||||
};
|
|
||||||
|
|
||||||
var identityDataJson = JsonSerializer.Serialize(identityData);
|
|
||||||
|
|
||||||
ciphers.Add(new Cipher
|
|
||||||
{
|
|
||||||
Id = cipherId,
|
|
||||||
UserId = user.Id,
|
|
||||||
Type = type,
|
|
||||||
Data = _encryptionService.EncryptString(identityDataJson, key),
|
|
||||||
CreationDate = DateTime.UtcNow,
|
|
||||||
RevisionDate = DateTime.UtcNow,
|
|
||||||
Reprompt = CipherRepromptType.None
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ciphers;
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user