diff --git a/util/Seeder/Commands/GenerateCommand.cs b/util/Seeder/Commands/GenerateCommand.cs index 7d07dc405b..9b2649ecd1 100644 --- a/util/Seeder/Commands/GenerateCommand.cs +++ b/util/Seeder/Commands/GenerateCommand.cs @@ -1,8 +1,4 @@ -using Bit.Core.Billing.Enums; -using Bit.Core.Enums; -using Bit.Infrastructure.EntityFramework.AdminConsole.Models; -using Bit.Infrastructure.EntityFramework.Models; -using Bit.Seeder.Services; +using Bit.Seeder.Factories; using Bit.Seeder.Settings; using Bit.SharedWeb.Utilities; using Microsoft.AspNetCore.DataProtection; @@ -23,45 +19,9 @@ public class GenerateCommand var logger = serviceProvider.GetRequiredService>(); - var organization = new Organization - { - Id = Guid.NewGuid(), - 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 - }; + var organization = OrganizationSeeder.CreateEnterprise(name, domain, users); + var user = UserSeeder.CreateUser($"admin@{domain}"); + var orgUser = organization.CreateOrganizationUser(user); using (var scope = serviceProvider.CreateScope()) { @@ -92,11 +52,6 @@ public class GenerateCommand services.AddDataProtection() .SetApplicationName("Bitwarden"); - // Register DatabaseContext and services - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddDatabaseRepositories(globalSettings); } } diff --git a/util/Seeder/Factories/OrganizationSeeder.cs b/util/Seeder/Factories/OrganizationSeeder.cs new file mode 100644 index 0000000000..5e5cb17419 --- /dev/null +++ b/util/Seeder/Factories/OrganizationSeeder.cs @@ -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 + }; + } +} diff --git a/util/Seeder/Factories/UserSeeder.cs b/util/Seeder/Factories/UserSeeder.cs new file mode 100644 index 0000000000..90cadf0b78 --- /dev/null +++ b/util/Seeder/Factories/UserSeeder.cs @@ -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, + }; + } +} diff --git a/util/Seeder/Services/DatabaseService.cs b/util/Seeder/Services/DatabaseService.cs deleted file mode 100644 index ff98835255..0000000000 --- a/util/Seeder/Services/DatabaseService.cs +++ /dev/null @@ -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 _logger; - - public DatabaseService(DatabaseContext dbContext, ILogger 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 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 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> 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> 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> 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; - } -} diff --git a/util/Seeder/Services/EncryptionService.cs b/util/Seeder/Services/EncryptionService.cs deleted file mode 100644 index c08c30110e..0000000000 --- a/util/Seeder/Services/EncryptionService.cs +++ /dev/null @@ -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 _logger; - private readonly IDataProtector _dataProtector; - - public EncryptionService( - ILogger 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); - } -} diff --git a/util/Seeder/Services/IDatabaseService.cs b/util/Seeder/Services/IDatabaseService.cs deleted file mode 100644 index 5684bf22ce..0000000000 --- a/util/Seeder/Services/IDatabaseService.cs +++ /dev/null @@ -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 users); - Task SaveCiphersAsync(IEnumerable ciphers); - Task> GetUsersAsync(); - Task> GetCiphersAsync(); - Task> GetCiphersByUserIdAsync(Guid userId); -} diff --git a/util/Seeder/Services/IEncryptionService.cs b/util/Seeder/Services/IEncryptionService.cs deleted file mode 100644 index 17de2a3a41..0000000000 --- a/util/Seeder/Services/IEncryptionService.cs +++ /dev/null @@ -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); -} diff --git a/util/Seeder/Services/ISeederService.cs b/util/Seeder/Services/ISeederService.cs deleted file mode 100644 index 5df1348cea..0000000000 --- a/util/Seeder/Services/ISeederService.cs +++ /dev/null @@ -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); -} diff --git a/util/Seeder/Services/SeederService.cs b/util/Seeder/Services/SeederService.cs deleted file mode 100644 index 6ac3b0b85d..0000000000 --- a/util/Seeder/Services/SeederService.cs +++ /dev/null @@ -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 _logger; - private readonly Faker _faker; - private readonly string _defaultPassword = "password"; - - public SeederService( - IEncryptionService encryptionService, - IDatabaseService databaseService, - ILogger 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(); - 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>(userJson, new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true - }) ?? new List(); - - 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>(cipherJson, new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true - }) ?? new List(); - - 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 GenerateUsers(int count) - { - _logger.LogInformation("Generating {Count} users", count); - - var users = new List(); - - 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 GenerateCiphers(User user, int count) - { - _logger.LogInformation("Generating {Count} ciphers for user {UserId}", count, user.Id); - - var ciphers = new List(); - 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; - } -}