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:
parent
7877042c32
commit
44f4efffaf
@ -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>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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>
|
|
@ -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>();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
|
||||||
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
namespace Bit.Seeder.Services;
|
namespace Bit.Seeder.Services;
|
||||||
|
|
||||||
public interface ISeederService
|
public interface ISeederService
|
||||||
{
|
{
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user