1
0
mirror of https://github.com/bitwarden/server.git synced 2025-05-23 04:21:05 -05:00

deleted backup files

This commit is contained in:
Robert Y 2025-03-13 16:51:40 -06:00
parent 7877042c32
commit 44f4efffaf
15 changed files with 342 additions and 759 deletions

View File

@ -1,119 +1,117 @@
using Microsoft.Extensions.DependencyInjection; using Bit.Core.Enums;
using Microsoft.Extensions.Logging;
using Bit.Seeder.Services; using Bit.Seeder.Services;
using Bit.Seeder.Settings; using Bit.Seeder.Settings;
using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.DataProtection;
using Bit.Core;
using Microsoft.Extensions.Configuration;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Bit.Core.Enums; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Bit.Seeder.Commands namespace Bit.Seeder.Commands;
public class ExtractCommand
{ {
public class ExtractCommand public bool Execute(string seedName)
{ {
public bool Execute(string seedName) try
{ {
try // Create service provider with necessary services
{ var services = new ServiceCollection();
// Create service provider with necessary services ConfigureServices(services);
var services = new ServiceCollection(); var serviceProvider = services.BuildServiceProvider();
ConfigureServices(services);
var serviceProvider = services.BuildServiceProvider();
// Get the necessary services // Get the necessary services
var seederService = serviceProvider.GetRequiredService<ISeederService>(); var seederService = serviceProvider.GetRequiredService<ISeederService>();
var logger = serviceProvider.GetRequiredService<ILogger<ExtractCommand>>(); var logger = serviceProvider.GetRequiredService<ILogger<ExtractCommand>>();
logger.LogInformation($"Extracting data from database to seed: {seedName}"); logger.LogInformation($"Extracting data from database to seed: {seedName}");
// Execute the extract operation // Execute the extract operation
seederService.ExtractSeedsAsync(seedName).GetAwaiter().GetResult(); seederService.ExtractSeedsAsync(seedName).GetAwaiter().GetResult();
logger.LogInformation("Seed extraction completed successfully"); logger.LogInformation("Seed extraction completed successfully");
return true; return true;
}
catch (Exception ex)
{
Console.WriteLine($"Error extracting seeds: {ex.Message}");
if (ex.InnerException != null)
{
Console.WriteLine($"Inner exception: {ex.InnerException.Message}");
}
Console.WriteLine($"Stack trace: {ex.StackTrace}");
return false;
}
} }
catch (Exception ex)
private void ConfigureServices(ServiceCollection services)
{ {
// Add logging Console.WriteLine($"Error extracting seeds: {ex.Message}");
services.AddLogging(builder => if (ex.InnerException != null)
{ {
builder.AddConsole(); Console.WriteLine($"Inner exception: {ex.InnerException.Message}");
builder.SetMinimumLevel(LogLevel.Information); }
}); Console.WriteLine($"Stack trace: {ex.StackTrace}");
return false;
// Add JSON file configuration for other app settings not in GlobalSettings
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables()
.AddUserSecrets("Bit.Seeder")
.Build();
services.AddSingleton<IConfiguration>(configuration);
// Get global settings
var globalSettings = GlobalSettingsFactory.GlobalSettings;
services.AddSingleton(globalSettings);
// Configure database provider
var provider = globalSettings.DatabaseProvider.ToLowerInvariant() switch
{
"postgres" or "postgresql" => SupportedDatabaseProviders.Postgres,
"mysql" or "mariadb" => SupportedDatabaseProviders.MySql,
"sqlite" => SupportedDatabaseProviders.Sqlite,
_ => SupportedDatabaseProviders.SqlServer
};
var connectionString = provider switch
{
SupportedDatabaseProviders.Postgres => globalSettings.PostgreSql?.ConnectionString,
SupportedDatabaseProviders.MySql => globalSettings.MySql?.ConnectionString,
SupportedDatabaseProviders.Sqlite => globalSettings.Sqlite?.ConnectionString,
_ => globalSettings.SqlServer?.ConnectionString
};
// Register database context
services.AddDbContext<DatabaseContext>(options =>
{
switch (provider)
{
case SupportedDatabaseProviders.Postgres:
options.UseNpgsql(connectionString);
break;
case SupportedDatabaseProviders.MySql:
options.UseMySql(connectionString!, ServerVersion.AutoDetect(connectionString!));
break;
case SupportedDatabaseProviders.Sqlite:
options.UseSqlite(connectionString!);
break;
default:
options.UseSqlServer(connectionString!);
break;
}
});
// Add Data Protection services
services.AddDataProtection()
.SetApplicationName("Bitwarden");
// Register other services
services.AddTransient<ISeederService, SeederService>();
services.AddTransient<IDatabaseService, DatabaseService>();
services.AddTransient<IEncryptionService, EncryptionService>();
} }
} }
}
private void ConfigureServices(ServiceCollection services)
{
// Add logging
services.AddLogging(builder =>
{
builder.AddConsole();
builder.SetMinimumLevel(LogLevel.Information);
});
// Add JSON file configuration for other app settings not in GlobalSettings
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables()
.AddUserSecrets("Bit.Seeder")
.Build();
services.AddSingleton<IConfiguration>(configuration);
// Get global settings
var globalSettings = GlobalSettingsFactory.GlobalSettings;
services.AddSingleton(globalSettings);
// Configure database provider
var provider = globalSettings.DatabaseProvider.ToLowerInvariant() switch
{
"postgres" or "postgresql" => SupportedDatabaseProviders.Postgres,
"mysql" or "mariadb" => SupportedDatabaseProviders.MySql,
"sqlite" => SupportedDatabaseProviders.Sqlite,
_ => SupportedDatabaseProviders.SqlServer
};
var connectionString = provider switch
{
SupportedDatabaseProviders.Postgres => globalSettings.PostgreSql?.ConnectionString,
SupportedDatabaseProviders.MySql => globalSettings.MySql?.ConnectionString,
SupportedDatabaseProviders.Sqlite => globalSettings.Sqlite?.ConnectionString,
_ => globalSettings.SqlServer?.ConnectionString
};
// Register database context
services.AddDbContext<DatabaseContext>(options =>
{
switch (provider)
{
case SupportedDatabaseProviders.Postgres:
options.UseNpgsql(connectionString);
break;
case SupportedDatabaseProviders.MySql:
options.UseMySql(connectionString!, ServerVersion.AutoDetect(connectionString!));
break;
case SupportedDatabaseProviders.Sqlite:
options.UseSqlite(connectionString!);
break;
default:
options.UseSqlServer(connectionString!);
break;
}
});
// Add Data Protection services
services.AddDataProtection()
.SetApplicationName("Bitwarden");
// Register other services
services.AddTransient<ISeederService, SeederService>();
services.AddTransient<IDatabaseService, DatabaseService>();
services.AddTransient<IEncryptionService, EncryptionService>();
}
}

View File

