Repository: bitwarden/server
Pull request #5703: [PM-21075] Initial database seeder
Author: login: Hinton
Created: 2025-04-24T15:59:51Z | Merged: 2025-05-09T13:00:27Z

## 🎟️ Tracking

<!-- Paste the link to the Jira or GitHub issue or otherwise describe / point to where this change is coming from. -->

https://bitwarden.atlassian.net/browse/PM-21075

## 📔 Objective

<!-- Describe what the purpose of this PR is, for example what bug you're fixing or new feature you're adding. -->

This PR adds a database seeder which can be used standalone using a CLI for seeding your local development environment, or used in unit tests to seed complex scenarios.

## ⏰ Reminders before review

- Contributor guidelines followed
- All formatters and local linters executed and passed
- Written new unit and / or integration tests where applicable
- Protected functional changes with optionality (feature flags)
- Used internationalization (i18n) for all UI strings
- CI builds passed
- Communicated to DevOps any deployment requirements
- Updated any necessary documentation (Confluence, contributing docs) or informed the documentation team

## 🦮 Reviewer guidelines

<!-- Suggested interactions but feel free to use (or not) as you desire! -->

- 👍 (`:+1:`) or similar for great changes
- 📝 (`:memo:`) or ℹ️ (`:information_source:`) for notes or general info
- ❓ (`:question:`) for questions
- 🤔 (`:thinking:`) or 💭 (`:thought_balloon:`) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion
- 🎨 (`:art:`) for suggestions / improvements
- ❌ (`:x:`) or ⚠️ (`:warning:`) for more significant problems or concerns needing attention
- 🌱 (`:seedling:`) or ♻️ (`:recycle:`) for future improvements or indications of technical debt
- ⛏ (`:pick:`) for minor or nitpick changes
… that can load from json or directly from seed generation - cannot be used to login as encryption methods are non functional… that can load from json or directly from seed generation - cannot be used to login as encryption methods are non functional


