mirror of
https://github.com/bitwarden/server
synced 2026-01-28 23:36:12 +00:00
Implement Bogus for name generation & Region specific usernames for vault items
This commit is contained in:
@@ -58,7 +58,9 @@ public class Program
|
||||
[Option('m', "mix-user-statuses", Description = "Use realistic status mix (85% confirmed, 5% each invited/accepted/revoked). Requires >= 10 users.")]
|
||||
bool mixStatuses = true,
|
||||
[Option('o', "org-structure", Description = "Org structure for collections: Traditional, Spotify, or Modern")]
|
||||
string? structure = null
|
||||
string? structure = null,
|
||||
[Option('r', "region", Description = "Geographic region for names: NorthAmerica, Europe, AsiaPacific, LatinAmerica, MiddleEast, Africa, or Global")]
|
||||
string? region = null
|
||||
)
|
||||
{
|
||||
if (users < 1)
|
||||
@@ -77,6 +79,7 @@ public class Program
|
||||
}
|
||||
|
||||
var structureModel = ParseOrgStructure(structure);
|
||||
var geographicRegion = ParseGeographicRegion(region);
|
||||
|
||||
var services = new ServiceCollection();
|
||||
ServiceCollectionExtension.ConfigureServices(services);
|
||||
@@ -99,7 +102,8 @@ public class Program
|
||||
Ciphers = ciphers,
|
||||
Groups = groups,
|
||||
RealisticStatusMix = mixStatuses,
|
||||
StructureModel = structureModel
|
||||
StructureModel = structureModel,
|
||||
Region = geographicRegion
|
||||
});
|
||||
}
|
||||
|
||||
@@ -118,4 +122,24 @@ public class Program
|
||||
_ => throw new ArgumentException($"Unknown structure '{structure}'. Use: Traditional, Spotify, or Modern")
|
||||
};
|
||||
}
|
||||
|
||||
private static GeographicRegion? ParseGeographicRegion(string? region)
|
||||
{
|
||||
if (string.IsNullOrEmpty(region))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return region.ToLowerInvariant() switch
|
||||
{
|
||||
"northamerica" => GeographicRegion.NorthAmerica,
|
||||
"europe" => GeographicRegion.Europe,
|
||||
"asiapacific" => GeographicRegion.AsiaPacific,
|
||||
"latinamerica" => GeographicRegion.LatinAmerica,
|
||||
"middleeast" => GeographicRegion.MiddleEast,
|
||||
"africa" => GeographicRegion.Africa,
|
||||
"global" => GeographicRegion.Global,
|
||||
_ => throw new ArgumentException($"Unknown region '{region}'. Use: NorthAmerica, Europe, AsiaPacific, LatinAmerica, MiddleEast, Africa, or Global")
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
78
util/Seeder/Data/BogusNameProvider.cs
Normal file
78
util/Seeder/Data/BogusNameProvider.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
using Bit.Seeder.Data.Enums;
|
||||
using Bogus;
|
||||
using Bogus.DataSets;
|
||||
|
||||
namespace Bit.Seeder.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Provides locale-aware name generation using the Bogus library.
|
||||
/// Maps GeographicRegion to appropriate Bogus locales for culturally-appropriate names.
|
||||
/// </summary>
|
||||
internal sealed class BogusNameProvider
|
||||
{
|
||||
private readonly Faker _faker;
|
||||
|
||||
public BogusNameProvider(GeographicRegion region, int? seed = null)
|
||||
{
|
||||
var locale = MapRegionToLocale(region, seed);
|
||||
_faker = seed.HasValue
|
||||
? new Faker(locale) { Random = new Randomizer(seed.Value) }
|
||||
: new Faker(locale);
|
||||
}
|
||||
|
||||
public string FirstName() => _faker.Name.FirstName();
|
||||
|
||||
public string FirstName(Name.Gender gender) => _faker.Name.FirstName(gender);
|
||||
|
||||
public string LastName() => _faker.Name.LastName();
|
||||
|
||||
private static string MapRegionToLocale(GeographicRegion region, int? seed) => region switch
|
||||
{
|
||||
GeographicRegion.NorthAmerica => "en_US",
|
||||
GeographicRegion.Europe => GetRandomEuropeanLocale(seed),
|
||||
GeographicRegion.AsiaPacific => GetRandomAsianLocale(seed),
|
||||
GeographicRegion.LatinAmerica => GetRandomLatinAmericanLocale(seed),
|
||||
GeographicRegion.MiddleEast => GetRandomMiddleEastLocale(seed),
|
||||
GeographicRegion.Africa => GetRandomAfricanLocale(seed),
|
||||
GeographicRegion.Global => "en",
|
||||
_ => "en"
|
||||
};
|
||||
|
||||
private static string GetRandomEuropeanLocale(int? seed)
|
||||
{
|
||||
var locales = new[] { "en_GB", "de", "fr", "es", "it", "nl", "pl", "pt_PT", "sv" };
|
||||
return PickLocale(locales, seed);
|
||||
}
|
||||
|
||||
private static string GetRandomAsianLocale(int? seed)
|
||||
{
|
||||
var locales = new[] { "ja", "ko", "zh_CN", "zh_TW", "vi" };
|
||||
return PickLocale(locales, seed);
|
||||
}
|
||||
|
||||
private static string GetRandomLatinAmericanLocale(int? seed)
|
||||
{
|
||||
var locales = new[] { "es_MX", "pt_BR", "es" };
|
||||
return PickLocale(locales, seed);
|
||||
}
|
||||
|
||||
private static string GetRandomMiddleEastLocale(int? seed)
|
||||
{
|
||||
// Bogus has limited Middle East support; use available Arabic/Turkish locales
|
||||
var locales = new[] { "ar", "tr", "fa" };
|
||||
return PickLocale(locales, seed);
|
||||
}
|
||||
|
||||
private static string GetRandomAfricanLocale(int? seed)
|
||||
{
|
||||
// Bogus has limited African support; use South African English and French (West Africa)
|
||||
var locales = new[] { "en_ZA", "fr" };
|
||||
return PickLocale(locales, seed);
|
||||
}
|
||||
|
||||
private static string PickLocale(string[] locales, int? seed)
|
||||
{
|
||||
var random = seed.HasValue ? new Random(seed.Value) : Random.Shared;
|
||||
return locales[random.Next(locales.Length)];
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,13 @@ namespace Bit.Seeder.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Generates deterministic usernames for companies using configurable patterns.
|
||||
/// Uses Bogus library for locale-aware name generation while maintaining determinism
|
||||
/// through pre-generated arrays indexed by a seed.
|
||||
/// </summary>
|
||||
internal sealed class UsernameGenerator
|
||||
internal sealed class CipherUsernameGenerator
|
||||
{
|
||||
private const int _namePoolSize = 1500;
|
||||
|
||||
private readonly Random _random;
|
||||
|
||||
private readonly UsernamePattern _pattern;
|
||||
@@ -15,7 +19,7 @@ internal sealed class UsernameGenerator
|
||||
|
||||
private readonly string[] _lastNames;
|
||||
|
||||
public UsernameGenerator(
|
||||
public CipherUsernameGenerator(
|
||||
int seed,
|
||||
UsernamePatternType patternType = UsernamePatternType.FirstDotLast,
|
||||
GeographicRegion? region = null)
|
||||
@@ -23,12 +27,10 @@ internal sealed class UsernameGenerator
|
||||
_random = new Random(seed);
|
||||
_pattern = UsernamePatterns.GetPattern(patternType);
|
||||
|
||||
(_firstNames, _lastNames) = region switch
|
||||
{
|
||||
GeographicRegion.NorthAmerica => (Names.UsFirstNames, Names.UsLastNames),
|
||||
GeographicRegion.Europe => (Names.EuropeanFirstNames, Names.EuropeanLastNames),
|
||||
_ => (Names.AllFirstNames, Names.AllLastNames)
|
||||
};
|
||||
// Pre-generate arrays from Bogus for deterministic index-based access
|
||||
var provider = new BogusNameProvider(region ?? GeographicRegion.Global, seed);
|
||||
_firstNames = Enumerable.Range(0, _namePoolSize).Select(_ => provider.FirstName()).ToArray();
|
||||
_lastNames = Enumerable.Range(0, _namePoolSize).Select(_ => provider.LastName()).ToArray();
|
||||
}
|
||||
|
||||
public string Generate(Company company)
|
||||
@@ -1,80 +0,0 @@
|
||||
namespace Bit.Seeder.Data;
|
||||
|
||||
/// <summary>
|
||||
/// First and last names organized by region for username generation.
|
||||
/// Add new regions by creating arrays and including them in the All* properties.
|
||||
/// </summary>
|
||||
internal static class Names
|
||||
{
|
||||
public static readonly string[] UsFirstNames =
|
||||
[
|
||||
// Male
|
||||
"James", "Robert", "John", "Michael", "David", "William", "Richard", "Joseph", "Thomas", "Christopher",
|
||||
"Charles", "Daniel", "Matthew", "Anthony", "Mark", "Donald", "Steven", "Paul", "Andrew", "Joshua",
|
||||
"Kenneth", "Kevin", "Brian", "George", "Timothy", "Ronald", "Edward", "Jason", "Jeffrey", "Ryan",
|
||||
"Jacob", "Gary", "Nicholas", "Eric", "Jonathan", "Stephen", "Larry", "Justin", "Scott", "Brandon",
|
||||
"Benjamin", "Samuel", "Raymond", "Gregory", "Frank", "Alexander", "Patrick", "Jack", "Dennis", "Jerry",
|
||||
// Female
|
||||
"Mary", "Patricia", "Jennifer", "Linda", "Barbara", "Elizabeth", "Susan", "Jessica", "Sarah", "Karen",
|
||||
"Lisa", "Nancy", "Betty", "Margaret", "Sandra", "Ashley", "Kimberly", "Emily", "Donna", "Michelle",
|
||||
"Dorothy", "Carol", "Amanda", "Melissa", "Deborah", "Stephanie", "Rebecca", "Sharon", "Laura", "Cynthia",
|
||||
"Kathleen", "Amy", "Angela", "Shirley", "Anna", "Brenda", "Pamela", "Emma", "Nicole", "Helen",
|
||||
"Samantha", "Katherine", "Christine", "Debra", "Rachel", "Carolyn", "Janet", "Catherine", "Maria", "Heather"
|
||||
];
|
||||
|
||||
public static readonly string[] UsLastNames =
|
||||
[
|
||||
"Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis", "Rodriguez", "Martinez",
|
||||
"Hernandez", "Lopez", "Gonzalez", "Wilson", "Anderson", "Thomas", "Taylor", "Moore", "Jackson", "Martin",
|
||||
"Lee", "Perez", "Thompson", "White", "Harris", "Sanchez", "Clark", "Ramirez", "Lewis", "Robinson",
|
||||
"Walker", "Young", "Allen", "King", "Wright", "Scott", "Torres", "Nguyen", "Hill", "Flores",
|
||||
"Green", "Adams", "Nelson", "Baker", "Hall", "Rivera", "Campbell", "Mitchell", "Carter", "Roberts",
|
||||
"Gomez", "Phillips", "Evans", "Turner", "Diaz", "Parker", "Cruz", "Edwards", "Collins", "Reyes",
|
||||
"Stewart", "Morris", "Morales", "Murphy", "Cook", "Rogers", "Gutierrez", "Ortiz", "Morgan", "Cooper",
|
||||
"Peterson", "Bailey", "Reed", "Kelly", "Howard", "Ramos", "Kim", "Cox", "Ward", "Richardson",
|
||||
"Watson", "Brooks", "Chavez", "Wood", "James", "Bennett", "Gray", "Mendoza", "Ruiz", "Hughes",
|
||||
"Price", "Alvarez", "Castillo", "Sanders", "Patel", "Myers", "Long", "Ross", "Foster", "Jimenez"
|
||||
];
|
||||
|
||||
public static readonly string[] EuropeanFirstNames =
|
||||
[
|
||||
// British
|
||||
"Oliver", "George", "Harry", "Jack", "Charlie", "Thomas", "Oscar", "William", "James", "Henry",
|
||||
"Olivia", "Amelia", "Isla", "Ava", "Emily", "Sophie", "Grace", "Mia", "Poppy", "Ella",
|
||||
// German
|
||||
"Maximilian", "Alexander", "Paul", "Leon", "Lukas", "Felix", "Noah", "Elias", "Ben", "Finn",
|
||||
"Emma", "Hannah", "Mia", "Sofia", "Anna", "Emilia", "Lena", "Marie", "Lea", "Clara",
|
||||
// French
|
||||
"Gabriel", "Raphael", "Leo", "Louis", "Lucas", "Adam", "Hugo", "Jules", "Arthur", "Nathan",
|
||||
"Louise", "Alice", "Chloe", "Ines", "Lea", "Manon", "Rose", "Anna", "Lina", "Mila",
|
||||
// Spanish
|
||||
"Hugo", "Martin", "Lucas", "Daniel", "Pablo", "Alejandro", "Adrian", "Alvaro", "David", "Mario",
|
||||
"Lucia", "Sofia", "Maria", "Martina", "Paula", "Julia", "Daniela", "Valeria", "Alba", "Emma",
|
||||
// Italian
|
||||
"Leonardo", "Francesco", "Alessandro", "Lorenzo", "Mattia", "Andrea", "Gabriele", "Riccardo", "Tommaso", "Edoardo",
|
||||
"Sofia", "Giulia", "Aurora", "Alice", "Ginevra", "Emma", "Giorgia", "Greta", "Beatrice", "Anna"
|
||||
];
|
||||
|
||||
public static readonly string[] EuropeanLastNames =
|
||||
[
|
||||
// British
|
||||
"Smith", "Jones", "Williams", "Brown", "Taylor", "Davies", "Wilson", "Evans", "Thomas", "Johnson",
|
||||
"Roberts", "Walker", "Wright", "Robinson", "Thompson", "White", "Hughes", "Edwards", "Green", "Hall",
|
||||
// German
|
||||
"Mueller", "Schmidt", "Schneider", "Fischer", "Weber", "Meyer", "Wagner", "Becker", "Schulz", "Hoffmann",
|
||||
"Schaefer", "Koch", "Bauer", "Richter", "Klein", "Wolf", "Schroeder", "Neumann", "Schwarz", "Zimmermann",
|
||||
// French
|
||||
"Martin", "Bernard", "Dubois", "Thomas", "Robert", "Richard", "Petit", "Durand", "Leroy", "Moreau",
|
||||
"Simon", "Laurent", "Lefebvre", "Michel", "Garcia", "David", "Bertrand", "Roux", "Vincent", "Fournier",
|
||||
// Spanish
|
||||
"Garcia", "Rodriguez", "Martinez", "Lopez", "Sanchez", "Gonzalez", "Perez", "Martin", "Gomez", "Ruiz",
|
||||
"Hernandez", "Jimenez", "Diaz", "Moreno", "Alvarez", "Munoz", "Romero", "Alonso", "Gutierrez", "Navarro",
|
||||
// Italian
|
||||
"Rossi", "Russo", "Ferrari", "Esposito", "Bianchi", "Romano", "Colombo", "Ricci", "Marino", "Greco",
|
||||
"Bruno", "Gallo", "Conti", "DeLuca", "Costa", "Giordano", "Mancini", "Rizzo", "Lombardi", "Moretti"
|
||||
];
|
||||
|
||||
public static readonly string[] AllFirstNames = [.. UsFirstNames, .. EuropeanFirstNames];
|
||||
|
||||
public static readonly string[] AllLastNames = [.. UsLastNames, .. EuropeanLastNames];
|
||||
}
|
||||
@@ -54,4 +54,10 @@ public class OrganizationVaultOptions
|
||||
/// (25% VeryWeak, 30% Weak, 25% Fair, 15% Strong, 5% VeryStrong).
|
||||
/// </summary>
|
||||
public PasswordStrength PasswordStrength { get; init; } = PasswordStrength.Realistic;
|
||||
|
||||
/// <summary>
|
||||
/// Geographic region for culturally-appropriate name generation in cipher usernames.
|
||||
/// Defaults to Global (mixed locales from all regions).
|
||||
/// </summary>
|
||||
public GeographicRegion? Region { get; init; }
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ public class OrganizationWithVaultRecipe(
|
||||
|
||||
var collectionIds = CreateCollections(organization.Id, orgKeys.Key, options.StructureModel, confirmedOrgUserIds);
|
||||
CreateGroups(organization.Id, options.Groups, confirmedOrgUserIds);
|
||||
CreateCiphers(organization.Id, orgKeys.Key, collectionIds, options.Ciphers, options.UsernamePattern, options.PasswordStrength);
|
||||
CreateCiphers(organization.Id, orgKeys.Key, collectionIds, options.Ciphers, options.UsernamePattern, options.PasswordStrength, options.Region);
|
||||
|
||||
return organization.Id;
|
||||
}
|
||||
@@ -170,10 +170,11 @@ public class OrganizationWithVaultRecipe(
|
||||
List<Guid> collectionIds,
|
||||
int cipherCount,
|
||||
UsernamePatternType usernamePattern,
|
||||
PasswordStrength passwordStrength)
|
||||
PasswordStrength passwordStrength,
|
||||
GeographicRegion? region)
|
||||
{
|
||||
var companies = Companies.All;
|
||||
var usernameGenerator = new UsernameGenerator(organizationId.GetHashCode(), usernamePattern);
|
||||
var usernameGenerator = new CipherUsernameGenerator(organizationId.GetHashCode(), usernamePattern, region);
|
||||
|
||||
var cipherList = Enumerable.Range(0, cipherCount)
|
||||
.Select(i =>
|
||||
|
||||
@@ -19,6 +19,10 @@
|
||||
<ProjectReference Include="..\RustSdk\RustSdk.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Bogus" Version="35.*" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="..\..\Program.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
Reference in New Issue
Block a user