@ -1,77 +1,75 @@
using Microsoft.Extensions.DependencyInjection; using Bit.Seeder.Services;
using Microsoft.Extensions.Logging;
using Bit.Seeder.Services;
using Bit.Seeder.Settings; using Bit.Seeder.Settings;
using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.DataProtection;
using Bit.Core; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Bit.Seeder.Commands namespace Bit.Seeder.Commands;
public class GenerateCommand
{ {
public class GenerateCommand public bool Execute(int users, int ciphersPerUser, string seedName, bool loadImmediately = false)
{ {
public bool Execute(int users, int ciphersPerUser, string seedName, bool loadImmediately = false) try
{ {
try // Create service provider with necessary services
var services = new ServiceCollection();
ConfigureServices(services);
var serviceProvider = services.BuildServiceProvider();
// Get the seeder service
var seederService = serviceProvider.GetRequiredService<ISeederService>();
var logger = serviceProvider.GetRequiredService<ILogger<GenerateCommand>>();
logger.LogInformation($"Generating seeds for {users} users with {ciphersPerUser} ciphers per user");
logger.LogInformation($"Seed name: {seedName}");
// Execute the appropriate action based on whether we need to load immediately
if (loadImmediately)
{ {
// Create service provider with necessary services // Generate and load directly without creating intermediate files
var services = new ServiceCollection(); seederService.GenerateAndLoadSeedsAsync(users, ciphersPerUser, seedName).GetAwaiter().GetResult();
ConfigureServices(services); logger.LogInformation("Seeds generated and loaded directly to database");
var serviceProvider = services.BuildServiceProvider();
// Get the seeder service
var seederService = serviceProvider.GetRequiredService<ISeederService>();
var logger = serviceProvider.GetRequiredService<ILogger<GenerateCommand>>();
logger.LogInformation($"Generating seeds for {users} users with {ciphersPerUser} ciphers per user");
logger.LogInformation($"Seed name: {seedName}");
// Execute the appropriate action based on whether we need to load immediately
if (loadImmediately)
{
// Generate and load directly without creating intermediate files
seederService.GenerateAndLoadSeedsAsync(users, ciphersPerUser, seedName).GetAwaiter().GetResult();
logger.LogInformation("Seeds generated and loaded directly to database");
}
else
{
// Only generate seeds and save to files
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
seederService.GenerateSeedsAsync(users, ciphersPerUser, seedName).GetAwaiter().GetResult();
logger.LogInformation("Seed generation completed successfully");
}
return true;
} }
catch (Exception ex) else
{ {
Console.WriteLine($"Error generating seeds: {ex.Message}"); // Only generate seeds and save to files
if (ex.InnerException != null) string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
{ seederService.GenerateSeedsAsync(users, ciphersPerUser, seedName).GetAwaiter().GetResult();
Console.WriteLine($"Inner exception: {ex.InnerException.Message}"); logger.LogInformation("Seed generation completed successfully");
}
Console.WriteLine($"Stack trace: {ex.StackTrace}");
return false;
} }
return true;
} }
catch (Exception ex)
private void ConfigureServices(ServiceCollection services)
{ {
// Load configuration using the GlobalSettingsFactory Console.WriteLine($"Error generating seeds: {ex.Message}");
var globalSettings = GlobalSettingsFactory.GlobalSettings; if (ex.InnerException != null)
{
// Register services Console.WriteLine($"Inner exception: {ex.InnerException.Message}");
services.AddLogging(builder => builder.AddConsole()); }
services.AddSingleton(globalSettings); Console.WriteLine($"Stack trace: {ex.StackTrace}");
return false;
// Add Data Protection services
services.AddDataProtection()
.SetApplicationName("Bitwarden");
// Register DatabaseContext and services
services.AddTransient<ISeederService, SeederService>();
services.AddTransient<IDatabaseService, DatabaseService>();
services.AddTransient<IEncryptionService, EncryptionService>();
services.AddDbContext<DatabaseContext>();
} }
} }
}
private 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");
// Register DatabaseContext and services
services.AddTransient<ISeederService, SeederService>();
services.AddTransient<IDatabaseService, DatabaseService>();
services.AddTransient<IEncryptionService, EncryptionService>();
services.AddDbContext<DatabaseContext>();
}
}

View File

@ -1,87 +1,85 @@
using Microsoft.Extensions.DependencyInjection; using Bit.Seeder.Services;
using Microsoft.Extensions.Logging;
using Bit.Seeder.Services;
using Bit.Seeder.Settings; using Bit.Seeder.Settings;
using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.DataProtection;
using Bit.Core; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Bit.Seeder.Commands namespace Bit.Seeder.Commands;
public class LoadCommand
{ {
public class LoadCommand public bool Execute(string seedName, string? timestamp = null, bool dryRun = false)
{ {
public bool Execute(string seedName, string? timestamp = null, bool dryRun = false) try
{ {
try // Create service provider with necessary services
var services = new ServiceCollection();
ConfigureServices(services);
var serviceProvider = services.BuildServiceProvider();
// Get the necessary services
var seederService = serviceProvider.GetRequiredService<ISeederService>();
var databaseService = serviceProvider.GetRequiredService<IDatabaseService>();
var logger = serviceProvider.GetRequiredService<ILogger<LoadCommand>>();
logger.LogInformation($"Loading seeds named: {seedName}");
if (!string.IsNullOrEmpty(timestamp))
{ {
// Create service provider with necessary services logger.LogInformation($"Using specific timestamp: {timestamp}");
var services = new ServiceCollection(); }
ConfigureServices(services);
var serviceProvider = services.BuildServiceProvider();
// Get the necessary services if (dryRun)
var seederService = serviceProvider.GetRequiredService<ISeederService>(); {
var databaseService = serviceProvider.GetRequiredService<IDatabaseService>(); logger.LogInformation("DRY RUN: No actual changes will be made to the database");
var logger = serviceProvider.GetRequiredService<ILogger<LoadCommand>>(); // Perform validation here if needed
logger.LogInformation("Seed loading validation completed");
logger.LogInformation($"Loading seeds named: {seedName}");
if (!string.IsNullOrEmpty(timestamp))
{
logger.LogInformation($"Using specific timestamp: {timestamp}");
}
if (dryRun)
{
logger.LogInformation("DRY RUN: No actual changes will be made to the database");
// Perform validation here if needed
logger.LogInformation("Seed loading validation completed");
return true;
}
// Execute the load operation
seederService.LoadSeedsAsync(seedName, timestamp).GetAwaiter().GetResult();
logger.LogInformation("Seed loading completed successfully");
return true; return true;
} }
catch (Exception ex)
{ // Execute the load operation
// Display the full exception details seederService.LoadSeedsAsync(seedName, timestamp).GetAwaiter().GetResult();
Console.WriteLine($"Error loading seeds: {ex.Message}");
logger.LogInformation("Seed loading completed successfully");
if (ex.InnerException != null) return true;
{
Console.WriteLine($"Inner exception: {ex.InnerException.Message}");
if (ex.InnerException.InnerException != null)
{
Console.WriteLine($"Inner inner exception: {ex.InnerException.InnerException.Message}");
}
}
Console.WriteLine($"Stack trace: {ex.StackTrace}");
return false;
}
} }
catch (Exception ex)
private void ConfigureServices(ServiceCollection services)
{ {
// Load configuration using the GlobalSettingsFactory // Display the full exception details
var globalSettings = GlobalSettingsFactory.GlobalSettings; Console.WriteLine($"Error loading seeds: {ex.Message}");
// Register services if (ex.InnerException != null)
services.AddLogging(builder => builder.AddConsole()); {
services.AddSingleton(globalSettings); Console.WriteLine($"Inner exception: {ex.InnerException.Message}");
// Add Data Protection services if (ex.InnerException.InnerException != null)
services.AddDataProtection() {
.SetApplicationName("Bitwarden"); Console.WriteLine($"Inner inner exception: {ex.InnerException.InnerException.Message}");
}
// Register DatabaseContext and services }
services.AddTransient<ISeederService, SeederService>();
services.AddTransient<IDatabaseService, DatabaseService>(); Console.WriteLine($"Stack trace: {ex.StackTrace}");
services.AddTransient<IEncryptionService, EncryptionService>();
services.AddDbContext<DatabaseContext>(); return false;
} }
} }
}
private 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");
// Register DatabaseContext and services
services.AddTransient<ISeederService, SeederService>();
services.AddTransient<IDatabaseService, DatabaseService>();
services.AddTransient<IEncryptionService, EncryptionService>();
services.AddDbContext<DatabaseContext>();
}
}

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<key id="c9676c44-16a7-4b38-b750-4ea5443f1b87" version="1">
<creationDate>2025-03-13T17:13:50.8174933Z</creationDate>
<activationDate>2025-03-13T17:13:50.8130955Z</activationDate>
<expirationDate>2025-06-11T17:13:50.8130955Z</expirationDate>
<descriptor deserializerType="Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60">
<descriptor>
<encryption algorithm="AES_256_CBC" />
<validation algorithm="HMACSHA256" />
<masterKey p4:requiresEncryption="true" xmlns:p4="http://schemas.asp.net/2015/03/dataProtection">
<!-- Warning: the key below is in an unencrypted form. -->
<value>VCxxS1xKEWQ9+XRXgMchHML7POXcwRkCHswj7JMjjN36RkERENo+ky/1mazB4RZ6BjjwXsAyjhSz2eGts/0BnQ==</value>
</masterKey>
</descriptor>
</descriptor>
</key>

