Refactor to correctly implement statics and remove hardcoded organization keys (#6924)

This commit is contained in:
Mick Letofsky
2026-01-30 16:03:56 +01:00
committed by GitHub
parent bfc645e1c1
commit 5941e830d2
18 changed files with 169 additions and 147 deletions

View File

@@ -1,11 +1,14 @@
using System.Net; using System.Net;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using AutoMapper;
using Bit.Api.AdminConsole.Models.Request; using Bit.Api.AdminConsole.Models.Request;
using Bit.Api.IntegrationTest.Factories; using Bit.Api.IntegrationTest.Factories;
using Bit.Api.IntegrationTest.Helpers; using Bit.Api.IntegrationTest.Helpers;
using Bit.Api.Models.Request; using Bit.Api.Models.Request;
using Bit.Core.Entities;
using Bit.Seeder.Recipes; using Bit.Seeder.Recipes;
using Microsoft.AspNetCore.Identity;
using Xunit; using Xunit;
using Xunit.Abstractions; using Xunit.Abstractions;
@@ -26,7 +29,9 @@ public class GroupsControllerPerformanceTests(ITestOutputHelper testOutputHelper
var client = factory.CreateClient(); var client = factory.CreateClient();
var db = factory.GetDatabaseContext(); var db = factory.GetDatabaseContext();
var orgSeeder = new OrganizationWithUsersRecipe(db); var mapper = factory.GetService<IMapper>();
var passwordHasher = factory.GetService<IPasswordHasher<User>>();
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher);
var collectionsSeeder = new CollectionsRecipe(db); var collectionsSeeder = new CollectionsRecipe(db);
var groupsSeeder = new GroupsRecipe(db); var groupsSeeder = new GroupsRecipe(db);
@@ -34,8 +39,8 @@ public class GroupsControllerPerformanceTests(ITestOutputHelper testOutputHelper
var orgId = orgSeeder.Seed(name: "Org", domain: domain, users: userCount); var orgId = orgSeeder.Seed(name: "Org", domain: domain, users: userCount);
var orgUserIds = db.OrganizationUsers.Where(ou => ou.OrganizationId == orgId).Select(ou => ou.Id).ToList(); var orgUserIds = db.OrganizationUsers.Where(ou => ou.OrganizationId == orgId).Select(ou => ou.Id).ToList();
var collectionIds = collectionsSeeder.AddToOrganization(orgId, collectionCount, orgUserIds, 0); var collectionIds = collectionsSeeder.Seed(orgId, collectionCount, orgUserIds, 0);
var groupIds = groupsSeeder.AddToOrganization(orgId, 1, orgUserIds, 0); var groupIds = groupsSeeder.Seed(orgId, 1, orgUserIds, 0);
var groupId = groupIds.First(); var groupId = groupIds.First();

View File

@@ -1,13 +1,16 @@
using System.Net; using System.Net;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using AutoMapper;
using Bit.Api.AdminConsole.Models.Request.Organizations; using Bit.Api.AdminConsole.Models.Request.Organizations;
using Bit.Api.IntegrationTest.Factories; using Bit.Api.IntegrationTest.Factories;
using Bit.Api.IntegrationTest.Helpers; using Bit.Api.IntegrationTest.Helpers;
using Bit.Api.Models.Request; using Bit.Api.Models.Request;
using Bit.Core.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
using Bit.Seeder.Recipes; using Bit.Seeder.Recipes;
using Microsoft.AspNetCore.Identity;
using Xunit; using Xunit;
using Xunit.Abstractions; using Xunit.Abstractions;
@@ -28,7 +31,9 @@ public class OrganizationUsersControllerPerformanceTests(ITestOutputHelper testO
var client = factory.CreateClient(); var client = factory.CreateClient();
var db = factory.GetDatabaseContext(); var db = factory.GetDatabaseContext();
var orgSeeder = new OrganizationWithUsersRecipe(db); var mapper = factory.GetService<IMapper>();
var passwordHasher = factory.GetService<IPasswordHasher<User>>();
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher);
var collectionsSeeder = new CollectionsRecipe(db); var collectionsSeeder = new CollectionsRecipe(db);
var groupsSeeder = new GroupsRecipe(db); var groupsSeeder = new GroupsRecipe(db);
@@ -37,8 +42,8 @@ public class OrganizationUsersControllerPerformanceTests(ITestOutputHelper testO
var orgId = orgSeeder.Seed(name: "Org", domain: domain, users: seats); var orgId = orgSeeder.Seed(name: "Org", domain: domain, users: seats);
var orgUserIds = db.OrganizationUsers.Where(ou => ou.OrganizationId == orgId).Select(ou => ou.Id).ToList(); var orgUserIds = db.OrganizationUsers.Where(ou => ou.OrganizationId == orgId).Select(ou => ou.Id).ToList();
collectionsSeeder.AddToOrganization(orgId, 10, orgUserIds); collectionsSeeder.Seed(orgId, 10, orgUserIds);
groupsSeeder.AddToOrganization(orgId, 5, orgUserIds); groupsSeeder.Seed(orgId, 5, orgUserIds);
await PerformanceTestHelpers.AuthenticateClientAsync(factory, client, $"owner@{domain}"); await PerformanceTestHelpers.AuthenticateClientAsync(factory, client, $"owner@{domain}");
@@ -64,7 +69,9 @@ public class OrganizationUsersControllerPerformanceTests(ITestOutputHelper testO
var client = factory.CreateClient(); var client = factory.CreateClient();
var db = factory.GetDatabaseContext(); var db = factory.GetDatabaseContext();
var orgSeeder = new OrganizationWithUsersRecipe(db); var mapper = factory.GetService<IMapper>();
var passwordHasher = factory.GetService<IPasswordHasher<User>>();
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher);
var collectionsSeeder = new CollectionsRecipe(db); var collectionsSeeder = new CollectionsRecipe(db);
var groupsSeeder = new GroupsRecipe(db); var groupsSeeder = new GroupsRecipe(db);
@@ -72,8 +79,8 @@ public class OrganizationUsersControllerPerformanceTests(ITestOutputHelper testO
var orgId = orgSeeder.Seed(name: "Org", domain: domain, users: seats); var orgId = orgSeeder.Seed(name: "Org", domain: domain, users: seats);
var orgUserIds = db.OrganizationUsers.Where(ou => ou.OrganizationId == orgId).Select(ou => ou.Id).ToList(); var orgUserIds = db.OrganizationUsers.Where(ou => ou.OrganizationId == orgId).Select(ou => ou.Id).ToList();
collectionsSeeder.AddToOrganization(orgId, 10, orgUserIds); collectionsSeeder.Seed(orgId, 10, orgUserIds);
groupsSeeder.AddToOrganization(orgId, 5, orgUserIds); groupsSeeder.Seed(orgId, 5, orgUserIds);
await PerformanceTestHelpers.AuthenticateClientAsync(factory, client, $"owner@{domain}"); await PerformanceTestHelpers.AuthenticateClientAsync(factory, client, $"owner@{domain}");
@@ -98,14 +105,16 @@ public class OrganizationUsersControllerPerformanceTests(ITestOutputHelper testO
var client = factory.CreateClient(); var client = factory.CreateClient();
var db = factory.GetDatabaseContext(); var db = factory.GetDatabaseContext();
var orgSeeder = new OrganizationWithUsersRecipe(db); var mapper = factory.GetService<IMapper>();
var passwordHasher = factory.GetService<IPasswordHasher<User>>();
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher);
var groupsSeeder = new GroupsRecipe(db); var groupsSeeder = new GroupsRecipe(db);
var domain = OrganizationTestHelpers.GenerateRandomDomain(); var domain = OrganizationTestHelpers.GenerateRandomDomain();
var orgId = orgSeeder.Seed(name: "Org", domain: domain, users: 1); var orgId = orgSeeder.Seed(name: "Org", domain: domain, users: 1);
var orgUserId = db.OrganizationUsers.Where(ou => ou.OrganizationId == orgId).Select(ou => ou.Id).FirstOrDefault(); var orgUserId = db.OrganizationUsers.Where(ou => ou.OrganizationId == orgId).Select(ou => ou.Id).FirstOrDefault();
groupsSeeder.AddToOrganization(orgId, 2, [orgUserId]); groupsSeeder.Seed(orgId, 2, [orgUserId]);
await PerformanceTestHelpers.AuthenticateClientAsync(factory, client, $"owner@{domain}"); await PerformanceTestHelpers.AuthenticateClientAsync(factory, client, $"owner@{domain}");
@@ -130,7 +139,9 @@ public class OrganizationUsersControllerPerformanceTests(ITestOutputHelper testO
var client = factory.CreateClient(); var client = factory.CreateClient();
var db = factory.GetDatabaseContext(); var db = factory.GetDatabaseContext();
var orgSeeder = new OrganizationWithUsersRecipe(db); var mapper = factory.GetService<IMapper>();
var passwordHasher = factory.GetService<IPasswordHasher<User>>();
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher);
var domain = OrganizationTestHelpers.GenerateRandomDomain(); var domain = OrganizationTestHelpers.GenerateRandomDomain();
var orgId = orgSeeder.Seed(name: "Org", domain: domain, users: 1); var orgId = orgSeeder.Seed(name: "Org", domain: domain, users: 1);
@@ -163,7 +174,9 @@ public class OrganizationUsersControllerPerformanceTests(ITestOutputHelper testO
var client = factory.CreateClient(); var client = factory.CreateClient();
var db = factory.GetDatabaseContext(); var db = factory.GetDatabaseContext();
var orgSeeder = new OrganizationWithUsersRecipe(db); var mapper = factory.GetService<IMapper>();
var passwordHasher = factory.GetService<IPasswordHasher<User>>();
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher);
var domain = OrganizationTestHelpers.GenerateRandomDomain(); var domain = OrganizationTestHelpers.GenerateRandomDomain();
var orgId = orgSeeder.Seed( var orgId = orgSeeder.Seed(
@@ -211,7 +224,9 @@ public class OrganizationUsersControllerPerformanceTests(ITestOutputHelper testO
var client = factory.CreateClient(); var client = factory.CreateClient();
var db = factory.GetDatabaseContext(); var db = factory.GetDatabaseContext();
var orgSeeder = new OrganizationWithUsersRecipe(db); var mapper = factory.GetService<IMapper>();
var passwordHasher = factory.GetService<IPasswordHasher<User>>();
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher);
var domain = OrganizationTestHelpers.GenerateRandomDomain(); var domain = OrganizationTestHelpers.GenerateRandomDomain();
var orgId = orgSeeder.Seed(name: "Org", domain: domain, users: userCount); var orgId = orgSeeder.Seed(name: "Org", domain: domain, users: userCount);
@@ -251,7 +266,9 @@ public class OrganizationUsersControllerPerformanceTests(ITestOutputHelper testO
var client = factory.CreateClient(); var client = factory.CreateClient();
var db = factory.GetDatabaseContext(); var db = factory.GetDatabaseContext();
var orgSeeder = new OrganizationWithUsersRecipe(db); var mapper = factory.GetService<IMapper>();
var passwordHasher = factory.GetService<IPasswordHasher<User>>();
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher);
var domain = OrganizationTestHelpers.GenerateRandomDomain(); var domain = OrganizationTestHelpers.GenerateRandomDomain();
var orgId = orgSeeder.Seed( var orgId = orgSeeder.Seed(
@@ -295,7 +312,9 @@ public class OrganizationUsersControllerPerformanceTests(ITestOutputHelper testO
var client = factory.CreateClient(); var client = factory.CreateClient();
var db = factory.GetDatabaseContext(); var db = factory.GetDatabaseContext();
var orgSeeder = new OrganizationWithUsersRecipe(db); var mapper = factory.GetService<IMapper>();
var passwordHasher = factory.GetService<IPasswordHasher<User>>();
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher);
var domain = OrganizationTestHelpers.GenerateRandomDomain(); var domain = OrganizationTestHelpers.GenerateRandomDomain();
var orgId = orgSeeder.Seed( var orgId = orgSeeder.Seed(
@@ -339,7 +358,9 @@ public class OrganizationUsersControllerPerformanceTests(ITestOutputHelper testO
var client = factory.CreateClient(); var client = factory.CreateClient();
var db = factory.GetDatabaseContext(); var db = factory.GetDatabaseContext();
var orgSeeder = new OrganizationWithUsersRecipe(db); var mapper = factory.GetService<IMapper>();
var passwordHasher = factory.GetService<IPasswordHasher<User>>();
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher);
var domainSeeder = new OrganizationDomainRecipe(db); var domainSeeder = new OrganizationDomainRecipe(db);
var domain = OrganizationTestHelpers.GenerateRandomDomain(); var domain = OrganizationTestHelpers.GenerateRandomDomain();
@@ -350,7 +371,7 @@ public class OrganizationUsersControllerPerformanceTests(ITestOutputHelper testO
users: userCount, users: userCount,
usersStatus: OrganizationUserStatusType.Confirmed); usersStatus: OrganizationUserStatusType.Confirmed);
domainSeeder.AddVerifiedDomainToOrganization(orgId, domain); domainSeeder.Seed(orgId, domain);
await PerformanceTestHelpers.AuthenticateClientAsync(factory, client, $"owner@{domain}"); await PerformanceTestHelpers.AuthenticateClientAsync(factory, client, $"owner@{domain}");
@@ -384,7 +405,9 @@ public class OrganizationUsersControllerPerformanceTests(ITestOutputHelper testO
var client = factory.CreateClient(); var client = factory.CreateClient();
var db = factory.GetDatabaseContext(); var db = factory.GetDatabaseContext();
var orgSeeder = new OrganizationWithUsersRecipe(db); var mapper = factory.GetService<IMapper>();
var passwordHasher = factory.GetService<IPasswordHasher<User>>();
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher);
var collectionsSeeder = new CollectionsRecipe(db); var collectionsSeeder = new CollectionsRecipe(db);
var groupsSeeder = new GroupsRecipe(db); var groupsSeeder = new GroupsRecipe(db);
@@ -392,8 +415,8 @@ public class OrganizationUsersControllerPerformanceTests(ITestOutputHelper testO
var orgId = orgSeeder.Seed(name: "Org", domain: domain, users: 1); var orgId = orgSeeder.Seed(name: "Org", domain: domain, users: 1);
var orgUserIds = db.OrganizationUsers.Where(ou => ou.OrganizationId == orgId).Select(ou => ou.Id).ToList(); var orgUserIds = db.OrganizationUsers.Where(ou => ou.OrganizationId == orgId).Select(ou => ou.Id).ToList();
var collectionIds = collectionsSeeder.AddToOrganization(orgId, 3, orgUserIds, 0); var collectionIds = collectionsSeeder.Seed(orgId, 3, orgUserIds, 0);
var groupIds = groupsSeeder.AddToOrganization(orgId, 2, orgUserIds, 0); var groupIds = groupsSeeder.Seed(orgId, 2, orgUserIds, 0);
await PerformanceTestHelpers.AuthenticateClientAsync(factory, client, $"owner@{domain}"); await PerformanceTestHelpers.AuthenticateClientAsync(factory, client, $"owner@{domain}");
@@ -434,7 +457,9 @@ public class OrganizationUsersControllerPerformanceTests(ITestOutputHelper testO
var client = factory.CreateClient(); var client = factory.CreateClient();
var db = factory.GetDatabaseContext(); var db = factory.GetDatabaseContext();
var orgSeeder = new OrganizationWithUsersRecipe(db); var mapper = factory.GetService<IMapper>();
var passwordHasher = factory.GetService<IPasswordHasher<User>>();
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher);
var domain = OrganizationTestHelpers.GenerateRandomDomain(); var domain = OrganizationTestHelpers.GenerateRandomDomain();
var orgId = orgSeeder.Seed(name: "Org", domain: domain, users: userCount); var orgId = orgSeeder.Seed(name: "Org", domain: domain, users: userCount);
@@ -471,7 +496,9 @@ public class OrganizationUsersControllerPerformanceTests(ITestOutputHelper testO
var client = factory.CreateClient(); var client = factory.CreateClient();
var db = factory.GetDatabaseContext(); var db = factory.GetDatabaseContext();
var orgSeeder = new OrganizationWithUsersRecipe(db); var mapper = factory.GetService<IMapper>();
var passwordHasher = factory.GetService<IPasswordHasher<User>>();
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher);
var domainSeeder = new OrganizationDomainRecipe(db); var domainSeeder = new OrganizationDomainRecipe(db);
var domain = OrganizationTestHelpers.GenerateRandomDomain(); var domain = OrganizationTestHelpers.GenerateRandomDomain();
@@ -481,7 +508,7 @@ public class OrganizationUsersControllerPerformanceTests(ITestOutputHelper testO
users: 2, users: 2,
usersStatus: OrganizationUserStatusType.Confirmed); usersStatus: OrganizationUserStatusType.Confirmed);
domainSeeder.AddVerifiedDomainToOrganization(orgId, domain); domainSeeder.Seed(orgId, domain);
await PerformanceTestHelpers.AuthenticateClientAsync(factory, client, $"owner@{domain}"); await PerformanceTestHelpers.AuthenticateClientAsync(factory, client, $"owner@{domain}");
@@ -512,14 +539,16 @@ public class OrganizationUsersControllerPerformanceTests(ITestOutputHelper testO
var client = factory.CreateClient(); var client = factory.CreateClient();
var db = factory.GetDatabaseContext(); var db = factory.GetDatabaseContext();
var orgSeeder = new OrganizationWithUsersRecipe(db); var mapper = factory.GetService<IMapper>();
var passwordHasher = factory.GetService<IPasswordHasher<User>>();
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher);
var collectionsSeeder = new CollectionsRecipe(db); var collectionsSeeder = new CollectionsRecipe(db);
var domain = OrganizationTestHelpers.GenerateRandomDomain(); var domain = OrganizationTestHelpers.GenerateRandomDomain();
var orgId = orgSeeder.Seed(name: "Org", domain: domain, users: 1); var orgId = orgSeeder.Seed(name: "Org", domain: domain, users: 1);
var orgUserIds = db.OrganizationUsers.Where(ou => ou.OrganizationId == orgId).Select(ou => ou.Id).ToList(); var orgUserIds = db.OrganizationUsers.Where(ou => ou.OrganizationId == orgId).Select(ou => ou.Id).ToList();
var collectionIds = collectionsSeeder.AddToOrganization(orgId, 2, orgUserIds, 0); var collectionIds = collectionsSeeder.Seed(orgId, 2, orgUserIds, 0);
await PerformanceTestHelpers.AuthenticateClientAsync(factory, client, $"owner@{domain}"); await PerformanceTestHelpers.AuthenticateClientAsync(factory, client, $"owner@{domain}");
@@ -560,7 +589,9 @@ public class OrganizationUsersControllerPerformanceTests(ITestOutputHelper testO
var client = factory.CreateClient(); var client = factory.CreateClient();
var db = factory.GetDatabaseContext(); var db = factory.GetDatabaseContext();
var orgSeeder = new OrganizationWithUsersRecipe(db); var mapper = factory.GetService<IMapper>();
var passwordHasher = factory.GetService<IPasswordHasher<User>>();
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher);
var domain = OrganizationTestHelpers.GenerateRandomDomain(); var domain = OrganizationTestHelpers.GenerateRandomDomain();
var orgId = orgSeeder.Seed( var orgId = orgSeeder.Seed(

View File

@@ -1,14 +1,17 @@
using System.Net; using System.Net;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using AutoMapper;
using Bit.Api.AdminConsole.Models.Request.Organizations; using Bit.Api.AdminConsole.Models.Request.Organizations;
using Bit.Api.Auth.Models.Request.Accounts; using Bit.Api.Auth.Models.Request.Accounts;
using Bit.Api.IntegrationTest.Factories; using Bit.Api.IntegrationTest.Factories;
using Bit.Api.IntegrationTest.Helpers; using Bit.Api.IntegrationTest.Helpers;
using Bit.Core.AdminConsole.Models.Business.Tokenables; using Bit.Core.AdminConsole.Models.Business.Tokenables;
using Bit.Core.Billing.Enums; using Bit.Core.Billing.Enums;
using Bit.Core.Entities;
using Bit.Core.Tokens; using Bit.Core.Tokens;
using Bit.Seeder.Recipes; using Bit.Seeder.Recipes;
using Microsoft.AspNetCore.Identity;
using Xunit; using Xunit;
using Xunit.Abstractions; using Xunit.Abstractions;
@@ -29,7 +32,9 @@ public class OrganizationsControllerPerformanceTests(ITestOutputHelper testOutpu
var client = factory.CreateClient(); var client = factory.CreateClient();
var db = factory.GetDatabaseContext(); var db = factory.GetDatabaseContext();
var orgSeeder = new OrganizationWithUsersRecipe(db); var mapper = factory.GetService<IMapper>();
var passwordHasher = factory.GetService<IPasswordHasher<User>>();
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher);
var collectionsSeeder = new CollectionsRecipe(db); var collectionsSeeder = new CollectionsRecipe(db);
var groupsSeeder = new GroupsRecipe(db); var groupsSeeder = new GroupsRecipe(db);
@@ -37,8 +42,8 @@ public class OrganizationsControllerPerformanceTests(ITestOutputHelper testOutpu
var orgId = orgSeeder.Seed(name: "Org", domain: domain, users: userCount); var orgId = orgSeeder.Seed(name: "Org", domain: domain, users: userCount);
var orgUserIds = db.OrganizationUsers.Where(ou => ou.OrganizationId == orgId).Select(ou => ou.Id).ToList(); var orgUserIds = db.OrganizationUsers.Where(ou => ou.OrganizationId == orgId).Select(ou => ou.Id).ToList();
collectionsSeeder.AddToOrganization(orgId, collectionCount, orgUserIds, 0); collectionsSeeder.Seed(orgId, collectionCount, orgUserIds, 0);
groupsSeeder.AddToOrganization(orgId, groupCount, orgUserIds, 0); groupsSeeder.Seed(orgId, groupCount, orgUserIds, 0);
await PerformanceTestHelpers.AuthenticateClientAsync(factory, client, $"owner@{domain}"); await PerformanceTestHelpers.AuthenticateClientAsync(factory, client, $"owner@{domain}");
@@ -77,7 +82,9 @@ public class OrganizationsControllerPerformanceTests(ITestOutputHelper testOutpu
var client = factory.CreateClient(); var client = factory.CreateClient();
var db = factory.GetDatabaseContext(); var db = factory.GetDatabaseContext();
var orgSeeder = new OrganizationWithUsersRecipe(db); var mapper = factory.GetService<IMapper>();
var passwordHasher = factory.GetService<IPasswordHasher<User>>();
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher);
var collectionsSeeder = new CollectionsRecipe(db); var collectionsSeeder = new CollectionsRecipe(db);
var groupsSeeder = new GroupsRecipe(db); var groupsSeeder = new GroupsRecipe(db);
@@ -85,8 +92,8 @@ public class OrganizationsControllerPerformanceTests(ITestOutputHelper testOutpu
var orgId = orgSeeder.Seed(name: "Org", domain: domain, users: userCount); var orgId = orgSeeder.Seed(name: "Org", domain: domain, users: userCount);
var orgUserIds = db.OrganizationUsers.Where(ou => ou.OrganizationId == orgId).Select(ou => ou.Id).ToList(); var orgUserIds = db.OrganizationUsers.Where(ou => ou.OrganizationId == orgId).Select(ou => ou.Id).ToList();
collectionsSeeder.AddToOrganization(orgId, collectionCount, orgUserIds, 0); collectionsSeeder.Seed(orgId, collectionCount, orgUserIds, 0);
groupsSeeder.AddToOrganization(orgId, groupCount, orgUserIds, 0); groupsSeeder.Seed(orgId, groupCount, orgUserIds, 0);
await PerformanceTestHelpers.AuthenticateClientAsync(factory, client, $"owner@{domain}"); await PerformanceTestHelpers.AuthenticateClientAsync(factory, client, $"owner@{domain}");

View File

@@ -18,18 +18,17 @@ public class RustSdkCipherTests
[Fact] [Fact]
public void EncryptDecrypt_LoginCipher_RoundtripPreservesPlaintext() public void EncryptDecrypt_LoginCipher_RoundtripPreservesPlaintext()
{ {
var sdk = new RustSdkService(); var orgKeys = RustSdkService.GenerateOrganizationKeys();
var orgKeys = sdk.GenerateOrganizationKeys();
var originalCipher = CreateTestLoginCipher(); var originalCipher = CreateTestLoginCipher();
var originalJson = JsonSerializer.Serialize(originalCipher, SdkJsonOptions); var originalJson = JsonSerializer.Serialize(originalCipher, SdkJsonOptions);
var encryptedJson = sdk.EncryptCipher(originalJson, orgKeys.Key); var encryptedJson = RustSdkService.EncryptCipher(originalJson, orgKeys.Key);
Assert.DoesNotContain("\"error\"", encryptedJson); Assert.DoesNotContain("\"error\"", encryptedJson);
Assert.Contains("\"name\":\"2.", encryptedJson); Assert.Contains("\"name\":\"2.", encryptedJson);
var decryptedJson = sdk.DecryptCipher(encryptedJson, orgKeys.Key); var decryptedJson = RustSdkService.DecryptCipher(encryptedJson, orgKeys.Key);
Assert.DoesNotContain("\"error\"", decryptedJson); Assert.DoesNotContain("\"error\"", decryptedJson);
@@ -45,8 +44,7 @@ public class RustSdkCipherTests
[Fact] [Fact]
public void EncryptCipher_WithUri_EncryptsAllFields() public void EncryptCipher_WithUri_EncryptsAllFields()
{ {
var sdk = new RustSdkService(); var orgKeys = RustSdkService.GenerateOrganizationKeys();
var orgKeys = sdk.GenerateOrganizationKeys();
var cipher = new CipherViewDto var cipher = new CipherViewDto
{ {
@@ -66,7 +64,7 @@ public class RustSdkCipherTests
}; };
var cipherJson = JsonSerializer.Serialize(cipher, SdkJsonOptions); var cipherJson = JsonSerializer.Serialize(cipher, SdkJsonOptions);
var encryptedJson = sdk.EncryptCipher(cipherJson, orgKeys.Key); var encryptedJson = RustSdkService.EncryptCipher(cipherJson, orgKeys.Key);
Assert.DoesNotContain("\"error\"", encryptedJson); Assert.DoesNotContain("\"error\"", encryptedJson);
Assert.DoesNotContain("Amazon Shopping", encryptedJson); Assert.DoesNotContain("Amazon Shopping", encryptedJson);
@@ -77,17 +75,16 @@ public class RustSdkCipherTests
[Fact] [Fact]
public void DecryptCipher_WithWrongKey_FailsOrProducesGarbage() public void DecryptCipher_WithWrongKey_FailsOrProducesGarbage()
{ {
var sdk = new RustSdkService(); var encryptionKey = RustSdkService.GenerateOrganizationKeys();
var encryptionKey = sdk.GenerateOrganizationKeys(); var differentKey = RustSdkService.GenerateOrganizationKeys();
var differentKey = sdk.GenerateOrganizationKeys();
var originalCipher = CreateTestLoginCipher(); var originalCipher = CreateTestLoginCipher();
var cipherJson = JsonSerializer.Serialize(originalCipher, SdkJsonOptions); var cipherJson = JsonSerializer.Serialize(originalCipher, SdkJsonOptions);
var encryptedJson = sdk.EncryptCipher(cipherJson, encryptionKey.Key); var encryptedJson = RustSdkService.EncryptCipher(cipherJson, encryptionKey.Key);
Assert.DoesNotContain("\"error\"", encryptedJson); Assert.DoesNotContain("\"error\"", encryptedJson);
var decryptedJson = sdk.DecryptCipher(encryptedJson, differentKey.Key); var decryptedJson = RustSdkService.DecryptCipher(encryptedJson, differentKey.Key);
var decryptionFailedWithError = decryptedJson.Contains("\"error\""); var decryptionFailedWithError = decryptedJson.Contains("\"error\"");
if (!decryptionFailedWithError) if (!decryptionFailedWithError)
@@ -100,8 +97,7 @@ public class RustSdkCipherTests
[Fact] [Fact]
public void EncryptCipher_WithFields_EncryptsCustomFields() public void EncryptCipher_WithFields_EncryptsCustomFields()
{ {
var sdk = new RustSdkService(); var orgKeys = RustSdkService.GenerateOrganizationKeys();
var orgKeys = sdk.GenerateOrganizationKeys();
var cipher = new CipherViewDto var cipher = new CipherViewDto
{ {
@@ -120,13 +116,13 @@ public class RustSdkCipherTests
}; };
var cipherJson = JsonSerializer.Serialize(cipher, SdkJsonOptions); var cipherJson = JsonSerializer.Serialize(cipher, SdkJsonOptions);
var encryptedJson = sdk.EncryptCipher(cipherJson, orgKeys.Key); var encryptedJson = RustSdkService.EncryptCipher(cipherJson, orgKeys.Key);
Assert.DoesNotContain("\"error\"", encryptedJson); Assert.DoesNotContain("\"error\"", encryptedJson);
Assert.DoesNotContain("sk-secret-api-key-12345", encryptedJson); Assert.DoesNotContain("sk-secret-api-key-12345", encryptedJson);
Assert.DoesNotContain("client-id-xyz", encryptedJson); Assert.DoesNotContain("client-id-xyz", encryptedJson);
var decryptedJson = sdk.DecryptCipher(encryptedJson, orgKeys.Key); var decryptedJson = RustSdkService.DecryptCipher(encryptedJson, orgKeys.Key);
var decrypted = JsonSerializer.Deserialize<CipherViewDto>(decryptedJson, SdkJsonOptions); var decrypted = JsonSerializer.Deserialize<CipherViewDto>(decryptedJson, SdkJsonOptions);
Assert.NotNull(decrypted?.Fields); Assert.NotNull(decrypted?.Fields);
@@ -138,13 +134,11 @@ public class RustSdkCipherTests
[Fact] [Fact]
public void CipherSeeder_ProducesServerCompatibleFormat() public void CipherSeeder_ProducesServerCompatibleFormat()
{ {
var sdk = new RustSdkService(); var orgKeys = RustSdkService.GenerateOrganizationKeys();
var orgKeys = sdk.GenerateOrganizationKeys();
var seeder = new CipherSeeder(sdk);
var orgId = Guid.NewGuid(); var orgId = Guid.NewGuid();
// Create cipher using the seeder // Create cipher using the seeder
var cipher = seeder.CreateOrganizationLoginCipher( var cipher = CipherSeeder.CreateOrganizationLoginCipher(
orgId, orgId,
orgKeys.Key, orgKeys.Key,
name: "GitHub Account", name: "GitHub Account",
@@ -179,11 +173,9 @@ public class RustSdkCipherTests
[Fact] [Fact]
public void CipherSeeder_WithFields_ProducesCorrectServerFormat() public void CipherSeeder_WithFields_ProducesCorrectServerFormat()
{ {
var sdk = new RustSdkService(); var orgKeys = RustSdkService.GenerateOrganizationKeys();
var orgKeys = sdk.GenerateOrganizationKeys();
var seeder = new CipherSeeder(sdk);
var cipher = seeder.CreateOrganizationLoginCipherWithFields( var cipher = CipherSeeder.CreateOrganizationLoginCipherWithFields(
Guid.NewGuid(), Guid.NewGuid(),
orgKeys.Key, orgKeys.Key,
name: "API Service", name: "API Service",

View File

@@ -1,7 +1,6 @@
using AutoMapper; using AutoMapper;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Infrastructure.EntityFramework.Repositories; using Bit.Infrastructure.EntityFramework.Repositories;
using Bit.RustSDK;
using Bit.Seeder.Recipes; using Bit.Seeder.Recipes;
using CommandDotNet; using CommandDotNet;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
@@ -37,7 +36,9 @@ public class Program
var scopedServices = scope.ServiceProvider; var scopedServices = scope.ServiceProvider;
var db = scopedServices.GetRequiredService<DatabaseContext>(); var db = scopedServices.GetRequiredService<DatabaseContext>();
var recipe = new OrganizationWithUsersRecipe(db); var mapper = scopedServices.GetRequiredService<IMapper>();
var passwordHasher = scopedServices.GetRequiredService<IPasswordHasher<User>>();
var recipe = new OrganizationWithUsersRecipe(db, mapper, passwordHasher);
recipe.Seed(name: name, domain: domain, users: users); recipe.Seed(name: name, domain: domain, users: users);
} }
@@ -56,7 +57,6 @@ public class Program
var recipe = new OrganizationWithVaultRecipe( var recipe = new OrganizationWithVaultRecipe(
scopedServices.GetRequiredService<DatabaseContext>(), scopedServices.GetRequiredService<DatabaseContext>(),
scopedServices.GetRequiredService<IMapper>(), scopedServices.GetRequiredService<IMapper>(),
scopedServices.GetRequiredService<RustSdkService>(),
scopedServices.GetRequiredService<IPasswordHasher<User>>()); scopedServices.GetRequiredService<IPasswordHasher<User>>());
recipe.Seed(args.ToOptions()); recipe.Seed(args.ToOptions());

View File

@@ -1,5 +1,4 @@
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.RustSDK;
using Bit.SharedWeb.Utilities; using Bit.SharedWeb.Utilities;
using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
@@ -23,7 +22,6 @@ public static class ServiceCollectionExtension
builder.AddFilter("Microsoft.EntityFrameworkCore.Model.Validation", LogLevel.Error); builder.AddFilter("Microsoft.EntityFrameworkCore.Model.Validation", LogLevel.Error);
}); });
services.AddSingleton(globalSettings); services.AddSingleton(globalSettings);
services.AddSingleton<RustSdkService>();
services.AddSingleton<IPasswordHasher<User>, PasswordHasher<User>>(); services.AddSingleton<IPasswordHasher<User>, PasswordHasher<User>>();
// Add Data Protection services // Add Data Protection services

View File

@@ -37,7 +37,7 @@ public class RustSdkService
PropertyNameCaseInsensitive = true PropertyNameCaseInsensitive = true
}; };
public unsafe UserKeys GenerateUserKeys(string email, string password) public static unsafe UserKeys GenerateUserKeys(string email, string password)
{ {
var emailBytes = StringToRustString(email); var emailBytes = StringToRustString(email);
var passwordBytes = StringToRustString(password); var passwordBytes = StringToRustString(password);
@@ -53,7 +53,7 @@ public class RustSdkService
} }
} }
public unsafe OrganizationKeys GenerateOrganizationKeys() public static unsafe OrganizationKeys GenerateOrganizationKeys()
{ {
var resultPtr = NativeMethods.generate_organization_keys(); var resultPtr = NativeMethods.generate_organization_keys();
@@ -62,7 +62,7 @@ public class RustSdkService
return JsonSerializer.Deserialize<OrganizationKeys>(result, CaseInsensitiveOptions)!; return JsonSerializer.Deserialize<OrganizationKeys>(result, CaseInsensitiveOptions)!;
} }
public unsafe string GenerateUserOrganizationKey(string userKey, string orgKey) public static unsafe string GenerateUserOrganizationKey(string userKey, string orgKey)
{ {
var userKeyBytes = StringToRustString(userKey); var userKeyBytes = StringToRustString(userKey);
var orgKeyBytes = StringToRustString(orgKey); var orgKeyBytes = StringToRustString(orgKey);
@@ -78,7 +78,7 @@ public class RustSdkService
} }
} }
public unsafe string EncryptCipher(string cipherViewJson, string symmetricKeyBase64) public static unsafe string EncryptCipher(string cipherViewJson, string symmetricKeyBase64)
{ {
var cipherViewBytes = StringToRustString(cipherViewJson); var cipherViewBytes = StringToRustString(cipherViewJson);
var keyBytes = StringToRustString(symmetricKeyBase64); var keyBytes = StringToRustString(symmetricKeyBase64);
@@ -92,7 +92,7 @@ public class RustSdkService
} }
} }
public unsafe string DecryptCipher(string cipherJson, string symmetricKeyBase64) public static unsafe string DecryptCipher(string cipherJson, string symmetricKeyBase64)
{ {
var cipherBytes = StringToRustString(cipherJson); var cipherBytes = StringToRustString(cipherJson);
var keyBytes = StringToRustString(symmetricKeyBase64); var keyBytes = StringToRustString(symmetricKeyBase64);
@@ -110,7 +110,7 @@ public class RustSdkService
/// Encrypts a plaintext string using the provided symmetric key. /// Encrypts a plaintext string using the provided symmetric key.
/// Returns an EncString in format "2.{iv}|{data}|{mac}". /// Returns an EncString in format "2.{iv}|{data}|{mac}".
/// </summary> /// </summary>
public unsafe string EncryptString(string plaintext, string symmetricKeyBase64) public static unsafe string EncryptString(string plaintext, string symmetricKeyBase64)
{ {
var plaintextBytes = StringToRustString(plaintext); var plaintextBytes = StringToRustString(plaintext);
var keyBytes = StringToRustString(symmetricKeyBase64); var keyBytes = StringToRustString(symmetricKeyBase64);

View File

@@ -22,8 +22,6 @@ namespace Bit.Seeder.Factories;
/// </remarks> /// </remarks>
public class CipherSeeder public class CipherSeeder
{ {
private readonly RustSdkService _sdkService;
private static readonly JsonSerializerOptions SdkJsonOptions = new() private static readonly JsonSerializerOptions SdkJsonOptions = new()
{ {
PropertyNamingPolicy = JsonNamingPolicy.CamelCase, PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
@@ -36,12 +34,7 @@ public class CipherSeeder
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
}; };
public CipherSeeder(RustSdkService sdkService) public static Cipher CreateOrganizationLoginCipher(
{
_sdkService = sdkService;
}
public Cipher CreateOrganizationLoginCipher(
Guid organizationId, Guid organizationId,
string orgKeyBase64, string orgKeyBase64,
string name, string name,
@@ -67,7 +60,7 @@ public class CipherSeeder
return EncryptAndTransform(cipherView, orgKeyBase64, organizationId); return EncryptAndTransform(cipherView, orgKeyBase64, organizationId);
} }
public Cipher CreateOrganizationLoginCipherWithFields( public static Cipher CreateOrganizationLoginCipherWithFields(
Guid organizationId, Guid organizationId,
string orgKeyBase64, string orgKeyBase64,
string name, string name,
@@ -98,10 +91,10 @@ public class CipherSeeder
return EncryptAndTransform(cipherView, orgKeyBase64, organizationId); return EncryptAndTransform(cipherView, orgKeyBase64, organizationId);
} }
private Cipher EncryptAndTransform(CipherViewDto cipherView, string keyBase64, Guid organizationId) private static Cipher EncryptAndTransform(CipherViewDto cipherView, string keyBase64, Guid organizationId)
{ {
var viewJson = JsonSerializer.Serialize(cipherView, SdkJsonOptions); var viewJson = JsonSerializer.Serialize(cipherView, SdkJsonOptions);
var encryptedJson = _sdkService.EncryptCipher(viewJson, keyBase64); var encryptedJson = RustSdkService.EncryptCipher(viewJson, keyBase64);
var encryptedDto = JsonSerializer.Deserialize<EncryptedCipherDto>(encryptedJson, SdkJsonOptions) var encryptedDto = JsonSerializer.Deserialize<EncryptedCipherDto>(encryptedJson, SdkJsonOptions)
?? throw new InvalidOperationException("Failed to parse encrypted cipher"); ?? throw new InvalidOperationException("Failed to parse encrypted cipher");

View File

@@ -3,15 +3,15 @@ using Bit.RustSDK;
namespace Bit.Seeder.Factories; namespace Bit.Seeder.Factories;
public class CollectionSeeder(RustSdkService sdkService) public class CollectionSeeder
{ {
public Collection CreateCollection(Guid organizationId, string orgKey, string name) public static Collection CreateCollection(Guid organizationId, string orgKey, string name)
{ {
return new Collection return new Collection
{ {
Id = Guid.NewGuid(), Id = Guid.NewGuid(),
OrganizationId = organizationId, OrganizationId = organizationId,
Name = sdkService.EncryptString(name, orgKey), Name = RustSdkService.EncryptString(name, orgKey),
CreationDate = DateTime.UtcNow, CreationDate = DateTime.UtcNow,
RevisionDate = DateTime.UtcNow RevisionDate = DateTime.UtcNow
}; };

View File

@@ -8,7 +8,7 @@ namespace Bit.Seeder.Factories;
/// Factory for creating Folder entities with encrypted names. /// Factory for creating Folder entities with encrypted names.
/// Folders are per-user constructs encrypted with the user's symmetric key. /// Folders are per-user constructs encrypted with the user's symmetric key.
/// </summary> /// </summary>
internal sealed class FolderSeeder(RustSdkService sdkService) internal sealed class FolderSeeder
{ {
/// <summary> /// <summary>
/// Creates a folder with an encrypted name. /// Creates a folder with an encrypted name.
@@ -16,13 +16,13 @@ internal sealed class FolderSeeder(RustSdkService sdkService)
/// <param name="userId">The user who owns this folder.</param> /// <param name="userId">The user who owns this folder.</param>
/// <param name="userKeyBase64">The user's symmetric key (not org key).</param> /// <param name="userKeyBase64">The user's symmetric key (not org key).</param>
/// <param name="name">The plaintext folder name to encrypt.</param> /// <param name="name">The plaintext folder name to encrypt.</param>
public Folder CreateFolder(Guid userId, string userKeyBase64, string name) public static Folder CreateFolder(Guid userId, string userKeyBase64, string name)
{ {
return new Folder return new Folder
{ {
Id = CoreHelpers.GenerateComb(), Id = CoreHelpers.GenerateComb(),
UserId = userId, UserId = userId,
Name = sdkService.EncryptString(name, userKeyBase64) Name = RustSdkService.EncryptString(name, userKeyBase64)
}; };
} }
} }

View File

@@ -7,9 +7,6 @@ namespace Bit.Seeder.Factories;
public class OrganizationSeeder public class OrganizationSeeder
{ {
private static readonly string _defaultPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmIJbGMk6eZqVE7UxhZ46Weu2jKciqOiOkSVYtGvs61rfe9AXxtLaaZEKN4d4DmkZcF6dna2eXNxZmb7U4pwlttye8ksqISe6IUAZQox7auBpjopdCEPhKRg3BD/u8ks9UxSxgWe+fpebjt6gd5hsl1/5HOObn7SeU6EEU04cp3/eH7a4OTdXxB8oN62HGV9kM/ubM1goILgjoSJDbihMK0eb7b8hPHwcA/YOgKKiu/N3FighccdSMD5Pk+HfjacsFNZQa2EsqW09IvvSZ+iL6HQeZ1vwc/6TO1J7EOfJZFQcjoEL9LVI693efYoMZSmrPEWziZ4PvwpOOGo6OObyMQIDAQAB";
private static readonly string _defaultPrivateKey = "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 Organization CreateEnterprise(string name, string domain, int seats, string? publicKey = null, string? privateKey = null) public static Organization CreateEnterprise(string name, string domain, int seats, string? publicKey = null, string? privateKey = null)
{ {
return new Organization return new Organization
@@ -43,36 +40,14 @@ public class OrganizationSeeder
SyncSeats = true, SyncSeats = true,
Status = OrganizationStatusType.Created, Status = OrganizationStatusType.Created,
MaxStorageGb = 10, MaxStorageGb = 10,
PublicKey = publicKey ?? _defaultPublicKey, PublicKey = publicKey,
PrivateKey = privateKey ?? _defaultPrivateKey, PrivateKey = privateKey
}; };
} }
} }
public static class OrganizationExtensions public static class OrganizationExtensions
{ {
/// <summary>
/// Creates an OrganizationUser with fields populated based on status.
/// For Invited status, only user.Email is used. For other statuses, user.Id is used.
/// </summary>
public static OrganizationUser CreateOrganizationUser(
this Organization organization, User user, OrganizationUserType type, OrganizationUserStatusType status)
{
var isInvited = status == OrganizationUserStatusType.Invited;
var isConfirmed = status == OrganizationUserStatusType.Confirmed || status == OrganizationUserStatusType.Revoked;
return new OrganizationUser
{
Id = Guid.NewGuid(),
OrganizationId = organization.Id,
UserId = isInvited ? null : user.Id,
Email = isInvited ? user.Email : null,
Key = isConfirmed ? "4.rY01mZFXHOsBAg5Fq4gyXuklWfm6mQASm42DJpx05a+e2mmp+P5W6r54WU2hlREX0uoTxyP91bKKwickSPdCQQ58J45LXHdr9t2uzOYyjVzpzebFcdMw1eElR9W2DW8wEk9+mvtWvKwu7yTebzND+46y1nRMoFydi5zPVLSlJEf81qZZ4Uh1UUMLwXz+NRWfixnGXgq2wRq1bH0n3mqDhayiG4LJKgGdDjWXC8W8MMXDYx24SIJrJu9KiNEMprJE+XVF9nQVNijNAjlWBqkDpsfaWTUfeVLRLctfAqW1blsmIv4RQ91PupYJZDNc8nO9ZTF3TEVM+2KHoxzDJrLs2Q==" : null,
Type = type,
Status = status
};
}
/// <summary> /// <summary>
/// Creates an OrganizationUser with a dynamically provided encrypted org key. /// Creates an OrganizationUser with a dynamically provided encrypted org key.
/// The encryptedOrgKey should be generated using sdkService.GenerateUserOrganizationKey(). /// The encryptedOrgKey should be generated using sdkService.GenerateUserOrganizationKey().

View File

@@ -11,7 +11,7 @@ public struct UserData
public string Email; public string Email;
} }
public class UserSeeder(RustSdkService sdkService, IPasswordHasher<Bit.Core.Entities.User> passwordHasher, MangleId mangleId) public class UserSeeder(IPasswordHasher<Bit.Core.Entities.User> passwordHasher, MangleId mangleId)
{ {
private string MangleEmail(string email) private string MangleEmail(string email)
{ {
@@ -21,7 +21,7 @@ public class UserSeeder(RustSdkService sdkService, IPasswordHasher<Bit.Core.Enti
public User CreateUser(string email, bool emailVerified = false, bool premium = false) public User CreateUser(string email, bool emailVerified = false, bool premium = false)
{ {
email = MangleEmail(email); email = MangleEmail(email);
var keys = sdkService.GenerateUserKeys(email, DefaultPassword); var keys = RustSdkService.GenerateUserKeys(email, DefaultPassword);
var user = new User var user = new User
{ {
@@ -76,10 +76,9 @@ public class UserSeeder(RustSdkService sdkService, IPasswordHasher<Bit.Core.Enti
/// </summary> /// </summary>
public static User CreateUserWithSdkKeys( public static User CreateUserWithSdkKeys(
string email, string email,
RustSdkService sdkService,
IPasswordHasher<User> passwordHasher) IPasswordHasher<User> passwordHasher)
{ {
var keys = sdkService.GenerateUserKeys(email, DefaultPassword); var keys = RustSdkService.GenerateUserKeys(email, DefaultPassword);
return CreateUserFromKeys(email, keys, passwordHasher); return CreateUserFromKeys(email, keys, passwordHasher);
} }

View File

@@ -14,7 +14,7 @@ public class CollectionsRecipe(DatabaseContext db)
/// <param name="collections">The number of collections to add.</param> /// <param name="collections">The number of collections to add.</param>
/// <param name="organizationUserIds">The IDs of the users to create relationships with.</param> /// <param name="organizationUserIds">The IDs of the users to create relationships with.</param>
/// <param name="maxUsersWithRelationships">The maximum number of users to create relationships with.</param> /// <param name="maxUsersWithRelationships">The maximum number of users to create relationships with.</param>
public List<Guid> AddToOrganization(Guid organizationId, int collections, List<Guid> organizationUserIds, int maxUsersWithRelationships = 1000) public List<Guid> Seed(Guid organizationId, int collections, List<Guid> organizationUserIds, int maxUsersWithRelationships = 1000)
{ {
var collectionList = CreateAndSaveCollections(organizationId, collections); var collectionList = CreateAndSaveCollections(organizationId, collections);

View File

@@ -13,7 +13,7 @@ public class GroupsRecipe(DatabaseContext db)
/// <param name="groups">The number of groups to add.</param> /// <param name="groups">The number of groups to add.</param>
/// <param name="organizationUserIds">The IDs of the users to create relationships with.</param> /// <param name="organizationUserIds">The IDs of the users to create relationships with.</param>
/// <param name="maxUsersWithRelationships">The maximum number of users to create relationships with.</param> /// <param name="maxUsersWithRelationships">The maximum number of users to create relationships with.</param>
public List<Guid> AddToOrganization(Guid organizationId, int groups, List<Guid> organizationUserIds, int maxUsersWithRelationships = 1000) public List<Guid> Seed(Guid organizationId, int groups, List<Guid> organizationUserIds, int maxUsersWithRelationships = 1000)
{ {
var groupList = CreateAndSaveGroups(organizationId, groups); var groupList = CreateAndSaveGroups(organizationId, groups);

View File

@@ -5,7 +5,7 @@ namespace Bit.Seeder.Recipes;
public class OrganizationDomainRecipe(DatabaseContext db) public class OrganizationDomainRecipe(DatabaseContext db)
{ {
public void AddVerifiedDomainToOrganization(Guid organizationId, string domainName) public void Seed(Guid organizationId, string domainName)
{ {
var domain = new OrganizationDomain var domain = new OrganizationDomain
{ {

View File

@@ -1,39 +1,66 @@
using Bit.Core.Entities; using AutoMapper;
using Bit.Core.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Infrastructure.EntityFramework.Repositories; using Bit.Infrastructure.EntityFramework.Repositories;
using Bit.RustSDK;
using Bit.Seeder.Factories; using Bit.Seeder.Factories;
using LinqToDB.EntityFrameworkCore; using LinqToDB.EntityFrameworkCore;
using Microsoft.AspNetCore.Identity;
using EfOrganization = Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization;
using EfOrganizationUser = Bit.Infrastructure.EntityFramework.Models.OrganizationUser;
using EfUser = Bit.Infrastructure.EntityFramework.Models.User;
namespace Bit.Seeder.Recipes; namespace Bit.Seeder.Recipes;
public class OrganizationWithUsersRecipe(DatabaseContext db) public class OrganizationWithUsersRecipe(DatabaseContext db, IMapper mapper, IPasswordHasher<User> passwordHasher)
{ {
public Guid Seed(string name, string domain, int users, OrganizationUserStatusType usersStatus = OrganizationUserStatusType.Confirmed) public Guid Seed(string name, string domain, int users, OrganizationUserStatusType usersStatus = OrganizationUserStatusType.Confirmed)
{ {
var seats = Math.Max(users + 1, 1000); var seats = Math.Max(users + 1, 1000);
var organization = OrganizationSeeder.CreateEnterprise(name, domain, seats);
var ownerUser = UserSeeder.CreateUserNoMangle($"owner@{domain}"); // Generate organization keys
var ownerOrgUser = organization.CreateOrganizationUser(ownerUser, OrganizationUserType.Owner, OrganizationUserStatusType.Confirmed); var orgKeys = RustSdkService.GenerateOrganizationKeys();
var organization = OrganizationSeeder.CreateEnterprise(
name, domain, seats, orgKeys.PublicKey, orgKeys.PrivateKey);
// Create owner with SDK-generated keys
var ownerUser = UserSeeder.CreateUserWithSdkKeys($"owner@{domain}", passwordHasher);
var ownerOrgKey = RustSdkService.GenerateUserOrganizationKey(ownerUser.PublicKey!, orgKeys.Key);
var ownerOrgUser = organization.CreateOrganizationUserWithKey(
ownerUser, OrganizationUserType.Owner, OrganizationUserStatusType.Confirmed, ownerOrgKey);
var additionalUsers = new List<User>(); var additionalUsers = new List<User>();
var additionalOrgUsers = new List<OrganizationUser>(); var additionalOrgUsers = new List<OrganizationUser>();
for (var i = 0; i < users; i++) for (var i = 0; i < users; i++)
{ {
var additionalUser = UserSeeder.CreateUserNoMangle($"user{i}@{domain}"); var additionalUser = UserSeeder.CreateUserWithSdkKeys($"user{i}@{domain}", passwordHasher);
additionalUsers.Add(additionalUser); additionalUsers.Add(additionalUser);
additionalOrgUsers.Add(organization.CreateOrganizationUser(additionalUser, OrganizationUserType.User, usersStatus));
// Generate org key for confirmed/revoked users
var shouldHaveKey = usersStatus == OrganizationUserStatusType.Confirmed
|| usersStatus == OrganizationUserStatusType.Revoked;
var userOrgKey = shouldHaveKey
? RustSdkService.GenerateUserOrganizationKey(additionalUser.PublicKey!, orgKeys.Key)
: null;
additionalOrgUsers.Add(organization.CreateOrganizationUserWithKey(
additionalUser, OrganizationUserType.User, usersStatus, userOrgKey));
} }
db.Add(organization); // Map Core entities to EF entities before adding to DbContext
db.Add(ownerUser); db.Add(mapper.Map<EfOrganization>(organization));
db.Add(ownerOrgUser); db.Add(mapper.Map<EfUser>(ownerUser));
db.Add(mapper.Map<EfOrganizationUser>(ownerOrgUser));
// Map and BulkCopy additional users
var efAdditionalUsers = additionalUsers.Select(u => mapper.Map<EfUser>(u)).ToList();
var efAdditionalOrgUsers = additionalOrgUsers.Select(ou => mapper.Map<EfOrganizationUser>(ou)).ToList();
db.BulkCopy(efAdditionalUsers);
db.BulkCopy(efAdditionalOrgUsers);
db.SaveChanges(); db.SaveChanges();
// Use LinqToDB's BulkCopy for significant better performance
db.BulkCopy(additionalUsers);
db.BulkCopy(additionalOrgUsers);
return organization.Id; return organization.Id;
} }
} }

View File

@@ -27,12 +27,8 @@ namespace Bit.Seeder.Recipes;
public class OrganizationWithVaultRecipe( public class OrganizationWithVaultRecipe(
DatabaseContext db, DatabaseContext db,
IMapper mapper, IMapper mapper,
RustSdkService sdkService,
IPasswordHasher<User> passwordHasher) IPasswordHasher<User> passwordHasher)
{ {
private readonly CollectionSeeder _collectionSeeder = new(sdkService);
private readonly CipherSeeder _cipherSeeder = new(sdkService);
private readonly FolderSeeder _folderSeeder = new(sdkService);
/// <summary> /// <summary>
/// Tracks a user with their symmetric key for folder encryption. /// Tracks a user with their symmetric key for folder encryption.
@@ -47,15 +43,15 @@ public class OrganizationWithVaultRecipe(
public Guid Seed(OrganizationVaultOptions options) public Guid Seed(OrganizationVaultOptions options)
{ {
var seats = Math.Max(options.Users + 1, 1000); var seats = Math.Max(options.Users + 1, 1000);
var orgKeys = sdkService.GenerateOrganizationKeys(); var orgKeys = RustSdkService.GenerateOrganizationKeys();
// Create organization via factory // Create organization via factory
var organization = OrganizationSeeder.CreateEnterprise( var organization = OrganizationSeeder.CreateEnterprise(
options.Name, options.Domain, seats, orgKeys.PublicKey, orgKeys.PrivateKey); options.Name, options.Domain, seats, orgKeys.PublicKey, orgKeys.PrivateKey);
// Create owner user via factory // Create owner user via factory
var ownerUser = UserSeeder.CreateUserWithSdkKeys($"owner@{options.Domain}", sdkService, passwordHasher); var ownerUser = UserSeeder.CreateUserWithSdkKeys($"owner@{options.Domain}", passwordHasher);
var ownerOrgKey = sdkService.GenerateUserOrganizationKey(ownerUser.PublicKey!, orgKeys.Key); var ownerOrgKey = RustSdkService.GenerateUserOrganizationKey(ownerUser.PublicKey!, orgKeys.Key);
var ownerOrgUser = organization.CreateOrganizationUserWithKey( var ownerOrgUser = organization.CreateOrganizationUserWithKey(
ownerUser, OrganizationUserType.Owner, OrganizationUserStatusType.Confirmed, ownerOrgKey); ownerUser, OrganizationUserType.Owner, OrganizationUserStatusType.Confirmed, ownerOrgKey);
@@ -67,7 +63,7 @@ public class OrganizationWithVaultRecipe(
for (var i = 0; i < options.Users; i++) for (var i = 0; i < options.Users; i++)
{ {
var email = $"user{i}@{options.Domain}"; var email = $"user{i}@{options.Domain}";
var userKeys = sdkService.GenerateUserKeys(email, UserSeeder.DefaultPassword); var userKeys = RustSdkService.GenerateUserKeys(email, UserSeeder.DefaultPassword);
var memberUser = UserSeeder.CreateUserFromKeys(email, userKeys, passwordHasher); var memberUser = UserSeeder.CreateUserFromKeys(email, userKeys, passwordHasher);
memberUsersWithKeys.Add(new UserWithKey(memberUser, userKeys.Key)); memberUsersWithKeys.Add(new UserWithKey(memberUser, userKeys.Key));
@@ -77,7 +73,7 @@ public class OrganizationWithVaultRecipe(
var memberOrgKey = (status == OrganizationUserStatusType.Confirmed || var memberOrgKey = (status == OrganizationUserStatusType.Confirmed ||
status == OrganizationUserStatusType.Revoked) status == OrganizationUserStatusType.Revoked)
? sdkService.GenerateUserOrganizationKey(memberUser.PublicKey!, orgKeys.Key) ? RustSdkService.GenerateUserOrganizationKey(memberUser.PublicKey!, orgKeys.Key)
: null; : null;
memberOrgUsers.Add(organization.CreateOrganizationUserWithKey( memberOrgUsers.Add(organization.CreateOrganizationUserWithKey(
@@ -124,12 +120,12 @@ public class OrganizationWithVaultRecipe(
{ {
var structure = OrgStructures.GetStructure(structureModel.Value); var structure = OrgStructures.GetStructure(structureModel.Value);
collections = structure.Units collections = structure.Units
.Select(unit => _collectionSeeder.CreateCollection(organizationId, orgKeyBase64, unit.Name)) .Select(unit => CollectionSeeder.CreateCollection(organizationId, orgKeyBase64, unit.Name))
.ToList(); .ToList();
} }
else else
{ {
collections = [_collectionSeeder.CreateCollection(organizationId, orgKeyBase64, "Default Collection")]; collections = [CollectionSeeder.CreateCollection(organizationId, orgKeyBase64, "Default Collection")];
} }
db.BulkCopy(collections); db.BulkCopy(collections);
@@ -191,7 +187,7 @@ public class OrganizationWithVaultRecipe(
.Select(i => .Select(i =>
{ {
var company = companies[i % companies.Length]; var company = companies[i % companies.Length];
return _cipherSeeder.CreateOrganizationLoginCipher( return CipherSeeder.CreateOrganizationLoginCipher(
organizationId, organizationId,
orgKeyBase64, orgKeyBase64,
name: $"{company.Name} ({company.Category})", name: $"{company.Name} ({company.Category})",
@@ -285,7 +281,7 @@ public class OrganizationWithVaultRecipe(
{ {
var folderCount = GetFolderCountForUser(userIndex, usersWithKeys.Count, random); var folderCount = GetFolderCountForUser(userIndex, usersWithKeys.Count, random);
return Enumerable.Range(0, folderCount) return Enumerable.Range(0, folderCount)
.Select(folderIndex => _folderSeeder.CreateFolder( .Select(folderIndex => FolderSeeder.CreateFolder(
uwk.User.Id, uwk.User.Id,
uwk.SymmetricKey, uwk.SymmetricKey,
folderNameGenerator.GetFolderName(userIndex * 15 + folderIndex))); folderNameGenerator.GetFolderName(userIndex * 15 + folderIndex)));

View File

@@ -37,7 +37,6 @@ public class Startup
services.AddScoped<IPasswordHasher<Core.Entities.User>, PasswordHasher<Core.Entities.User>>(); services.AddScoped<IPasswordHasher<Core.Entities.User>, PasswordHasher<Core.Entities.User>>();
services.AddSingleton<RustSDK.RustSdkService>();
services.AddScoped<UserSeeder>(); services.AddScoped<UserSeeder>();
services.AddSeederApiServices(); services.AddSeederApiServices();