Diff:
diff --git a/bitwarden-server.sln b/bitwarden-server.sln
index 892d2f425571..2ec8d86e0ebc 100644
--- a/bitwarden-server.sln
+++ b/bitwarden-server.sln
@@ -129,6 +129,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Events.IntegrationTest", "t
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core.IntegrationTest", "test\Core.IntegrationTest\Core.IntegrationTest.csproj", "{3631BA42-6731-4118-A917-DAA43C5032B9}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Seeder", "util\Seeder\Seeder.csproj", "{9A612EBA-1C0E-42B8-982B-62F0EE81000A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DbSeederUtility", "util\DbSeederUtility\DbSeederUtility.csproj", "{17A89266-260A-4A03-81AE-C0468C6EE06E}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -325,6 +329,14 @@ Global
 		{3631BA42-6731-4118-A917-DAA43C5032B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{3631BA42-6731-4118-A917-DAA43C5032B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{3631BA42-6731-4118-A917-DAA43C5032B9}.Release|Any CPU.Build.0 = Release|Any CPU
+		{9A612EBA-1C0E-42B8-982B-62F0EE81000A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{9A612EBA-1C0E-42B8-982B-62F0EE81000A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{9A612EBA-1C0E-42B8-982B-62F0EE81000A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{9A612EBA-1C0E-42B8-982B-62F0EE81000A}.Release|Any CPU.Build.0 = Release|Any CPU
+		{17A89266-260A-4A03-81AE-C0468C6EE06E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{17A89266-260A-4A03-81AE-C0468C6EE06E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{17A89266-260A-4A03-81AE-C0468C6EE06E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{17A89266-260A-4A03-81AE-C0468C6EE06E}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -377,6 +389,8 @@ Global
 		{4A725DB3-BE4F-4C23-9087-82D0610D67AF} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F}
 		{4F4C63A9-AEE2-48C4-AB86-A5BCD665E401} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F}
 		{3631BA42-6731-4118-A917-DAA43C5032B9} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F}
+		{9A612EBA-1C0E-42B8-982B-62F0EE81000A} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84E}
+		{17A89266-260A-4A03-81AE-C0468C6EE06E} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84E}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {E01CBF68-2E20-425F-9EDB-E0A6510CA92F}
diff --git a/test/Api.IntegrationTest/AdminConsole/Controllers/OrganizationUsersControllerPerformanceTests.cs b/test/Api.IntegrationTest/AdminConsole/Controllers/OrganizationUsersControllerPerformanceTests.cs
new file mode 100644
index 000000000000..94432b05a002
--- /dev/null
+++ b/test/Api.IntegrationTest/AdminConsole/Controllers/OrganizationUsersControllerPerformanceTests.cs
@@ -0,0 +1,39 @@
+﻿using System.Net;
+using System.Net.Http.Headers;
+using Bit.Api.IntegrationTest.Factories;
+using Bit.Seeder.Recipes;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Bit.Api.IntegrationTest.AdminConsole.Controllers;
+
+public class OrganizationUsersControllerPerformanceTest(ITestOutputHelper testOutputHelper)
+{
+    [Theory(Skip = "Performance test")]
+    [InlineData(100)]
+    [InlineData(60000)]
+    public async Task GetAsync(int seats)
+    {
+        await using var factory = new ApiApplicationFactory();
+        var client = factory.CreateClient();
+
+        var db = factory.GetDatabaseContext();
+        var seeder = new OrganizationWithUsersRecipe(db);
+
+        var orgId = seeder.Seed("Org", seats, "large.test");
+
+        var tokens = await factory.LoginAsync("admin@large.test", "c55hlJ/cfdvTd4awTXUqow6X3cOQCfGwn11o3HblnPs=");
+        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokens.Token);
+
+        var stopwatch = System.Diagnostics.Stopwatch.StartNew();
+
+        var response = await client.GetAsync($"/organizations/{orgId}/users?includeCollections=true");
+        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+
+        var result = await response.Content.ReadAsStringAsync();
+        Assert.NotEmpty(result);
+
+        stopwatch.Stop();
+        testOutputHelper.WriteLine($"Seed: {seats}; Request duration: {stopwatch.ElapsedMilliseconds} ms");
+    }
+}
diff --git a/test/Api.IntegrationTest/Api.IntegrationTest.csproj b/test/Api.IntegrationTest/Api.IntegrationTest.csproj
index 8fa74f98d46e..a9d7fd502e78 100644
--- a/test/Api.IntegrationTest/Api.IntegrationTest.csproj
+++ b/test/Api.IntegrationTest/Api.IntegrationTest.csproj
@@ -18,6 +18,7 @@
 
   <ItemGroup>
     <ProjectReference Include="..\..\src\Api\Api.csproj" />
+    <ProjectReference Include="..\..\util\Seeder\Seeder.csproj" />
     <ProjectReference Include="..\IntegrationTestCommon\IntegrationTestCommon.csproj" />
 
     <Content Include="..\..\src\Api\appsettings.*.json">
diff --git a/util/DbSeederUtility/DbSeederUtility.csproj b/util/DbSeederUtility/DbSeederUtility.csproj
new file mode 100644
index 000000000000..90ac7f22b443
--- /dev/null
+++ b/util/DbSeederUtility/DbSeederUtility.csproj
@@ -0,0 +1,22 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net8.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+    <RootNamespace>Bit.DbSeederUtility</RootNamespace>
+    <AssemblyName>DbSeeder</AssemblyName>
+    <IncludeAllContentForSelfExtract>true</IncludeAllContentForSelfExtract>
+    <UserSecretsId>2294c6ba-7cd0-4293-a797-3882e41c61cb</UserSecretsId>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\Seeder\Seeder.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="CommandDotNet" Version="7.0.4" />
+  </ItemGroup>
+
+</Project>
diff --git a/util/DbSeederUtility/GlobalSettingsFactory.cs b/util/DbSeederUtility/GlobalSettingsFactory.cs
new file mode 100644
index 000000000000..e4ad275a0e0e
--- /dev/null
+++ b/util/DbSeederUtility/GlobalSettingsFactory.cs
@@ -0,0 +1,34 @@
+﻿using Bit.Core.Settings;
+using Microsoft.Extensions.Configuration;
+
+namespace Bit.DbSeederUtility;
+
+public static class GlobalSettingsFactory
+{
+    private static GlobalSettings? _globalSettings;
+
+    public static GlobalSettings GlobalSettings
+    {
+        get { return _globalSettings ??= LoadGlobalSettings(); }
+    }
+
+    private static GlobalSettings LoadGlobalSettings()
+    {
+        Console.WriteLine("Loading global settings...");
+
+        var configBuilder = new ConfigurationBuilder()
+            .SetBasePath(Directory.GetCurrentDirectory())
+            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
+            .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true, reloadOnChange: true)
+            .AddUserSecrets("bitwarden-Api") // Load user secrets from the API project
+            .AddEnvironmentVariables();
+
+        var configuration = configBuilder.Build();
+        var globalSettingsSection = configuration.GetSection("globalSettings");
+
+        var settings = new GlobalSettings();
+        globalSettingsSection.Bind(settings);
+
+        return settings;
+    }
+}
diff --git a/util/DbSeederUtility/Program.cs b/util/DbSeederUtility/Program.cs
new file mode 100644
index 000000000000..2d75b31934c3
--- /dev/null
+++ b/util/DbSeederUtility/Program.cs
@@ -0,0 +1,39 @@
+﻿using Bit.Infrastructure.EntityFramework.Repositories;
+using Bit.Seeder.Recipes;
+using CommandDotNet;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Bit.DbSeederUtility;
+
+public class Program
+{
+    private static int Main(string[] args)
+    {
+        return new AppRunner<Program>()
+            .Run(args);
+    }
+
+    [Command("organization", Description = "Seed an organization and organization users")]
+    public void Organization(
+        [Option('n', "Name", Description = "Name of organization")]
+        string name,
+        [Option('u', "users", Description = "Number of users to generate")]
+        int users,
+        [Option('d', "domain", Description = "Email domain for users")]
+        string domain
+    )
+    {
+        // Create service provider with necessary services
+        var services = new ServiceCollection();
+        ServiceCollectionExtension.ConfigureServices(services);
+        var serviceProvider = services.BuildServiceProvider();
+
+        // Get a scoped DB context
+        using var scope = serviceProvider.CreateScope();
+        var scopedServices = scope.ServiceProvider;
+        var db = scopedServices.GetRequiredService<DatabaseContext>();
+
+        var recipe = new OrganizationWithUsersRecipe(db);
+        recipe.Seed(name, users, domain);
+    }
+}
diff --git a/util/DbSeederUtility/README.md b/util/DbSeederUtility/README.md
new file mode 100644
index 000000000000..0eb21ae6c5b2
--- /dev/null
+++ b/util/DbSeederUtility/README.md
@@ -0,0 +1,40 @@
+# Bitwarden Database Seeder Utility
+
+A command-line utility for generating and managing test data for Bitwarden databases.
+
+## Overview
+
+DbSeederUtility is an executable wrapper around the Seeder class library that provides a convenient command-line
+interface for executing seed-recipes in your local environment.
+
+## Installation
+
+The utility can be built and run as a .NET 8 application:
+
+```
+dotnet build
+dotnet run -- <command> [options]
+```
+
+Or directly using the compiled executable:
+
+```
+DbSeeder.exe <command> [options]
+```
+
+## Examples
+
+### Generate and load test organization
+
+```bash
+# Generate an organization called "seeded" with 10000 users using the @large.test email domain.
+# Login using "admin@large.test" with password "asdfasdfasdf"
+DbSeeder.exe organization -n seeded -u 10000 -d large.test
+```
+
+## Dependencies
+
+This utility depends on:
+- The Seeder class library
+- CommandDotNet for command-line parsing
+- .NET 8.0 runtime
diff --git a/util/DbSeederUtility/ServiceCollectionExtension.cs b/util/DbSeederUtility/ServiceCollectionExtension.cs
new file mode 100644
index 000000000000..0653bb180168
--- /dev/null
+++ b/util/DbSeederUtility/ServiceCollectionExtension.cs
@@ -0,0 +1,25 @@
+﻿using Bit.SharedWeb.Utilities;
+using Microsoft.AspNetCore.DataProtection;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Bit.DbSeederUtility;
+
+public static class ServiceCollectionExtension
+{
+    public static void ConfigureServices(ServiceCollection services)
+    {
+        // Load configuration using the GlobalSettingsFactory
+        var globalSettings = GlobalSettingsFactory.GlobalSettings;
+
+        // Register services
+        services.AddLogging(builder => builder.AddConsole());
+        services.AddSingleton(globalSettings);
+
+        // Add Data Protection services
+        services.AddDataProtection()
+            .SetApplicationName("Bitwarden");
+
+        services.AddDatabaseRepositories(globalSettings);
+    }
+}
diff --git a/util/Seeder/Factories/OrganizationSeeder.cs b/util/Seeder/Factories/OrganizationSeeder.cs
new file mode 100644
index 000000000000..5e5cb174194a
--- /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 000000000000..90cadf0b78e4
--- /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/README.md b/util/Seeder/README.md
new file mode 100644
index 000000000000..8597ad6e393a
--- /dev/null
+++ b/util/Seeder/README.md
@@ -0,0 +1,18 @@
+# Bitwarden Database Seeder
+
+A class library for generating and inserting test data.
+
+## Project Structure
+
+The project is organized into these main components:
+
+### Factories
+
+Factories are helper classes for creating domain entities and populating them with realistic data. This assist in
+decreasing the amount of boilerplate code needed to create test data in recipes.
+
+### Recipes
+
+Recipes are pre-defined data sets which can be run to generate and load data into the database. They often allow a allow
+for a few arguments to customize the data slightly. Recipes should be kept simple and focused on a single task. Default
+to creating more recipes rather than adding complexity to existing ones.
diff --git a/util/Seeder/Recipes/OrganizationWithUsersRecipe.cs b/util/Seeder/Recipes/OrganizationWithUsersRecipe.cs
new file mode 100644
index 000000000000..fb06c091ae86
--- /dev/null
+++ b/util/Seeder/Recipes/OrganizationWithUsersRecipe.cs
@@ -0,0 +1,37 @@
+﻿using Bit.Infrastructure.EntityFramework.Models;
+using Bit.Infrastructure.EntityFramework.Repositories;
+using Bit.Seeder.Factories;
+using LinqToDB.EntityFrameworkCore;
+
+namespace Bit.Seeder.Recipes;
+
+public class OrganizationWithUsersRecipe(DatabaseContext db)
+{
+    public Guid Seed(string name, int users, string domain)
+    {
+        var organization = OrganizationSeeder.CreateEnterprise(name, domain, users);
+        var user = UserSeeder.CreateUser($"admin@{domain}");
+        var orgUser = organization.CreateOrganizationUser(user);
+
+        var additionalUsers = new List<User>();
+        var additionalOrgUsers = new List<OrganizationUser>();
+        for (var i = 0; i < users; i++)
+        {
+            var additionalUser = UserSeeder.CreateUser($"user{i}@{domain}");
+            additionalUsers.Add(additionalUser);
+            additionalOrgUsers.Add(organization.CreateOrganizationUser(additionalUser));
+        }
+
+        db.Add(organization);
+        db.Add(user);
+        db.Add(orgUser);
+
+        db.SaveChanges();
+
+        // Use LinqToDB's BulkCopy for significant better performance
+        db.BulkCopy(additionalUsers);
+        db.BulkCopy(additionalOrgUsers);
+
+        return organization.Id;
+    }
+}
diff --git a/util/Seeder/Seeder.csproj b/util/Seeder/Seeder.csproj
new file mode 100644
index 000000000000..392f6434cc38
--- /dev/null
+++ b/util/Seeder/Seeder.csproj
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net8.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+    <RootNamespace>Bit.Seeder</RootNamespace>
+    <UserSecretsId>Bit.Seeder</UserSecretsId>
+    <Description>Core library for generating and managing test data for Bitwarden</Description>
+    <OutputType>library</OutputType>
+    <IsPackable>false</IsPackable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Folder Include="Settings\" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\src\Core\Core.csproj" />
+    <ProjectReference Include="..\..\src\Infrastructure.EntityFramework\Infrastructure.EntityFramework.csproj" />
+    <ProjectReference Include="..\..\src\SharedWeb\SharedWeb.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Compile Remove="..\..\Program.cs" />
+  </ItemGroup>
+
+</Project>


Reviews:
user: login: github-advanced-security[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-24T16:10:09Z
user: login: eliykat
body: Nice one, some minor feedback only but looks like a solid MVP.
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-05T03:40:41Z
user: login: Hinton
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-05T13:13:11Z
user: login: Hinton
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-05T13:20:28Z
user: login: eliykat
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-06T05:40:43Z
user: login: eliykat
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-06T05:41:34Z
user: login: eliykat
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-06T05:41:58Z
user: login: eliykat
body: LGTM except for unresolved feedback [here](https://github.com/bitwarden/server/pull/5703#discussion_r2074762226).
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-05-06T05:42:27Z
user: login: rkac-bw
body: lgtm
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T02:50:17Z
user: login: eliykat
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-09T00:35:41Z
user: login: Hinton
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-09T11:16:43Z
user: login: withinfocus
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T20:05:52Z

Review comments:
diff_hunk: @@ -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==",
path: util/Seeder/Factories/UserSeeder.cs
user: login: github-advanced-security[bot]
body: Use Of Hardcoded Password

[Show more details](https://github.com/bitwarden/server/security/code-scanning/22424)
created_at: 2025-04-24T16:10:09Z
author_association: NONE
path: test/Api.IntegrationTest/AdminConsole/Controllers/OrganizationUsersControllerTests.cs
user: login: eliykat
body: I suggest we keep these separate from normal integration tests which focus on correctness. e.g. `OrganizationUsersControllerPerformanceTests`.
created_at: 2025-05-05T03:10:37Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,80 @@
+﻿using System.Net;
+using System.Net.Http.Headers;
+using Bit.Api.IntegrationTest.Factories;
+using Bit.Seeder.Recipes;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Bit.Api.IntegrationTest.AdminConsole.Controllers;
+
+public class OrganizationUsersControllerTest : IClassFixture<ApiApplicationFactory>, IAsyncLifetime
+{
+    private readonly HttpClient _client;
+    private readonly ApiApplicationFactory _factory;
+    private readonly ITestOutputHelper _testOutputHelper;
+
+    public OrganizationUsersControllerTest(ApiApplicationFactory factory, ITestOutputHelper testOutputHelper)
+    {
+        _factory = factory;
+        _testOutputHelper = testOutputHelper;
+        _client = _factory.CreateClient();
+    }
+
+    public Task InitializeAsync()
+    {
+        return Task.CompletedTask;
+    }
+
+    public Task DisposeAsync()
+    {
+        _client.Dispose();
+        return Task.CompletedTask;
+    }
+
+    [Fact(Skip = "Performance testing")]
+    public async Task Get_SmallOrg()
+    {
+        var db = _factory.GetDatabaseContext();
+        var seeder = new OrganizationWithUsersRecipe(db);
+
+        var orgId = seeder.Seed("Org", 100, "large.test");
path: test/Api.IntegrationTest/AdminConsole/Controllers/OrganizationUsersControllerTests.cs
user: login: eliykat
body: Should this be `small.test`?

On that note - can we combine these 2 tests and just use inline theory data to seed different numbers and different domains?
created_at: 2025-05-05T03:23:59Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,80 @@
+﻿using System.Net;
+using System.Net.Http.Headers;
+using Bit.Api.IntegrationTest.Factories;
+using Bit.Seeder.Recipes;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Bit.Api.IntegrationTest.AdminConsole.Controllers;
+
+public class OrganizationUsersControllerTest : IClassFixture<ApiApplicationFactory>, IAsyncLifetime
+{
+    private readonly HttpClient _client;
+    private readonly ApiApplicationFactory _factory;
+    private readonly ITestOutputHelper _testOutputHelper;
+
+    public OrganizationUsersControllerTest(ApiApplicationFactory factory, ITestOutputHelper testOutputHelper)
+    {
+        _factory = factory;
+        _testOutputHelper = testOutputHelper;
+        _client = _factory.CreateClient();
+    }
+
+    public Task InitializeAsync()
+    {
+        return Task.CompletedTask;
+    }
+
+    public Task DisposeAsync()
+    {
+        _client.Dispose();
+        return Task.CompletedTask;
+    }
+
+    [Fact(Skip = "Performance testing")]
+    public async Task Get_SmallOrg()
+    {
+        var db = _factory.GetDatabaseContext();
+        var seeder = new OrganizationWithUsersRecipe(db);
+
+        var orgId = seeder.Seed("Org", 100, "large.test");
+
+        var tokens = await _factory.LoginAsync("admin@large.test", "c55hlJ/cfdvTd4awTXUqow6X3cOQCfGwn11o3HblnPs=");
+        _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokens.Token);
+
+        var stopwatch = System.Diagnostics.Stopwatch.StartNew();
+
+        var response = await _client.GetAsync($"/organizations/{orgId}/users?includeCollections=true");
+        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+
+        var result = await response.Content.ReadAsStringAsync();
+        Assert.NotEmpty(result);
+
+        stopwatch.Stop();
+        _testOutputHelper.WriteLine($"Request duration: {stopwatch.ElapsedMilliseconds} ms");
path: test/Api.IntegrationTest/AdminConsole/Controllers/OrganizationUsersControllerTests.cs
user: login: eliykat
body: Suggestion: include the test size in the output. e.g. `Seed: {seedSize}; request duration: {time} ms`.
created_at: 2025-05-05T03:26:14Z
author_association: MEMBER
path: util/Seeder/Recipes/OrganizationWithUsersRecipe.cs
user: login: eliykat
body: Should we have team directories under here so that each team can own their seeder recipes?
created_at: 2025-05-05T03:40:08Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,80 @@
+﻿using System.Net;
+using System.Net.Http.Headers;
+using Bit.Api.IntegrationTest.Factories;
+using Bit.Seeder.Recipes;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Bit.Api.IntegrationTest.AdminConsole.Controllers;
+
+public class OrganizationUsersControllerTest : IClassFixture<ApiApplicationFactory>, IAsyncLifetime
+{
+    private readonly HttpClient _client;
+    private readonly ApiApplicationFactory _factory;
+    private readonly ITestOutputHelper _testOutputHelper;
+
+    public OrganizationUsersControllerTest(ApiApplicationFactory factory, ITestOutputHelper testOutputHelper)
+    {
+        _factory = factory;
+        _testOutputHelper = testOutputHelper;
+        _client = _factory.CreateClient();
+    }
+
+    public Task InitializeAsync()
+    {
+        return Task.CompletedTask;
+    }
+
+    public Task DisposeAsync()
+    {
+        _client.Dispose();
+        return Task.CompletedTask;
+    }
+
+    [Fact(Skip = "Performance testing")]
+    public async Task Get_SmallOrg()
+    {
+        var db = _factory.GetDatabaseContext();
+        var seeder = new OrganizationWithUsersRecipe(db);
+
+        var orgId = seeder.Seed("Org", 100, "large.test");
path: test/Api.IntegrationTest/AdminConsole/Controllers/OrganizationUsersControllerTests.cs
user: login: Hinton
body: It should, but the password only work on `large.test` right now 😓 
created_at: 2025-05-05T13:13:11Z
author_association: MEMBER
path: util/Seeder/Recipes/OrganizationWithUsersRecipe.cs
user: login: Hinton
body: Maybe. But I also wonder if we should encourage more generic seeds which are used by various teams.
created_at: 2025-05-05T13:20:27Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,80 @@
+﻿using System.Net;
+using System.Net.Http.Headers;
+using Bit.Api.IntegrationTest.Factories;
+using Bit.Seeder.Recipes;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Bit.Api.IntegrationTest.AdminConsole.Controllers;
+
+public class OrganizationUsersControllerTest : IClassFixture<ApiApplicationFactory>, IAsyncLifetime
+{
+    private readonly HttpClient _client;
+    private readonly ApiApplicationFactory _factory;
+    private readonly ITestOutputHelper _testOutputHelper;
+
+    public OrganizationUsersControllerTest(ApiApplicationFactory factory, ITestOutputHelper testOutputHelper)
+    {
+        _factory = factory;
+        _testOutputHelper = testOutputHelper;
+        _client = _factory.CreateClient();
+    }
+
+    public Task InitializeAsync()
+    {
+        return Task.CompletedTask;
+    }
+
+    public Task DisposeAsync()
+    {
+        _client.Dispose();
+        return Task.CompletedTask;
+    }
+
+    [Fact(Skip = "Performance testing")]
+    public async Task Get_SmallOrg()
+    {
+        var db = _factory.GetDatabaseContext();
+        var seeder = new OrganizationWithUsersRecipe(db);
+
+        var orgId = seeder.Seed("Org", 100, "large.test");
path: test/Api.IntegrationTest/AdminConsole/Controllers/OrganizationUsersControllerTests.cs
user: login: eliykat
body: 😞 
created_at: 2025-05-06T05:40:43Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,80 @@
+﻿using System.Net;
+using System.Net.Http.Headers;
+using Bit.Api.IntegrationTest.Factories;
+using Bit.Seeder.Recipes;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Bit.Api.IntegrationTest.AdminConsole.Controllers;
+
+public class OrganizationUsersControllerTest : IClassFixture<ApiApplicationFactory>, IAsyncLifetime
+{
+    private readonly HttpClient _client;
+    private readonly ApiApplicationFactory _factory;
+    private readonly ITestOutputHelper _testOutputHelper;
+
+    public OrganizationUsersControllerTest(ApiApplicationFactory factory, ITestOutputHelper testOutputHelper)
+    {
+        _factory = factory;
+        _testOutputHelper = testOutputHelper;
+        _client = _factory.CreateClient();
+    }
+
+    public Task InitializeAsync()
+    {
+        return Task.CompletedTask;
+    }
+
+    public Task DisposeAsync()
+    {
+        _client.Dispose();
+        return Task.CompletedTask;
+    }
+
+    [Fact(Skip = "Performance testing")]
+    public async Task Get_SmallOrg()
+    {
+        var db = _factory.GetDatabaseContext();
+        var seeder = new OrganizationWithUsersRecipe(db);
+
+        var orgId = seeder.Seed("Org", 100, "large.test");
path: test/Api.IntegrationTest/AdminConsole/Controllers/OrganizationUsersControllerTests.cs
user: login: eliykat
body: Keeping this unresolved as I hid other feedback here:

> On that note - can we combine these 2 tests and just use inline theory data to seed different numbers and different domains?
created_at: 2025-05-06T05:41:34Z
author_association: MEMBER
path: util/Seeder/Recipes/OrganizationWithUsersRecipe.cs
user: login: eliykat
body: I'm OK with leaving it where it is for now.
created_at: 2025-05-06T05:41:58Z
author_association: MEMBER
diff_hunk: @@ -7,74 +7,33 @@
 
 namespace Bit.Api.IntegrationTest.AdminConsole.Controllers;
 
-public class OrganizationUsersControllerPerformanceTest : IClassFixture<ApiApplicationFactory>, IAsyncLifetime
+public class OrganizationUsersControllerPerformanceTest(ITestOutputHelper testOutputHelper)
 {
-    private readonly HttpClient _client;
-    private readonly ApiApplicationFactory _factory;
-    private readonly ITestOutputHelper _testOutputHelper;
-
-    public OrganizationUsersControllerPerformanceTest(ApiApplicationFactory factory, ITestOutputHelper testOutputHelper)
+    [Theory(Skip = "Performance test")]
+    [InlineData(100)]
+    [InlineData(60000)]
+    public async Task GetAsync(int seats)
     {
-        _factory = factory;
-        _testOutputHelper = testOutputHelper;
-        _client = _factory.CreateClient();
-    }
+        await using var factory = new ApiApplicationFactory();
path: test/Api.IntegrationTest/AdminConsole/Controllers/OrganizationUsersControllerPerformanceTests.cs
user: login: eliykat
body: This is a much simpler way of handling this 👏 
created_at: 2025-05-09T00:35:38Z
author_association: MEMBER
diff_hunk: @@ -7,74 +7,33 @@
 
 namespace Bit.Api.IntegrationTest.AdminConsole.Controllers;
 
-public class OrganizationUsersControllerPerformanceTest : IClassFixture<ApiApplicationFactory>, IAsyncLifetime
+public class OrganizationUsersControllerPerformanceTest(ITestOutputHelper testOutputHelper)
 {
-    private readonly HttpClient _client;
-    private readonly ApiApplicationFactory _factory;
-    private readonly ITestOutputHelper _testOutputHelper;
-
-    public OrganizationUsersControllerPerformanceTest(ApiApplicationFactory factory, ITestOutputHelper testOutputHelper)
+    [Theory(Skip = "Performance test")]
+    [InlineData(100)]
+    [InlineData(60000)]
+    public async Task GetAsync(int seats)
     {
-        _factory = factory;
-        _testOutputHelper = testOutputHelper;
-        _client = _factory.CreateClient();
-    }
+        await using var factory = new ApiApplicationFactory();
path: test/Api.IntegrationTest/AdminConsole/Controllers/OrganizationUsersControllerPerformanceTests.cs
user: login: Hinton
body: Yea, I had to create a new factory per test instance since the data collides.
created_at: 2025-05-09T11:16:43Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,40 @@
+# Bitwarden Database Seeder Utility
+
+A command-line utility for generating and managing test data for Bitwarden databases.
+
+## Overview
+
+DbSeederUtility is an executable wrapper around the Seeder class library that provides a convenient command-line
+interface for executing seed-recipes in your local environment.
+
+## Installation
+
+The utility can be built and run as a .NET 8 application:
path: util/DbSeederUtility/README.md
user: login: withinfocus
body: 🎨 These specific version references will get stale and probably missed when we upgrade versions. Consider removing them.
created_at: 2025-05-13T18:48:24Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-24T16:08:45Z
body: ![Logo](https://cdn.ast.checkmarx.net/integrations/logo/Checkmarx.png)
**Checkmarx One – Scan Summary & Details** – [77a166f0\-c797\-4e60\-a016\-f645aac1a10d](https://ast.checkmarx.net/projects/fdc4f159-5309-4fab-8961-0f78c088bd72/scans?id=77a166f0-c797-4e60-a016-f645aac1a10d&branch=arch%2Fseed-org-users)
<!--new issues section--><details><summary><b>New Issues (1)</b></summary>
Checkmarx found the following issues in this Pull Request

Severity|Issue|Source File / Package|Checkmarx Insight
---|---|---|---
![MEDIUM](https://cdn.ast.checkmarx.net/integrations/severity/Medium.png)|[Use_Of_Hardcoded_Password](https://ast.checkmarx.net/results/fdc4f159-5309-4fab-8961-0f78c088bd72/77a166f0-c797-4e60-a016-f645aac1a10d/sast/description/259/5559909016429917497)|/util/Seeder/Factories/UserSeeder\.cs: [14](https://github.com/bitwarden/server/blob/arch/seed-org-users//util/Seeder/Factories/UserSeeder.cs#L14)|<details><summary>details</summary>The application uses the hard\-coded password MasterPassword for authentication purposes, either using it to verify users' identities, or to access...<br>`ID: bTudK%2FmCFxq1ATFoOqbLzU5qXx8%3D`</details>[Attack Vector](https://ast.checkmarx.net/results/77a166f0-c797-4e60-a016-f645aac1a10d/fdc4f159-5309-4fab-8961-0f78c088bd72/sast?result-id=bTudK%2FmCFxq1ATFoOqbLzU5qXx8%3D&redirect=true)

</details>

<!--new issues section-->
author_association: CONTRIBUTOR
user: login: codecov[bot]
created_at: 2025-04-24T16:09:30Z
body: ## [Codecov](https://app.codecov.io/gh/bitwarden/server/pull/5703?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bitwarden) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 47.23%. Comparing base [(`7fe022e`)](https://app.codecov.io/gh/bitwarden/server/commit/7fe022e26fce3c3f032757e832df50e9478e6658?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bitwarden) to head [(`6c9b613`)](https://app.codecov.io/gh/bitwarden/server/commit/6c9b613f9daed7626ca509f3723a0fae3dedf7be?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bitwarden).
> Report is 16 commits behind head on main.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #5703      +/-   ##
==========================================
- Coverage   47.29%   47.23%   -0.07%     
==========================================
  Files        1647     1649       +2     
  Lines       74977    75085     +108     
  Branches     6768     6785      +17     
==========================================
+ Hits        35464    35468       +4     
- Misses      38030    38132     +102     
- Partials     1483     1485       +2     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/bitwarden/server/pull/5703?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bitwarden).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bitwarden).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: sonarqubecloud[bot]
created_at: 2025-05-08T16:04:37Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=bitwarden_server&pullRequest=5703) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [1 New issue](https://sonarcloud.io/project/issues?id=bitwarden_server&pullRequest=5703&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=bitwarden_server&pullRequest=5703&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [1 Security Hotspot](https://sonarcloud.io/project/security_hotspots?id=bitwarden_server&pullRequest=5703&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=bitwarden_server&pullRequest=5703&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=bitwarden_server&pullRequest=5703&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=bitwarden_server&pullRequest=5703)


author_association: NONE