View File

@ -1,8 +1,7 @@
using Microsoft.EntityFrameworkCore; using Bit.Core.Entities;
using Bit.Core.Entities;
using Bit.Core.Vault.Entities; using Bit.Core.Vault.Entities;
using Bit.Core.Enums;
using Bit.Seeder.Settings; using Bit.Seeder.Settings;
using Microsoft.EntityFrameworkCore;
namespace Bit.Seeder.Services; namespace Bit.Seeder.Services;
@ -22,13 +21,13 @@ public class DatabaseContext : DbContext
{ {
var provider = _globalSettings.DatabaseProvider ?? string.Empty; var provider = _globalSettings.DatabaseProvider ?? string.Empty;
Console.WriteLine($"Database Provider: '{provider}'"); Console.WriteLine($"Database Provider: '{provider}'");
// Output all available connection strings for debugging // Output all available connection strings for debugging
Console.WriteLine($"SqlServer ConnectionString available: {!string.IsNullOrEmpty(_globalSettings.SqlServer?.ConnectionString)}"); Console.WriteLine($"SqlServer ConnectionString available: {!string.IsNullOrEmpty(_globalSettings.SqlServer?.ConnectionString)}");
Console.WriteLine($"PostgreSql ConnectionString available: {!string.IsNullOrEmpty(_globalSettings.PostgreSql?.ConnectionString)}"); Console.WriteLine($"PostgreSql ConnectionString available: {!string.IsNullOrEmpty(_globalSettings.PostgreSql?.ConnectionString)}");
Console.WriteLine($"MySql ConnectionString available: {!string.IsNullOrEmpty(_globalSettings.MySql?.ConnectionString)}"); Console.WriteLine($"MySql ConnectionString available: {!string.IsNullOrEmpty(_globalSettings.MySql?.ConnectionString)}");
Console.WriteLine($"Sqlite ConnectionString available: {!string.IsNullOrEmpty(_globalSettings.Sqlite?.ConnectionString)}"); Console.WriteLine($"Sqlite ConnectionString available: {!string.IsNullOrEmpty(_globalSettings.Sqlite?.ConnectionString)}");
var connectionString = _globalSettings.DatabaseProvider switch var connectionString = _globalSettings.DatabaseProvider switch
{ {
"postgres" => _globalSettings.PostgreSql?.ConnectionString, "postgres" => _globalSettings.PostgreSql?.ConnectionString,
@ -73,4 +72,4 @@ public class DatabaseContext : DbContext
entity.Property(e => e.Kdf).HasConversion<int>(); entity.Property(e => e.Kdf).HasConversion<int>();
}); });
} }
} }

View File

@ -1,7 +1,7 @@
using Bit.Core.Entities;
using Bit.Core.Vault.Entities;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Bit.Core.Entities;
using Bit.Core.Vault.Entities;
namespace Bit.Seeder.Services; namespace Bit.Seeder.Services;

View File

@ -1,8 +1,8 @@
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.DataProtection;
using Bit.Core; using Bit.Core;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.Logging;
namespace Bit.Seeder.Services; namespace Bit.Seeder.Services;
@ -22,11 +22,11 @@ public class EncryptionService : IEncryptionService
public string HashPassword(string password) public string HashPassword(string password)
{ {
_logger.LogDebug("Hashing password using Data Protection"); _logger.LogDebug("Hashing password using Data Protection");
// The real Bitwarden implementation uses BCrypt first and then protects that value // The real Bitwarden implementation uses BCrypt first and then protects that value
// For simplicity we're just protecting the raw password since this is only for seeding test data // For simplicity we're just protecting the raw password since this is only for seeding test data
var protectedPassword = _dataProtector.Protect(password); var protectedPassword = _dataProtector.Protect(password);
// Prefix with "P|" to match Bitwarden's password format // Prefix with "P|" to match Bitwarden's password format
return string.Concat(Constants.DatabaseFieldProtectedPrefix, protectedPassword); return string.Concat(Constants.DatabaseFieldProtectedPrefix, protectedPassword);
} }
@ -34,33 +34,33 @@ public class EncryptionService : IEncryptionService
public byte[] DeriveKey(string password, string salt) public byte[] DeriveKey(string password, string salt)
{ {
_logger.LogDebug("Deriving key"); _logger.LogDebug("Deriving key");
using var pbkdf2 = new Rfc2898DeriveBytes( using var pbkdf2 = new Rfc2898DeriveBytes(
Encoding.UTF8.GetBytes(password), Encoding.UTF8.GetBytes(password),
Encoding.UTF8.GetBytes(salt), Encoding.UTF8.GetBytes(salt),
100000, 100000,
HashAlgorithmName.SHA256); HashAlgorithmName.SHA256);
return pbkdf2.GetBytes(32); return pbkdf2.GetBytes(32);
} }
public string EncryptString(string plaintext, byte[] key) public string EncryptString(string plaintext, byte[] key)
{ {
_logger.LogDebug("Encrypting string"); _logger.LogDebug("Encrypting string");
using var aes = Aes.Create(); using var aes = Aes.Create();
aes.Key = key; aes.Key = key;
aes.GenerateIV(); aes.GenerateIV();
using var encryptor = aes.CreateEncryptor(); using var encryptor = aes.CreateEncryptor();
var plaintextBytes = Encoding.UTF8.GetBytes(plaintext); var plaintextBytes = Encoding.UTF8.GetBytes(plaintext);
var cipherBytes = encryptor.TransformFinalBlock(plaintextBytes, 0, plaintextBytes.Length); var cipherBytes = encryptor.TransformFinalBlock(plaintextBytes, 0, plaintextBytes.Length);
var result = new byte[aes.IV.Length + cipherBytes.Length]; var result = new byte[aes.IV.Length + cipherBytes.Length];
Buffer.BlockCopy(aes.IV, 0, result, 0, aes.IV.Length); Buffer.BlockCopy(aes.IV, 0, result, 0, aes.IV.Length);
Buffer.BlockCopy(cipherBytes, 0, result, aes.IV.Length, cipherBytes.Length); Buffer.BlockCopy(cipherBytes, 0, result, aes.IV.Length, cipherBytes.Length);
return Convert.ToBase64String(result); return Convert.ToBase64String(result);
} }
} }

View File

