mirror of
https://github.com/bitwarden/server
synced 2026-01-06 10:34:01 +00:00
Merge branch 'main' of github.com:bitwarden/server into arch/seeder-api
# Conflicts: # util/Seeder/Recipes/OrganizationWithUsersRecipe.cs
This commit is contained in:
@@ -17,7 +17,31 @@ public class OrganizationSeeder
|
||||
Plan = "Enterprise (Annually)",
|
||||
PlanType = PlanType.EnterpriseAnnually,
|
||||
Seats = seats,
|
||||
|
||||
UseCustomPermissions = true,
|
||||
UseOrganizationDomains = true,
|
||||
UseSecretsManager = true,
|
||||
UseGroups = true,
|
||||
UseDirectory = true,
|
||||
UseEvents = true,
|
||||
UseTotp = true,
|
||||
Use2fa = true,
|
||||
UseApi = true,
|
||||
UseResetPassword = true,
|
||||
UsePasswordManager = true,
|
||||
UseAutomaticUserConfirmation = true,
|
||||
SelfHost = true,
|
||||
UsersGetPremium = true,
|
||||
LimitCollectionCreation = true,
|
||||
LimitCollectionDeletion = true,
|
||||
LimitItemDeletion = true,
|
||||
AllowAdminAccessToAllCollectionItems = true,
|
||||
UseRiskInsights = true,
|
||||
UseAdminSponsoredFamilies = true,
|
||||
SyncSeats = true,
|
||||
Status = OrganizationStatusType.Created,
|
||||
//GatewayCustomerId = "example-customer-id",
|
||||
//GatewaySubscriptionId = "example-subscription-id",
|
||||
MaxStorageGb = 10,
|
||||
// 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",
|
||||
@@ -28,17 +52,25 @@ public class OrganizationSeeder
|
||||
|
||||
public static class OrgnaizationExtensions
|
||||
{
|
||||
public static OrganizationUser CreateOrganizationUser(this Organization organization, User user)
|
||||
/// <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 = user.Id,
|
||||
|
||||
Key = "4.rY01mZFXHOsBAg5Fq4gyXuklWfm6mQASm42DJpx05a+e2mmp+P5W6r54WU2hlREX0uoTxyP91bKKwickSPdCQQ58J45LXHdr9t2uzOYyjVzpzebFcdMw1eElR9W2DW8wEk9+mvtWvKwu7yTebzND+46y1nRMoFydi5zPVLSlJEf81qZZ4Uh1UUMLwXz+NRWfixnGXgq2wRq1bH0n3mqDhayiG4LJKgGdDjWXC8W8MMXDYx24SIJrJu9KiNEMprJE+XVF9nQVNijNAjlWBqkDpsfaWTUfeVLRLctfAqW1blsmIv4RQ91PupYJZDNc8nO9ZTF3TEVM+2KHoxzDJrLs2Q==",
|
||||
Type = OrganizationUserType.Admin,
|
||||
Status = OrganizationUserStatusType.Confirmed
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
122
util/Seeder/Recipes/CollectionsRecipe.cs
Normal file
122
util/Seeder/Recipes/CollectionsRecipe.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Infrastructure.EntityFramework.Repositories;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
|
||||
namespace Bit.Seeder.Recipes;
|
||||
|
||||
public class CollectionsRecipe(DatabaseContext db)
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds collections to an organization and creates relationships between users and collections.
|
||||
/// </summary>
|
||||
/// <param name="organizationId">The ID of the organization to add collections to.</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="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)
|
||||
{
|
||||
var collectionList = CreateAndSaveCollections(organizationId, collections);
|
||||
|
||||
if (collectionList.Any())
|
||||
{
|
||||
CreateAndSaveCollectionUserRelationships(collectionList, organizationUserIds, maxUsersWithRelationships);
|
||||
}
|
||||
|
||||
return collectionList.Select(c => c.Id).ToList();
|
||||
}
|
||||
|
||||
private List<Core.Entities.Collection> CreateAndSaveCollections(Guid organizationId, int count)
|
||||
{
|
||||
var collectionList = new List<Core.Entities.Collection>();
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
collectionList.Add(new Core.Entities.Collection
|
||||
{
|
||||
Id = CoreHelpers.GenerateComb(),
|
||||
OrganizationId = organizationId,
|
||||
Name = $"Collection {i + 1}",
|
||||
Type = CollectionType.SharedCollection,
|
||||
CreationDate = DateTime.UtcNow,
|
||||
RevisionDate = DateTime.UtcNow
|
||||
});
|
||||
}
|
||||
|
||||
if (collectionList.Any())
|
||||
{
|
||||
db.BulkCopy(collectionList);
|
||||
}
|
||||
|
||||
return collectionList;
|
||||
}
|
||||
|
||||
private void CreateAndSaveCollectionUserRelationships(
|
||||
List<Core.Entities.Collection> collections,
|
||||
List<Guid> organizationUserIds,
|
||||
int maxUsersWithRelationships)
|
||||
{
|
||||
if (!organizationUserIds.Any() || maxUsersWithRelationships <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var collectionUsers = BuildCollectionUserRelationships(collections, organizationUserIds, maxUsersWithRelationships);
|
||||
|
||||
if (collectionUsers.Any())
|
||||
{
|
||||
db.BulkCopy(collectionUsers);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates user-to-collection relationships with varied assignment patterns for realistic test data.
|
||||
/// Each user gets 1-3 collections based on a rotating pattern.
|
||||
/// </summary>
|
||||
private List<Core.Entities.CollectionUser> BuildCollectionUserRelationships(
|
||||
List<Core.Entities.Collection> collections,
|
||||
List<Guid> organizationUserIds,
|
||||
int maxUsersWithRelationships)
|
||||
{
|
||||
var maxRelationships = Math.Min(organizationUserIds.Count, maxUsersWithRelationships);
|
||||
var collectionUsers = new List<Core.Entities.CollectionUser>();
|
||||
|
||||
for (var i = 0; i < maxRelationships; i++)
|
||||
{
|
||||
var orgUserId = organizationUserIds[i];
|
||||
var userCollectionAssignments = CreateCollectionAssignmentsForUser(collections, orgUserId, i);
|
||||
collectionUsers.AddRange(userCollectionAssignments);
|
||||
}
|
||||
|
||||
return collectionUsers;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assigns collections to a user with varying permissions.
|
||||
/// Pattern: 1-3 collections per user (cycles: 1, 2, 3, 1, 2, 3...).
|
||||
/// First collection has Manage rights, subsequent ones are ReadOnly.
|
||||
/// </summary>
|
||||
private List<Core.Entities.CollectionUser> CreateCollectionAssignmentsForUser(
|
||||
List<Core.Entities.Collection> collections,
|
||||
Guid organizationUserId,
|
||||
int userIndex)
|
||||
{
|
||||
var assignments = new List<Core.Entities.CollectionUser>();
|
||||
var userCollectionCount = (userIndex % 3) + 1; // Cycles through 1, 2, or 3 collections
|
||||
|
||||
for (var j = 0; j < userCollectionCount; j++)
|
||||
{
|
||||
var collectionIndex = (userIndex + j) % collections.Count; // Distribute across available collections
|
||||
assignments.Add(new Core.Entities.CollectionUser
|
||||
{
|
||||
CollectionId = collections[collectionIndex].Id,
|
||||
OrganizationUserId = organizationUserId,
|
||||
ReadOnly = j > 0, // First assignment gets write access
|
||||
HidePasswords = false,
|
||||
Manage = j == 0 // First assignment gets manage permissions
|
||||
});
|
||||
}
|
||||
|
||||
return assignments;
|
||||
}
|
||||
}
|
||||
94
util/Seeder/Recipes/GroupsRecipe.cs
Normal file
94
util/Seeder/Recipes/GroupsRecipe.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Infrastructure.EntityFramework.Repositories;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
|
||||
namespace Bit.Seeder.Recipes;
|
||||
|
||||
public class GroupsRecipe(DatabaseContext db)
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds groups to an organization and creates relationships between users and groups.
|
||||
/// </summary>
|
||||
/// <param name="organizationId">The ID of the organization to add groups to.</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="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)
|
||||
{
|
||||
var groupList = CreateAndSaveGroups(organizationId, groups);
|
||||
|
||||
if (groupList.Any())
|
||||
{
|
||||
CreateAndSaveGroupUserRelationships(groupList, organizationUserIds, maxUsersWithRelationships);
|
||||
}
|
||||
|
||||
return groupList.Select(g => g.Id).ToList();
|
||||
}
|
||||
|
||||
private List<Core.AdminConsole.Entities.Group> CreateAndSaveGroups(Guid organizationId, int count)
|
||||
{
|
||||
var groupList = new List<Core.AdminConsole.Entities.Group>();
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
groupList.Add(new Core.AdminConsole.Entities.Group
|
||||
{
|
||||
Id = CoreHelpers.GenerateComb(),
|
||||
OrganizationId = organizationId,
|
||||
Name = $"Group {i + 1}"
|
||||
});
|
||||
}
|
||||
|
||||
if (groupList.Any())
|
||||
{
|
||||
db.BulkCopy(groupList);
|
||||
}
|
||||
|
||||
return groupList;
|
||||
}
|
||||
|
||||
private void CreateAndSaveGroupUserRelationships(
|
||||
List<Core.AdminConsole.Entities.Group> groups,
|
||||
List<Guid> organizationUserIds,
|
||||
int maxUsersWithRelationships)
|
||||
{
|
||||
if (!organizationUserIds.Any() || maxUsersWithRelationships <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var groupUsers = BuildGroupUserRelationships(groups, organizationUserIds, maxUsersWithRelationships);
|
||||
|
||||
if (groupUsers.Any())
|
||||
{
|
||||
db.BulkCopy(groupUsers);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates user-to-group relationships with distributed assignment patterns for realistic test data.
|
||||
/// Each user is assigned to one group, distributed evenly across available groups.
|
||||
/// </summary>
|
||||
private List<Core.AdminConsole.Entities.GroupUser> BuildGroupUserRelationships(
|
||||
List<Core.AdminConsole.Entities.Group> groups,
|
||||
List<Guid> organizationUserIds,
|
||||
int maxUsersWithRelationships)
|
||||
{
|
||||
var maxRelationships = Math.Min(organizationUserIds.Count, maxUsersWithRelationships);
|
||||
var groupUsers = new List<Core.AdminConsole.Entities.GroupUser>();
|
||||
|
||||
for (var i = 0; i < maxRelationships; i++)
|
||||
{
|
||||
var orgUserId = organizationUserIds[i];
|
||||
var groupIndex = i % groups.Count; // Round-robin distribution across groups
|
||||
|
||||
groupUsers.Add(new Core.AdminConsole.Entities.GroupUser
|
||||
{
|
||||
GroupId = groups[groupIndex].Id,
|
||||
OrganizationUserId = orgUserId
|
||||
});
|
||||
}
|
||||
|
||||
return groupUsers;
|
||||
}
|
||||
}
|
||||
25
util/Seeder/Recipes/OrganizationDomainRecipe.cs
Normal file
25
util/Seeder/Recipes/OrganizationDomainRecipe.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using Bit.Infrastructure.EntityFramework.Models;
|
||||
using Bit.Infrastructure.EntityFramework.Repositories;
|
||||
|
||||
namespace Bit.Seeder.Recipes;
|
||||
|
||||
public class OrganizationDomainRecipe(DatabaseContext db)
|
||||
{
|
||||
public void AddVerifiedDomainToOrganization(Guid organizationId, string domainName)
|
||||
{
|
||||
var domain = new OrganizationDomain
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrganizationId = organizationId,
|
||||
DomainName = domainName,
|
||||
Txt = Guid.NewGuid().ToString("N"),
|
||||
CreationDate = DateTime.UtcNow,
|
||||
};
|
||||
|
||||
domain.SetVerifiedDate();
|
||||
domain.SetLastCheckedDate();
|
||||
|
||||
db.Add(domain);
|
||||
db.SaveChanges();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Infrastructure.EntityFramework.Repositories;
|
||||
using Bit.Seeder.Factories;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
@@ -7,11 +8,12 @@ namespace Bit.Seeder.Recipes;
|
||||
|
||||
public class OrganizationWithUsersRecipe(DatabaseContext db)
|
||||
{
|
||||
public Guid Seed(string name, int users, string domain)
|
||||
public Guid Seed(string name, string domain, int users, OrganizationUserStatusType usersStatus = OrganizationUserStatusType.Confirmed)
|
||||
{
|
||||
var organization = OrganizationSeeder.CreateEnterprise(name, domain, users);
|
||||
var user = UserSeeder.CreateUserNoMangle($"admin@{domain}");
|
||||
var orgUser = organization.CreateOrganizationUser(user);
|
||||
var seats = Math.Max(users + 1, 1000);
|
||||
var organization = OrganizationSeeder.CreateEnterprise(name, domain, seats);
|
||||
var ownerUser = UserSeeder.CreateUserNoMangle($"owner@{domain}");
|
||||
var ownerOrgUser = organization.CreateOrganizationUser(ownerUser, OrganizationUserType.Owner, OrganizationUserStatusType.Confirmed);
|
||||
|
||||
var additionalUsers = new List<User>();
|
||||
var additionalOrgUsers = new List<OrganizationUser>();
|
||||
@@ -19,12 +21,12 @@ public class OrganizationWithUsersRecipe(DatabaseContext db)
|
||||
{
|
||||
var additionalUser = UserSeeder.CreateUserNoMangle($"user{i}@{domain}");
|
||||
additionalUsers.Add(additionalUser);
|
||||
additionalOrgUsers.Add(organization.CreateOrganizationUser(additionalUser));
|
||||
additionalOrgUsers.Add(organization.CreateOrganizationUser(additionalUser, OrganizationUserType.User, usersStatus));
|
||||
}
|
||||
|
||||
db.Add(organization);
|
||||
db.Add(user);
|
||||
db.Add(orgUser);
|
||||
db.Add(ownerUser);
|
||||
db.Add(ownerOrgUser);
|
||||
|
||||
db.SaveChanges();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user