@ -1,4 +1,4 @@
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Vault.Entities; using Bit.Core.Vault.Entities;
namespace Bit.Seeder.Services; namespace Bit.Seeder.Services;

View File

@ -1,11 +0,0 @@
using Bit.Core.Entities;
using Bit.Core.Vault.Entities;
namespace Bit.Seeder.Services;
public interface IDatabaseService
{
Task ClearDatabaseAsync();
Task SaveUsersAsync(IEnumerable<User> users);
Task SaveCiphersAsync(IEnumerable<Cipher> ciphers);
}

View File

@ -1,8 +1,8 @@
namespace Bit.Seeder.Services; namespace Bit.Seeder.Services;
public interface IEncryptionService public interface IEncryptionService
{ {
string HashPassword(string password); string HashPassword(string password);
byte[] DeriveKey(string password, string salt); byte[] DeriveKey(string password, string salt);
string EncryptString(string plaintext, byte[] key); string EncryptString(string plaintext, byte[] key);
} }

View File

@ -1,4 +1,4 @@
namespace Bit.Seeder.Services; namespace Bit.Seeder.Services;
public interface ISeederService public interface ISeederService
{ {

View File

@ -1,4 +1,4 @@
using System.Text.Json; using System.Text.Json;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Vault.Entities; using Bit.Core.Vault.Entities;
@ -25,7 +25,7 @@ public class SeederService : ISeederService
_databaseService = databaseService; _databaseService = databaseService;
_logger = logger; _logger = logger;
_faker = new Faker(); _faker = new Faker();
// Set the random seed to ensure reproducible data // Set the random seed to ensure reproducible data
Randomizer.Seed = new Random(42); Randomizer.Seed = new Random(42);
} }
@ -33,34 +33,34 @@ public class SeederService : ISeederService
public async Task GenerateSeedsAsync(int userCount, int ciphersPerUser, string outputName) public async Task GenerateSeedsAsync(int userCount, int ciphersPerUser, string outputName)
{ {
_logger.LogInformation("Generating seeds: {UserCount} users with {CiphersPerUser} ciphers each", userCount, ciphersPerUser); _logger.LogInformation("Generating seeds: {UserCount} users with {CiphersPerUser} ciphers each", userCount, ciphersPerUser);
// Create timestamped folder under a named folder in seeds directory // Create timestamped folder under a named folder in seeds directory
var seedsBaseDir = Path.Combine(Directory.GetCurrentDirectory(), "seeds"); var seedsBaseDir = Path.Combine(Directory.GetCurrentDirectory(), "seeds");
Directory.CreateDirectory(seedsBaseDir); Directory.CreateDirectory(seedsBaseDir);
var namedDir = Path.Combine(seedsBaseDir, outputName); var namedDir = Path.Combine(seedsBaseDir, outputName);
Directory.CreateDirectory(namedDir); Directory.CreateDirectory(namedDir);
var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss"); var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
var outputDir = Path.Combine(namedDir, timestamp); var outputDir = Path.Combine(namedDir, timestamp);
Directory.CreateDirectory(outputDir); Directory.CreateDirectory(outputDir);
// Create users and ciphers subdirectories // Create users and ciphers subdirectories
Directory.CreateDirectory(Path.Combine(outputDir, "users")); Directory.CreateDirectory(Path.Combine(outputDir, "users"));
Directory.CreateDirectory(Path.Combine(outputDir, "ciphers")); Directory.CreateDirectory(Path.Combine(outputDir, "ciphers"));
_logger.LogInformation("Seed output directory: {OutputDir}", outputDir); _logger.LogInformation("Seed output directory: {OutputDir}", outputDir);
// Generate users // Generate users
var users = GenerateUsers(userCount); var users = GenerateUsers(userCount);
// Generate ciphers for each user // Generate ciphers for each user
var allCiphers = new List<Cipher>(); var allCiphers = new List<Cipher>();
foreach (var user in users) foreach (var user in users)
{ {
var ciphers = GenerateCiphers(user, ciphersPerUser); var ciphers = GenerateCiphers(user, ciphersPerUser);
allCiphers.AddRange(ciphers); allCiphers.AddRange(ciphers);
// Save each user's ciphers to a file // Save each user's ciphers to a file
var cipherFilePath = Path.Combine(outputDir, "ciphers", $"{user.Id}.json"); var cipherFilePath = Path.Combine(outputDir, "ciphers", $"{user.Id}.json");
await File.WriteAllTextAsync(cipherFilePath, JsonSerializer.Serialize(ciphers, new JsonSerializerOptions await File.WriteAllTextAsync(cipherFilePath, JsonSerializer.Serialize(ciphers, new JsonSerializerOptions
@ -68,33 +68,33 @@ public class SeederService : ISeederService
WriteIndented = true WriteIndented = true
})); }));
} }
// Save users to a file // Save users to a file
var userFilePath = Path.Combine(outputDir, "users", "users.json"); var userFilePath = Path.Combine(outputDir, "users", "users.json");
await File.WriteAllTextAsync(userFilePath, JsonSerializer.Serialize(users, new JsonSerializerOptions await File.WriteAllTextAsync(userFilePath, JsonSerializer.Serialize(users, new JsonSerializerOptions
{ {
WriteIndented = true WriteIndented = true
})); }));
_logger.LogInformation("Successfully generated {UserCount} users and {CipherCount} ciphers", users.Count, allCiphers.Count); _logger.LogInformation("Successfully generated {UserCount} users and {CipherCount} ciphers", users.Count, allCiphers.Count);
_logger.LogInformation("Seed data saved to directory: {OutputDir}", outputDir); _logger.LogInformation("Seed data saved to directory: {OutputDir}", outputDir);
} }
public async Task GenerateAndLoadSeedsAsync(int userCount, int ciphersPerUser, string seedName) public async Task GenerateAndLoadSeedsAsync(int userCount, int ciphersPerUser, string seedName)
{ {
_logger.LogInformation("Generating and loading seeds directly: {UserCount} users with {CiphersPerUser} ciphers each", _logger.LogInformation("Generating and loading seeds directly: {UserCount} users with {CiphersPerUser} ciphers each",
userCount, ciphersPerUser); userCount, ciphersPerUser);
// Generate users directly without saving to files // Generate users directly without saving to files
var users = GenerateUsers(userCount); var users = GenerateUsers(userCount);
// Clear the database first // Clear the database first
await _databaseService.ClearDatabaseAsync(); await _databaseService.ClearDatabaseAsync();
// Save users to database // Save users to database
await _databaseService.SaveUsersAsync(users); await _databaseService.SaveUsersAsync(users);
_logger.LogInformation("Saved {UserCount} users directly to database", users.Count); _logger.LogInformation("Saved {UserCount} users directly to database", users.Count);
// Generate and save ciphers for each user // Generate and save ciphers for each user
int totalCiphers = 0; int totalCiphers = 0;
foreach (var user in users) foreach (var user in users)
@ -102,11 +102,11 @@ public class SeederService : ISeederService
var ciphers = GenerateCiphers(user, ciphersPerUser); var ciphers = GenerateCiphers(user, ciphersPerUser);
await _databaseService.SaveCiphersAsync(ciphers); await _databaseService.SaveCiphersAsync(ciphers);
totalCiphers += ciphers.Count; totalCiphers += ciphers.Count;
_logger.LogInformation("Saved {CipherCount} ciphers for user {UserId} directly to database", _logger.LogInformation("Saved {CipherCount} ciphers for user {UserId} directly to database",
ciphers.Count, user.Id); ciphers.Count, user.Id);
} }
_logger.LogInformation("Successfully generated and loaded {UserCount} users and {CipherCount} ciphers directly to database", _logger.LogInformation("Successfully generated and loaded {UserCount} users and {CipherCount} ciphers directly to database",
users.Count, totalCiphers); users.Count, totalCiphers);
} }
@ -115,15 +115,15 @@ public class SeederService : ISeederService
// Construct path to seeds directory // Construct path to seeds directory
var seedsBaseDir = Path.Combine(Directory.GetCurrentDirectory(), "seeds"); var seedsBaseDir = Path.Combine(Directory.GetCurrentDirectory(), "seeds");
var namedDir = Path.Combine(seedsBaseDir, seedName); var namedDir = Path.Combine(seedsBaseDir, seedName);
if (!Directory.Exists(namedDir)) if (!Directory.Exists(namedDir))
{ {
_logger.LogError("Seed directory not found: {SeedDir}", namedDir); _logger.LogError("Seed directory not found: {SeedDir}", namedDir);
return; return;
} }
string seedDir; string seedDir;
// If timestamp is specified, use that exact directory // If timestamp is specified, use that exact directory
if (!string.IsNullOrEmpty(timestamp)) if (!string.IsNullOrEmpty(timestamp))
{ {
@ -143,21 +143,21 @@ public class SeederService : ISeederService
_logger.LogError("No seed data found in directory: {SeedDir}", namedDir); _logger.LogError("No seed data found in directory: {SeedDir}", namedDir);
return; return;
} }
// Sort by directory name (which is a timestamp) in descending order // Sort by directory name (which is a timestamp) in descending order
Array.Sort(timestampDirs); Array.Sort(timestampDirs);
Array.Reverse(timestampDirs); Array.Reverse(timestampDirs);
// Use the most recent one // Use the most recent one
seedDir = timestampDirs[0]; seedDir = timestampDirs[0];
_logger.LogInformation("Using most recent seed data from: {SeedDir}", seedDir); _logger.LogInformation("Using most recent seed data from: {SeedDir}", seedDir);
} }
_logger.LogInformation("Loading seeds from directory: {SeedDir}", seedDir); _logger.LogInformation("Loading seeds from directory: {SeedDir}", seedDir);
// Clear database first // Clear database first
await _databaseService.ClearDatabaseAsync(); await _databaseService.ClearDatabaseAsync();
// Load users // Load users
var userFilePath = Path.Combine(seedDir, "users", "users.json"); var userFilePath = Path.Combine(seedDir, "users", "users.json");
if (!File.Exists(userFilePath)) if (!File.Exists(userFilePath))
@ -165,22 +165,22 @@ public class SeederService : ISeederService
_logger.LogError("User file not found: {UserFilePath}", userFilePath); _logger.LogError("User file not found: {UserFilePath}", userFilePath);
return; return;
} }
var userJson = await File.ReadAllTextAsync(userFilePath); var userJson = await File.ReadAllTextAsync(userFilePath);
var users = JsonSerializer.Deserialize<List<User>>(userJson, new JsonSerializerOptions var users = JsonSerializer.Deserialize<List<User>>(userJson, new JsonSerializerOptions
{ {
PropertyNameCaseInsensitive = true PropertyNameCaseInsensitive = true
}) ?? new List<User>(); }) ?? new List<User>();
if (users.Count == 0) if (users.Count == 0)
{ {
_logger.LogError("No users found in user file"); _logger.LogError("No users found in user file");
return; return;
} }
// Save users to database // Save users to database
await _databaseService.SaveUsersAsync(users); await _databaseService.SaveUsersAsync(users);
// Load and save ciphers for each user // Load and save ciphers for each user
var cipherDir = Path.Combine(seedDir, "ciphers"); var cipherDir = Path.Combine(seedDir, "ciphers");
if (!Directory.Exists(cipherDir)) if (!Directory.Exists(cipherDir))
@ -188,7 +188,7 @@ public class SeederService : ISeederService
_logger.LogError("Cipher directory not found: {CipherDir}", cipherDir); _logger.LogError("Cipher directory not found: {CipherDir}", cipherDir);
return; return;
} }
var cipherFiles = Directory.GetFiles(cipherDir, "*.json"); var cipherFiles = Directory.GetFiles(cipherDir, "*.json");
foreach (var cipherFile in cipherFiles) foreach (var cipherFile in cipherFiles)
{ {
@ -197,37 +197,37 @@ public class SeederService : ISeederService
{ {
PropertyNameCaseInsensitive = true PropertyNameCaseInsensitive = true
}) ?? new List<Cipher>(); }) ?? new List<Cipher>();
if (ciphers.Count > 0) if (ciphers.Count > 0)
{ {
await _databaseService.SaveCiphersAsync(ciphers); await _databaseService.SaveCiphersAsync(ciphers);
} }
} }
_logger.LogInformation("Successfully loaded seed data into database"); _logger.LogInformation("Successfully loaded seed data into database");
} }
public async Task ExtractSeedsAsync(string seedName) public async Task ExtractSeedsAsync(string seedName)
{ {
_logger.LogInformation("Extracting seed data from database for seed name: {SeedName}", seedName); _logger.LogInformation("Extracting seed data from database for seed name: {SeedName}", seedName);
// Create timestamped folder under a named folder in seeds directory // Create timestamped folder under a named folder in seeds directory
var seedsBaseDir = Path.Combine(Directory.GetCurrentDirectory(), "seeds"); var seedsBaseDir = Path.Combine(Directory.GetCurrentDirectory(), "seeds");
Directory.CreateDirectory(seedsBaseDir); Directory.CreateDirectory(seedsBaseDir);
var namedDir = Path.Combine(seedsBaseDir, seedName); var namedDir = Path.Combine(seedsBaseDir, seedName);
Directory.CreateDirectory(namedDir); Directory.CreateDirectory(namedDir);
var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss"); var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
var outputDir = Path.Combine(namedDir, timestamp); var outputDir = Path.Combine(namedDir, timestamp);
Directory.CreateDirectory(outputDir); Directory.CreateDirectory(outputDir);
// Create users and ciphers subdirectories // Create users and ciphers subdirectories
Directory.CreateDirectory(Path.Combine(outputDir, "users")); Directory.CreateDirectory(Path.Combine(outputDir, "users"));
Directory.CreateDirectory(Path.Combine(outputDir, "ciphers")); Directory.CreateDirectory(Path.Combine(outputDir, "ciphers"));
_logger.LogInformation("Seed output directory: {OutputDir}", outputDir); _logger.LogInformation("Seed output directory: {OutputDir}", outputDir);
try try
{ {
// Get all users from the database // Get all users from the database
@ -237,16 +237,16 @@ public class SeederService : ISeederService
_logger.LogWarning("No users found in the database"); _logger.LogWarning("No users found in the database");
return; return;
} }
_logger.LogInformation("Extracted {Count} users from database", users.Count); _logger.LogInformation("Extracted {Count} users from database", users.Count);
// Save users to a file // Save users to a file
var userFilePath = Path.Combine(outputDir, "users", "users.json"); var userFilePath = Path.Combine(outputDir, "users", "users.json");
await File.WriteAllTextAsync(userFilePath, JsonSerializer.Serialize(users, new JsonSerializerOptions await File.WriteAllTextAsync(userFilePath, JsonSerializer.Serialize(users, new JsonSerializerOptions
{ {
WriteIndented = true WriteIndented = true
})); }));
int totalCiphers = 0; int totalCiphers = 0;
// Get ciphers for each user // Get ciphers for each user
foreach (var user in users) foreach (var user in users)
@ -263,7 +263,7 @@ public class SeederService : ISeederService
totalCiphers += ciphers.Count; totalCiphers += ciphers.Count;
} }
} }
_logger.LogInformation("Successfully extracted {UserCount} users and {CipherCount} ciphers", users.Count, totalCiphers); _logger.LogInformation("Successfully extracted {UserCount} users and {CipherCount} ciphers", users.Count, totalCiphers);
_logger.LogInformation("Seed data saved to directory: {OutputDir}", outputDir); _logger.LogInformation("Seed data saved to directory: {OutputDir}", outputDir);
} }
@ -273,13 +273,13 @@ public class SeederService : ISeederService
throw; throw;
} }
} }
private List<User> GenerateUsers(int count) private List<User> GenerateUsers(int count)
{ {
_logger.LogInformation("Generating {Count} users", count); _logger.LogInformation("Generating {Count} users", count);
var users = new List<User>(); var users = new List<User>();
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
var userId = Guid.NewGuid(); var userId = Guid.NewGuid();
@ -288,7 +288,7 @@ public class SeederService : ISeederService
var masterPassword = _encryptionService.HashPassword(_defaultPassword); var masterPassword = _encryptionService.HashPassword(_defaultPassword);
var masterPasswordHint = "It's the word 'password'"; var masterPasswordHint = "It's the word 'password'";
var key = _encryptionService.DeriveKey(_defaultPassword, email); var key = _encryptionService.DeriveKey(_defaultPassword, email);
var user = new User var user = new User
{ {
Id = userId, Id = userId,
@ -305,35 +305,35 @@ public class SeederService : ISeederService
RevisionDate = DateTime.UtcNow, RevisionDate = DateTime.UtcNow,
Key = _encryptionService.EncryptString(Convert.ToBase64String(key), key) Key = _encryptionService.EncryptString(Convert.ToBase64String(key), key)
}; };
users.Add(user); users.Add(user);
} }
return users; return users;
} }
private List<Cipher> GenerateCiphers(User user, int count) private List<Cipher> GenerateCiphers(User user, int count)
{ {
_logger.LogInformation("Generating {Count} ciphers for user {UserId}", count, user.Id); _logger.LogInformation("Generating {Count} ciphers for user {UserId}", count, user.Id);
var ciphers = new List<Cipher>(); var ciphers = new List<Cipher>();
var key = _encryptionService.DeriveKey(_defaultPassword, user.Email); var key = _encryptionService.DeriveKey(_defaultPassword, user.Email);
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
var cipherId = Guid.NewGuid(); var cipherId = Guid.NewGuid();
CipherType type; CipherType type;
string name; string name;
string? notes = null; string? notes = null;
var typeRandom = _faker.Random.Int(1, 4); var typeRandom = _faker.Random.Int(1, 4);
type = (CipherType)typeRandom; type = (CipherType)typeRandom;
switch (type) switch (type)
{ {
case CipherType.Login: case CipherType.Login:
name = $"Login - {_faker.Internet.DomainName()}"; name = $"Login - {_faker.Internet.DomainName()}";
var loginData = new var loginData = new
{ {
Name = name, Name = name,
Notes = notes, Notes = notes,
@ -344,9 +344,9 @@ public class SeederService : ISeederService
new { Uri = $"https://{_faker.Internet.DomainName()}" } new { Uri = $"https://{_faker.Internet.DomainName()}" }
} }
}; };
var loginDataJson = JsonSerializer.Serialize(loginData); var loginDataJson = JsonSerializer.Serialize(loginData);
ciphers.Add(new Cipher ciphers.Add(new Cipher
{ {
Id = cipherId, Id = cipherId,
@ -358,19 +358,19 @@ public class SeederService : ISeederService
Reprompt = CipherRepromptType.None Reprompt = CipherRepromptType.None
}); });
break; break;
case CipherType.SecureNote: case CipherType.SecureNote:
name = $"Note - {_faker.Lorem.Word()}"; name = $"Note - {_faker.Lorem.Word()}";
notes = _faker.Lorem.Paragraph(); notes = _faker.Lorem.Paragraph();
var secureNoteData = new var secureNoteData = new
{ {
Name = name, Name = name,
Notes = notes, Notes = notes,
Type = 0 // Text Type = 0 // Text
}; };
var secureNoteDataJson = JsonSerializer.Serialize(secureNoteData); var secureNoteDataJson = JsonSerializer.Serialize(secureNoteData);
ciphers.Add(new Cipher ciphers.Add(new Cipher
{ {
Id = cipherId, Id = cipherId,
@ -382,10 +382,10 @@ public class SeederService : ISeederService
Reprompt = CipherRepromptType.None Reprompt = CipherRepromptType.None
}); });
break; break;
case CipherType.Card: case CipherType.Card:
name = $"Card - {_faker.Finance.CreditCardNumber().Substring(0, 4)}"; name = $"Card - {_faker.Finance.CreditCardNumber().Substring(0, 4)}";
var cardData = new var cardData = new
{ {
Name = name, Name = name,
Notes = notes, Notes = notes,
@ -395,9 +395,9 @@ public class SeederService : ISeederService
ExpYear = _faker.Random.Int(DateTime.UtcNow.Year, DateTime.UtcNow.Year + 10).ToString(), ExpYear = _faker.Random.Int(DateTime.UtcNow.Year, DateTime.UtcNow.Year + 10).ToString(),
Code = _faker.Random.Int(100, 999).ToString() Code = _faker.Random.Int(100, 999).ToString()
}; };
var cardDataJson = JsonSerializer.Serialize(cardData); var cardDataJson = JsonSerializer.Serialize(cardData);
ciphers.Add(new Cipher ciphers.Add(new Cipher
{ {
Id = cipherId, Id = cipherId,
@ -409,10 +409,10 @@ public class SeederService : ISeederService
Reprompt = CipherRepromptType.None Reprompt = CipherRepromptType.None
}); });
break; break;
case CipherType.Identity: case CipherType.Identity:
name = $"Identity - {_faker.Name.FullName()}"; name = $"Identity - {_faker.Name.FullName()}";
var identityData = new var identityData = new
{ {
Name = name, Name = name,
Notes = notes, Notes = notes,
@ -428,9 +428,9 @@ public class SeederService : ISeederService
PostalCode = _faker.Address.ZipCode(), PostalCode = _faker.Address.ZipCode(),
Country = _faker.Address.CountryCode() Country = _faker.Address.CountryCode()
}; };
var identityDataJson = JsonSerializer.Serialize(identityData); var identityDataJson = JsonSerializer.Serialize(identityData);
ciphers.Add(new Cipher ciphers.Add(new Cipher
{ {
Id = cipherId, Id = cipherId,
@ -444,7 +444,7 @@ public class SeederService : ISeederService
break; break;
} }
} }
return ciphers; return ciphers;
} }
} }

View File

@ -1,383 +0,0 @@
using System.Text.Json;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Vault.Entities;
using Bit.Core.Vault.Enums;
using Bogus;
using Microsoft.Extensions.Logging;
namespace Bit.Seeder.Services;
public class SeederService : ISeederService
{
private readonly IEncryptionService _encryptionService;
private readonly IDatabaseService _databaseService;
private readonly ILogger<SeederService> _logger;
private readonly Faker _faker;
private readonly string _defaultPassword = "password";
public SeederService(
IEncryptionService encryptionService,
IDatabaseService databaseService,
ILogger<SeederService> logger)
{
_encryptionService = encryptionService;
_databaseService = databaseService;
_logger = logger;
_faker = new Faker();
// Set the random seed to ensure reproducible data
Randomizer.Seed = new Random(42);
}
public async Task GenerateSeedsAsync(int userCount, int ciphersPerUser, string outputName)
{
_logger.LogInformation("Generating seeds: {UserCount} users with {CiphersPerUser} ciphers each", userCount, ciphersPerUser);
// Create timestamped folder under a named folder in seeds directory
var seedsBaseDir = Path.Combine(Directory.GetCurrentDirectory(), "seeds");
Directory.CreateDirectory(seedsBaseDir);
var namedDir = Path.Combine(seedsBaseDir, outputName);
Directory.CreateDirectory(namedDir);
var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
var outputDir = Path.Combine(namedDir, timestamp);
Directory.CreateDirectory(outputDir);
// Create users and ciphers subdirectories
Directory.CreateDirectory(Path.Combine(outputDir, "users"));
Directory.CreateDirectory(Path.Combine(outputDir, "ciphers"));
_logger.LogInformation("Seed output directory: {OutputDir}", outputDir);
// Generate users
var users = GenerateUsers(userCount);
// Generate ciphers for each user
var allCiphers = new List<Cipher>();
foreach (var user in users)
{
var ciphers = GenerateCiphers(user, ciphersPerUser);
allCiphers.AddRange(ciphers);
// Save each user's ciphers to a file
var cipherFilePath = Path.Combine(outputDir, "ciphers", $"{user.Id}.json");
await File.WriteAllTextAsync(cipherFilePath, JsonSerializer.Serialize(ciphers, new JsonSerializerOptions
{
WriteIndented = true
}));
}
// Save users to a file
var userFilePath = Path.Combine(outputDir, "users", "users.json");
await File.WriteAllTextAsync(userFilePath, JsonSerializer.Serialize(users, new JsonSerializerOptions
{
WriteIndented = true
}));
_logger.LogInformation("Successfully generated {UserCount} users and {CipherCount} ciphers", users.Count, allCiphers.Count);
_logger.LogInformation("Seed data saved to directory: {OutputDir}", outputDir);
}
public async Task GenerateAndLoadSeedsAsync(int userCount, int ciphersPerUser, string seedName)
{
_logger.LogInformation("Generating and loading seeds directly: {UserCount} users with {CiphersPerUser} ciphers each",
userCount, ciphersPerUser);
// Generate users directly without saving to files
var users = GenerateUsers(userCount);
// Clear the database first
await _databaseService.ClearDatabaseAsync();
// Save users to database
await _databaseService.SaveUsersAsync(users);
_logger.LogInformation("Saved {UserCount} users directly to database", users.Count);
// Generate and save ciphers for each user
int totalCiphers = 0;
foreach (var user in users)
{
var ciphers = GenerateCiphers(user, ciphersPerUser);
await _databaseService.SaveCiphersAsync(ciphers);
totalCiphers += ciphers.Count;
_logger.LogInformation("Saved {CipherCount} ciphers for user {UserId} directly to database",
ciphers.Count, user.Id);
}
_logger.LogInformation("Successfully generated and loaded {UserCount} users and {CipherCount} ciphers directly to database",
users.Count, totalCiphers);
}
public async Task LoadSeedsAsync(string seedName, string? timestamp = null)
{
// Construct path to seeds directory
var seedsBaseDir = Path.Combine(Directory.GetCurrentDirectory(), "seeds");
var namedDir = Path.Combine(seedsBaseDir, seedName);
if (!Directory.Exists(namedDir))
{
_logger.LogError("Seed directory not found: {SeedDir}", namedDir);
return;
}
string seedDir;
// If timestamp is specified, use that exact directory
if (!string.IsNullOrEmpty(timestamp))
{
seedDir = Path.Combine(namedDir, timestamp);
if (!Directory.Exists(seedDir))
{
_logger.LogError("Timestamp directory not found: {TimestampDir}", seedDir);
return;
}
}
else
{
// Otherwise, find the most recent timestamped directory
var timestampDirs = Directory.GetDirectories(namedDir);
if (timestampDirs.Length == 0)
{
_logger.LogError("No seed data found in directory: {SeedDir}", namedDir);
return;
}
// Sort by directory name (which is a timestamp) in descending order
Array.Sort(timestampDirs);
Array.Reverse(timestampDirs);
// Use the most recent one
seedDir = timestampDirs[0];
_logger.LogInformation("Using most recent seed data from: {SeedDir}", seedDir);
}
_logger.LogInformation("Loading seeds from directory: {SeedDir}", seedDir);
// Clear database first
await _databaseService.ClearDatabaseAsync();
// Load users
var userFilePath = Path.Combine(seedDir, "users", "users.json");
if (!File.Exists(userFilePath))
{
_logger.LogError("User file not found: {UserFilePath}", userFilePath);
return;
}
var userJson = await File.ReadAllTextAsync(userFilePath);
var users = JsonSerializer.Deserialize<List<User>>(userJson, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
}) ?? new List<User>();
if (users.Count == 0)
{
_logger.LogError("No users found in user file");
return;
}
// Save users to database
await _databaseService.SaveUsersAsync(users);
// Load and save ciphers for each user
var cipherDir = Path.Combine(seedDir, "ciphers");
if (!Directory.Exists(cipherDir))
{
_logger.LogError("Cipher directory not found: {CipherDir}", cipherDir);
return;
}
var cipherFiles = Directory.GetFiles(cipherDir, "*.json");
foreach (var cipherFile in cipherFiles)
{
var cipherJson = await File.ReadAllTextAsync(cipherFile);
var ciphers = JsonSerializer.Deserialize<List<Cipher>>(cipherJson, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
}) ?? new List<Cipher>();
if (ciphers.Count > 0)
{
await _databaseService.SaveCiphersAsync(ciphers);
}
}
_logger.LogInformation("Successfully loaded seed data into database");
}
private List<User> GenerateUsers(int count)
{
_logger.LogInformation("Generating {Count} users", count);
var users = new List<User>();
for (int i = 0; i < count; i++)
{
var userId = Guid.NewGuid();
var email = _faker.Internet.Email(provider: "example.com");
var name = _faker.Name.FullName();
var masterPassword = _encryptionService.HashPassword(_defaultPassword);
var masterPasswordHint = "It's the word 'password'";
var key = _encryptionService.DeriveKey(_defaultPassword, email);
var user = new User
{
Id = userId,
Email = email,
Name = name,
MasterPassword = masterPassword,
MasterPasswordHint = masterPasswordHint,
SecurityStamp = Guid.NewGuid().ToString(),
EmailVerified = true,
ApiKey = Guid.NewGuid().ToString("N").Substring(0, 30),
Kdf = KdfType.PBKDF2_SHA256,
KdfIterations = 100000,
CreationDate = DateTime.UtcNow,
RevisionDate = DateTime.UtcNow,
Key = _encryptionService.EncryptString(Convert.ToBase64String(key), key)
};
users.Add(user);
}
return users;
}
private List<Cipher> GenerateCiphers(User user, int count)
{
_logger.LogInformation("Generating {Count} ciphers for user {UserId}", count, user.Id);
var ciphers = new List<Cipher>();
var key = _encryptionService.DeriveKey(_defaultPassword, user.Email);
for (int i = 0; i < count; i++)
{
var cipherId = Guid.NewGuid();
CipherType type;
string name;
string? notes = null;
var typeRandom = _faker.Random.Int(1, 4);
type = (CipherType)typeRandom;
switch (type)
{
case CipherType.Login:
name = $"Login - {_faker.Internet.DomainName()}";
var loginData = new
{
Name = name,
Notes = notes,
Username = _faker.Internet.UserName(),
Password = _faker.Internet.Password(),
Uris = new[]
{
new { Uri = $"https://{_faker.Internet.DomainName()}" }
}
};
var loginDataJson = JsonSerializer.Serialize(loginData);
ciphers.Add(new Cipher
{
Id = cipherId,
UserId = user.Id,
Type = type,
Data = _encryptionService.EncryptString(loginDataJson, key),
CreationDate = DateTime.UtcNow,
RevisionDate = DateTime.UtcNow,
Reprompt = CipherRepromptType.None
});
break;
case CipherType.SecureNote:
name = $"Note - {_faker.Lorem.Word()}";
notes = _faker.Lorem.Paragraph();
var secureNoteData = new
{
Name = name,
Notes = notes,
Type = 0 // Text
};
var secureNoteDataJson = JsonSerializer.Serialize(secureNoteData);
ciphers.Add(new Cipher
{
Id = cipherId,
UserId = user.Id,
Type = type,
Data = _encryptionService.EncryptString(secureNoteDataJson, key),
CreationDate = DateTime.UtcNow,
RevisionDate = DateTime.UtcNow,
Reprompt = CipherRepromptType.None
});
break;
case CipherType.Card:
name = $"Card - {_faker.Finance.CreditCardNumber().Substring(0, 4)}";
var cardData = new
{
Name = name,
Notes = notes,
CardholderName = _faker.Name.FullName(),
Number = _faker.Finance.CreditCardNumber(),
ExpMonth = _faker.Random.Int(1, 12).ToString(),
ExpYear = _faker.Random.Int(DateTime.UtcNow.Year, DateTime.UtcNow.Year + 10).ToString(),
Code = _faker.Random.Int(100, 999).ToString()
};
var cardDataJson = JsonSerializer.Serialize(cardData);
ciphers.Add(new Cipher
{
Id = cipherId,
UserId = user.Id,
Type = type,
Data = _encryptionService.EncryptString(cardDataJson, key),
CreationDate = DateTime.UtcNow,
RevisionDate = DateTime.UtcNow,
Reprompt = CipherRepromptType.None
});
break;
case CipherType.Identity:
name = $"Identity - {_faker.Name.FullName()}";
var identityData = new
{
Name = name,
Notes = notes,
Title = _faker.Name.Prefix(),
FirstName = _faker.Name.FirstName(),
MiddleName = _faker.Name.FirstName(),
LastName = _faker.Name.LastName(),
Email = _faker.Internet.Email(),
Phone = _faker.Phone.PhoneNumber(),
Address1 = _faker.Address.StreetAddress(),
City = _faker.Address.City(),
State = _faker.Address.State(),
PostalCode = _faker.Address.ZipCode(),
Country = _faker.Address.CountryCode()
};
var identityDataJson = JsonSerializer.Serialize(identityData);
ciphers.Add(new Cipher
{
Id = cipherId,
UserId = user.Id,
Type = type,
Data = _encryptionService.EncryptString(identityDataJson, key),
CreationDate = DateTime.UtcNow,
RevisionDate = DateTime.UtcNow,
Reprompt = CipherRepromptType.None
});
break;
}
}
return ciphers;
}
}

View File

@ -1,4 +1,4 @@
namespace Bit.Seeder.Settings; namespace Bit.Seeder.Settings;
public class GlobalSettings public class GlobalSettings
{ {
@ -13,4 +13,4 @@ public class GlobalSettings
{ {
public string ConnectionString { get; set; } = string.Empty; public string ConnectionString { get; set; } = string.Empty;
} }
} }

View File

@ -1,4 +1,4 @@
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
namespace Bit.Seeder.Settings; namespace Bit.Seeder.Settings;
@ -14,7 +14,7 @@ public static class GlobalSettingsFactory
{ {
_globalSettings = LoadGlobalSettings(); _globalSettings = LoadGlobalSettings();
} }
return _globalSettings; return _globalSettings;
} }
} }
@ -22,7 +22,7 @@ public static class GlobalSettingsFactory
private static GlobalSettings LoadGlobalSettings() private static GlobalSettings LoadGlobalSettings()
{ {
Console.WriteLine("Loading global settings..."); Console.WriteLine("Loading global settings...");
var configBuilder = new ConfigurationBuilder() var configBuilder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory()) .SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
@ -32,7 +32,7 @@ public static class GlobalSettingsFactory
var configuration = configBuilder.Build(); var configuration = configBuilder.Build();
var globalSettingsSection = configuration.GetSection("globalSettings"); var globalSettingsSection = configuration.GetSection("globalSettings");
// Debug: Print all settings from globalSettings section // Debug: Print all settings from globalSettings section
foreach (var setting in globalSettingsSection.GetChildren()) foreach (var setting in globalSettingsSection.GetChildren())
{ {
@ -42,23 +42,23 @@ public static class GlobalSettingsFactory
Console.WriteLine($" - {setting.Key}.{child.Key}"); Console.WriteLine($" - {setting.Key}.{child.Key}");
} }
} }
var settings = new GlobalSettings(); var settings = new GlobalSettings();
globalSettingsSection.Bind(settings); globalSettingsSection.Bind(settings);
// Output the loaded settings // Output the loaded settings
Console.WriteLine($"Loaded DatabaseProvider: {settings.DatabaseProvider}"); Console.WriteLine($"Loaded DatabaseProvider: {settings.DatabaseProvider}");
Console.WriteLine($"PostgreSql settings loaded: {settings.PostgreSql != null}"); Console.WriteLine($"PostgreSql settings loaded: {settings.PostgreSql != null}");
Console.WriteLine($"SqlServer settings loaded: {settings.SqlServer != null}"); Console.WriteLine($"SqlServer settings loaded: {settings.SqlServer != null}");
Console.WriteLine($"MySql settings loaded: {settings.MySql != null}"); Console.WriteLine($"MySql settings loaded: {settings.MySql != null}");
Console.WriteLine($"Sqlite settings loaded: {settings.Sqlite != null}"); Console.WriteLine($"Sqlite settings loaded: {settings.Sqlite != null}");
// Check for case sensitivity issue with PostgreSql/postgresql keys // Check for case sensitivity issue with PostgreSql/postgresql keys
var postgresqlValue = globalSettingsSection.GetSection("postgresql")?.Value; var postgresqlValue = globalSettingsSection.GetSection("postgresql")?.Value;
var postgreSqlValue = globalSettingsSection.GetSection("postgreSql")?.Value; var postgreSqlValue = globalSettingsSection.GetSection("postgreSql")?.Value;
Console.WriteLine($"Raw check - postgresql setting exists: {postgresqlValue != null}"); Console.WriteLine($"Raw check - postgresql setting exists: {postgresqlValue != null}");
Console.WriteLine($"Raw check - postgreSql setting exists: {postgreSqlValue != null}"); Console.WriteLine($"Raw check - postgreSql setting exists: {postgreSqlValue != null}");
return settings; return settings;
} }
} }
@ -71,7 +71,7 @@ public class GlobalSettingsFactoryWithArgs
public GlobalSettingsFactoryWithArgs(string[] args) public GlobalSettingsFactoryWithArgs(string[] args)
{ {
GlobalSettings = new GlobalSettings(); GlobalSettings = new GlobalSettings();
var config = new ConfigurationBuilder() var config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory()) .SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
@ -83,4 +83,4 @@ public class GlobalSettingsFactoryWithArgs
config.GetSection("globalSettings").Bind(GlobalSettings); config.GetSection("globalSettings").Bind(GlobalSettings);
} }
} }