1
0
mirror of https://github.com/bitwarden/server synced 2025-12-18 17:23:28 +00:00

Wire up crypto logic for sharing org key

This commit is contained in:
Hinton
2025-07-31 14:48:48 +02:00
parent 75f11f68ac
commit 3132e09e21
9 changed files with 133 additions and 95 deletions

View File

@@ -1,7 +1,9 @@
using System.Net; using System.Net;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using Bit.Api.IntegrationTest.Factories; using Bit.Api.IntegrationTest.Factories;
using Bit.Infrastructure.EntityFramework.Models;
using Bit.Seeder.Recipes; using Bit.Seeder.Recipes;
using Microsoft.AspNetCore.Identity;
using Xunit; using Xunit;
using Xunit.Abstractions; using Xunit.Abstractions;
@@ -18,7 +20,8 @@ public class OrganizationUsersControllerPerformanceTest(ITestOutputHelper testOu
var client = factory.CreateClient(); var client = factory.CreateClient();
var db = factory.GetDatabaseContext(); var db = factory.GetDatabaseContext();
var seeder = new OrganizationWithUsersRecipe(db); var passwordHasher = factory.Services.CreateScope().ServiceProvider.GetService<IPasswordHasher<User>>();
var seeder = new OrganizationWithUsersRecipe(db, passwordHasher);
var orgId = seeder.Seed("Org", seats, "large.test"); var orgId = seeder.Seed("Org", seats, "large.test");

View File

@@ -7,11 +7,26 @@ namespace Bit.RustSDK;
public class UserKeys public class UserKeys
{ {
public required string MasterPasswordHash { get; set; } public required string MasterPasswordHash { get; set; }
/// <summary>
/// Base64 encoded UserKey
/// </summary>
public required string Key { get; set; }
public required string EncryptedUserKey { get; set; } public required string EncryptedUserKey { get; set; }
public required string PublicKey { get; set; } public required string PublicKey { get; set; }
public required string PrivateKey { get; set; } public required string PrivateKey { get; set; }
} }
public class OrganizationKeys
{
/// <summary>
/// Base64 encoded SymmetricCryptoKey
/// </summary>
public required string Key { get; set; }
public required string PublicKey { get; set; }
public required string PrivateKey { get; set; }
}
/// <summary> /// <summary>
/// Service implementation that provides a C# friendly interface to the Rust SDK /// Service implementation that provides a C# friendly interface to the Rust SDK
/// </summary> /// </summary>
@@ -38,43 +53,32 @@ public class RustSdkService
} }
} }
/// <summary> public unsafe OrganizationKeys GenerateOrganizationKeys()
/// Hashes a password using the native implementation
/// </summary>
/// <param name="email">User email</param>
/// <param name="password">User password</param>
/// <returns>The hashed password as a string</returns>
/// <exception cref="ArgumentNullException">Thrown when email or password is null</exception>
/// <exception cref="ArgumentException">Thrown when email or password is empty</exception>
/// <exception cref="RustSdkException">Thrown when the native operation fails</exception>
public unsafe string HashPassword(string email, string password)
{ {
// Convert strings to null-terminated byte arrays var resultPtr = NativeMethods.generate_organization_keys();
var emailBytes = StringToRustString(email);
var passwordBytes = StringToRustString(password);
try var result = TakeAndDestroyRustString(resultPtr);
{
fixed (byte* emailPtr = emailBytes)
fixed (byte* passwordPtr = passwordBytes)
{
var resultPtr = NativeMethods.hash_password(emailPtr, passwordPtr);
var result = TakeAndDestroyRustString(resultPtr); return JsonSerializer.Deserialize<OrganizationKeys>(result, CaseInsensitiveOptions)!;
}
return result; public unsafe string GenerateUserOrganizationKey(string userKey, string orgKey)
} {
} var userKeyBytes = StringToRustString(userKey);
catch (RustSdkException) var orgKeyBytes = StringToRustString(orgKey);
fixed (byte* userKeyPtr = userKeyBytes)
fixed (byte* orgKeyPtr = orgKeyBytes)
{ {
throw; // Re-throw our custom exceptions var resultPtr = NativeMethods.generate_user_organization_key(userKeyPtr, orgKeyPtr);
}
catch (Exception ex) var result = TakeAndDestroyRustString(resultPtr);
{
throw new RustSdkException($"Failed to hash password: {ex.Message}", ex); return result;
} }
} }
private static byte[] StringToRustString(string str) private static byte[] StringToRustString(string str)
{ {
return Encoding.UTF8.GetBytes(str + '\0'); return Encoding.UTF8.GetBytes(str + '\0');

View File

@@ -5,15 +5,6 @@
/// </summary> /// </summary>
public static class RustSdkServiceFactory public static class RustSdkServiceFactory
{ {
/// <summary>
/// Creates a new instance of the Rust SDK service
/// </summary>
/// <returns>A new IRustSdkService instance</returns>
public static RustSdkService Create()
{
return new RustSdkService();
}
/// <summary> /// <summary>
/// Creates a singleton instance of the Rust SDK service (thread-safe) /// Creates a singleton instance of the Rust SDK service (thread-safe)
/// </summary> /// </summary>
@@ -25,6 +16,6 @@ public static class RustSdkServiceFactory
private static class SingletonHolder private static class SingletonHolder
{ {
internal static readonly RustSdkService Instance = new RustSdkService(); internal static readonly RustSdkService Instance = new();
} }
} }

View File

@@ -135,7 +135,7 @@ checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
[[package]] [[package]]
name = "bitwarden-api-api" name = "bitwarden-api-api"
version = "1.0.0" version = "1.0.0"
source = "git+https://github.com/bitwarden/sdk-internal.git?rev=b0c950dad701bc419c76e8a7d37bf5c17a6909d6#b0c950dad701bc419c76e8a7d37bf5c17a6909d6" source = "git+https://github.com/bitwarden/sdk-internal.git?rev=29c6158636d50141788e41736d15f2f6c7bc7fa8#29c6158636d50141788e41736d15f2f6c7bc7fa8"
dependencies = [ dependencies = [
"reqwest", "reqwest",
"serde", "serde",
@@ -149,7 +149,7 @@ dependencies = [
[[package]] [[package]]
name = "bitwarden-api-identity" name = "bitwarden-api-identity"
version = "1.0.0" version = "1.0.0"
source = "git+https://github.com/bitwarden/sdk-internal.git?rev=b0c950dad701bc419c76e8a7d37bf5c17a6909d6#b0c950dad701bc419c76e8a7d37bf5c17a6909d6" source = "git+https://github.com/bitwarden/sdk-internal.git?rev=29c6158636d50141788e41736d15f2f6c7bc7fa8#29c6158636d50141788e41736d15f2f6c7bc7fa8"
dependencies = [ dependencies = [
"reqwest", "reqwest",
"serde", "serde",
@@ -163,8 +163,9 @@ dependencies = [
[[package]] [[package]]
name = "bitwarden-core" name = "bitwarden-core"
version = "1.0.0" version = "1.0.0"
source = "git+https://github.com/bitwarden/sdk-internal.git?rev=b0c950dad701bc419c76e8a7d37bf5c17a6909d6#b0c950dad701bc419c76e8a7d37bf5c17a6909d6" source = "git+https://github.com/bitwarden/sdk-internal.git?rev=29c6158636d50141788e41736d15f2f6c7bc7fa8#29c6158636d50141788e41736d15f2f6c7bc7fa8"
dependencies = [ dependencies = [
"async-trait",
"base64", "base64",
"bitwarden-api-api", "bitwarden-api-api",
"bitwarden-api-identity", "bitwarden-api-identity",
@@ -181,10 +182,11 @@ dependencies = [
"rustls-platform-verifier", "rustls-platform-verifier",
"schemars 0.8.22", "schemars 0.8.22",
"serde", "serde",
"serde_bytes",
"serde_json", "serde_json",
"serde_qs", "serde_qs",
"serde_repr", "serde_repr",
"thiserror 2.0.12", "thiserror 1.0.69",
"uuid", "uuid",
"zeroize", "zeroize",
] ]
@@ -192,7 +194,7 @@ dependencies = [
[[package]] [[package]]
name = "bitwarden-crypto" name = "bitwarden-crypto"
version = "1.0.0" version = "1.0.0"
source = "git+https://github.com/bitwarden/sdk-internal.git?rev=b0c950dad701bc419c76e8a7d37bf5c17a6909d6#b0c950dad701bc419c76e8a7d37bf5c17a6909d6" source = "git+https://github.com/bitwarden/sdk-internal.git?rev=29c6158636d50141788e41736d15f2f6c7bc7fa8#29c6158636d50141788e41736d15f2f6c7bc7fa8"
dependencies = [ dependencies = [
"aes", "aes",
"argon2", "argon2",
@@ -220,7 +222,7 @@ dependencies = [
"sha1", "sha1",
"sha2", "sha2",
"subtle", "subtle",
"thiserror 2.0.12", "thiserror 1.0.69",
"typenum", "typenum",
"uuid", "uuid",
"zeroize", "zeroize",
@@ -230,7 +232,7 @@ dependencies = [
[[package]] [[package]]
name = "bitwarden-error" name = "bitwarden-error"
version = "1.0.0" version = "1.0.0"
source = "git+https://github.com/bitwarden/sdk-internal.git?rev=b0c950dad701bc419c76e8a7d37bf5c17a6909d6#b0c950dad701bc419c76e8a7d37bf5c17a6909d6" source = "git+https://github.com/bitwarden/sdk-internal.git?rev=29c6158636d50141788e41736d15f2f6c7bc7fa8#29c6158636d50141788e41736d15f2f6c7bc7fa8"
dependencies = [ dependencies = [
"bitwarden-error-macro", "bitwarden-error-macro",
] ]
@@ -238,7 +240,7 @@ dependencies = [
[[package]] [[package]]
name = "bitwarden-error-macro" name = "bitwarden-error-macro"
version = "1.0.0" version = "1.0.0"
source = "git+https://github.com/bitwarden/sdk-internal.git?rev=b0c950dad701bc419c76e8a7d37bf5c17a6909d6#b0c950dad701bc419c76e8a7d37bf5c17a6909d6" source = "git+https://github.com/bitwarden/sdk-internal.git?rev=29c6158636d50141788e41736d15f2f6c7bc7fa8#29c6158636d50141788e41736d15f2f6c7bc7fa8"
dependencies = [ dependencies = [
"darling", "darling",
"proc-macro2", "proc-macro2",
@@ -249,16 +251,16 @@ dependencies = [
[[package]] [[package]]
name = "bitwarden-state" name = "bitwarden-state"
version = "1.0.0" version = "1.0.0"
source = "git+https://github.com/bitwarden/sdk-internal.git?rev=b0c950dad701bc419c76e8a7d37bf5c17a6909d6#b0c950dad701bc419c76e8a7d37bf5c17a6909d6" source = "git+https://github.com/bitwarden/sdk-internal.git?rev=29c6158636d50141788e41736d15f2f6c7bc7fa8#29c6158636d50141788e41736d15f2f6c7bc7fa8"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"thiserror 2.0.12", "thiserror 1.0.69",
] ]
[[package]] [[package]]
name = "bitwarden-uuid" name = "bitwarden-uuid"
version = "1.0.0" version = "1.0.0"
source = "git+https://github.com/bitwarden/sdk-internal.git?rev=b0c950dad701bc419c76e8a7d37bf5c17a6909d6#b0c950dad701bc419c76e8a7d37bf5c17a6909d6" source = "git+https://github.com/bitwarden/sdk-internal.git?rev=29c6158636d50141788e41736d15f2f6c7bc7fa8#29c6158636d50141788e41736d15f2f6c7bc7fa8"
dependencies = [ dependencies = [
"bitwarden-uuid-macro", "bitwarden-uuid-macro",
] ]
@@ -266,7 +268,7 @@ dependencies = [
[[package]] [[package]]
name = "bitwarden-uuid-macro" name = "bitwarden-uuid-macro"
version = "1.0.0" version = "1.0.0"
source = "git+https://github.com/bitwarden/sdk-internal.git?rev=b0c950dad701bc419c76e8a7d37bf5c17a6909d6#b0c950dad701bc419c76e8a7d37bf5c17a6909d6" source = "git+https://github.com/bitwarden/sdk-internal.git?rev=29c6158636d50141788e41736d15f2f6c7bc7fa8#29c6158636d50141788e41736d15f2f6c7bc7fa8"
dependencies = [ dependencies = [
"quote", "quote",
"syn", "syn",
@@ -1894,6 +1896,7 @@ dependencies = [
name = "sdk" name = "sdk"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"base64",
"bitwarden-core", "bitwarden-core",
"bitwarden-crypto", "bitwarden-crypto",
"csbindgen", "csbindgen",

View File

@@ -12,8 +12,9 @@ repository = "https://github.com/bitwarden/server"
crate-type = ["cdylib"] crate-type = ["cdylib"]
[dependencies] [dependencies]
bitwarden-core = { git = "https://github.com/bitwarden/sdk-internal.git", rev = "b0c950dad701bc419c76e8a7d37bf5c17a6909d6" } base64 = "0.22.1"
bitwarden-crypto = { git = "https://github.com/bitwarden/sdk-internal.git", rev = "b0c950dad701bc419c76e8a7d37bf5c17a6909d6" } bitwarden-core = { git = "https://github.com/bitwarden/sdk-internal.git", rev = "29c6158636d50141788e41736d15f2f6c7bc7fa8" }
bitwarden-crypto = { git = "https://github.com/bitwarden/sdk-internal.git", rev = "29c6158636d50141788e41736d15f2f6c7bc7fa8" }
serde = "=1.0.219" serde = "=1.0.219"
serde_json = "=1.0.141" serde_json = "=1.0.141"

View File

@@ -4,7 +4,12 @@ use std::{
num::NonZeroU32, num::NonZeroU32,
}; };
use bitwarden_crypto::{HashPurpose, Kdf, MasterKey}; use base64::{engine::general_purpose::STANDARD, Engine};
use bitwarden_crypto::{
AsymmetricPublicCryptoKey, BitwardenLegacyKeyBytes, HashPurpose, Kdf, MasterKey,
SpkiPublicKeyBytes, SymmetricCryptoKey, UnsignedSharedKey, UserKey,
};
#[no_mangle] #[no_mangle]
pub extern "C" fn my_add(x: i32, y: i32) -> i32 { pub extern "C" fn my_add(x: i32, y: i32) -> i32 {
@@ -29,13 +34,14 @@ pub unsafe extern "C" fn generate_user_keys(
.derive_master_key_hash(password.as_bytes(), HashPurpose::ServerAuthorization) .derive_master_key_hash(password.as_bytes(), HashPurpose::ServerAuthorization)
.unwrap(); .unwrap();
let (user_key, encrypted_user_key) = master_key.make_user_key().unwrap(); let (user_key, encrypted_user_key) = master_key.make_user_key().unwrap();
let keys = user_key.make_key_pair().unwrap(); let keypair = user_key.make_key_pair().unwrap();
let json = serde_json::json!({ let json = serde_json::json!({
"masterPasswordHash": master_password_hash, "masterPasswordHash": master_password_hash,
"key": user_key.0.to_base64(),
"encryptedUserKey": encrypted_user_key.to_string(), "encryptedUserKey": encrypted_user_key.to_string(),
"publicKey": keys.public.to_string(), "publicKey": keypair.public.to_string(),
"privateKey": keys.private.to_string(), "privateKey": keypair.private.to_string(),
}) })
.to_string(); .to_string();
@@ -44,31 +50,51 @@ pub unsafe extern "C" fn generate_user_keys(
result.into_raw() result.into_raw()
} }
/// # Safety
///
/// The `email` and `password` pointers must be valid null-terminated C strings.
/// Both pointers must be non-null and point to valid memory for the duration of the function call.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn hash_password( pub unsafe extern "C" fn generate_organization_keys() -> *const c_char {
email: *const c_char, let key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
password: *const c_char,
let key = UserKey::new(key);
let keypair = key.make_key_pair().expect("Failed to generate key pair");
let json = serde_json::json!({
"key": key.0.to_base64(),
"publicKey": keypair.public.to_string(),
"privateKey": keypair.private.to_string(),
})
.to_string();
let result = CString::new(json).unwrap();
result.into_raw()
}
#[no_mangle]
pub unsafe extern "C" fn generate_user_organization_key(
user_public_key: *const c_char,
organization_key: *const c_char,
) -> *const c_char { ) -> *const c_char {
let kdf = Kdf::PBKDF2 { let user_public_key = CStr::from_ptr(user_public_key).to_str().unwrap().to_owned();
iterations: NonZeroU32::new(600_000).unwrap(), let organization_key = CStr::from_ptr(organization_key)
}; .to_str()
.unwrap()
.to_owned();
let email = CStr::from_ptr(email).to_str().unwrap(); let user_public_key = STANDARD.decode(user_public_key).unwrap();
let password = CStr::from_ptr(password).to_str().unwrap(); let organization_key = STANDARD.decode(organization_key).unwrap();
let master_key = MasterKey::derive(password, email, &kdf).unwrap(); let encapsulation_key =
AsymmetricPublicCryptoKey::from_der(&SpkiPublicKeyBytes::from(user_public_key)).unwrap();
let res = master_key let encrypted_key = UnsignedSharedKey::encapsulate_key_unsigned(
.derive_master_key_hash(password.as_bytes(), HashPurpose::ServerAuthorization) &SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(organization_key)).unwrap(),
.unwrap(); &encapsulation_key,
)
.unwrap();
let res = CString::new(res).unwrap(); let result = CString::new(encrypted_key.to_string()).unwrap();
res.into_raw() result.into_raw()
} }
/// # Safety /// # Safety

View File

@@ -2,14 +2,19 @@
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Infrastructure.EntityFramework.AdminConsole.Models; using Bit.Infrastructure.EntityFramework.AdminConsole.Models;
using Bit.Infrastructure.EntityFramework.Models; using Bit.Infrastructure.EntityFramework.Models;
using Bit.RustSDK;
namespace Bit.Seeder.Factories; namespace Bit.Seeder.Factories;
public class OrganizationSeeder public class OrganizationSeeder
{ {
public static Organization CreateEnterprise(string name, string domain, int seats) public static (Organization organization, string key) CreateEnterprise(string name, string domain, int seats)
{ {
return new Organization var nativeService = RustSdkServiceFactory.CreateSingleton();
var keys = nativeService.GenerateOrganizationKeys();
var organization = new Organization
{ {
Id = Guid.NewGuid(), Id = Guid.NewGuid(),
Name = name, Name = name,
@@ -20,23 +25,28 @@ public class OrganizationSeeder
// Currently hardcoded to the values from https://github.com/bitwarden/sdk-internal/blob/main/crates/bitwarden-core/src/client/test_accounts.rs. // Currently hardcoded to the values from https://github.com/bitwarden/sdk-internal/blob/main/crates/bitwarden-core/src/client/test_accounts.rs.
// TODO: These should be dynamically generated by the SDK. // TODO: These should be dynamically generated by the SDK.
PublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmIJbGMk6eZqVE7UxhZ46Weu2jKciqOiOkSVYtGvs61rfe9AXxtLaaZEKN4d4DmkZcF6dna2eXNxZmb7U4pwlttye8ksqISe6IUAZQox7auBpjopdCEPhKRg3BD/u8ks9UxSxgWe+fpebjt6gd5hsl1/5HOObn7SeU6EEU04cp3/eH7a4OTdXxB8oN62HGV9kM/ubM1goILgjoSJDbihMK0eb7b8hPHwcA/YOgKKiu/N3FighccdSMD5Pk+HfjacsFNZQa2EsqW09IvvSZ+iL6HQeZ1vwc/6TO1J7EOfJZFQcjoEL9LVI693efYoMZSmrPEWziZ4PvwpOOGo6OObyMQIDAQAB", PublicKey = keys.PublicKey,
PrivateKey = "2.6FggyKVyaKQsfohi5yqgbg==|UU2JeafOB41L5UscGmf4kq15JGDf3Bkf67KECiehTODzbWctVLTgyDk0Qco8/6CMN6nZGXjxR2A4r5ExhmwRNsNxd77G+MprkmiJz+7w33ROZ1ouQO5XjD3wbQ3ssqNiTKId6yAUPBvuAZRixVApauTuADc8QWGixqCQcqZzmU7YSBBIPf652/AEYr4Tk64YihoE39pHiK8MRbTLdRt3EF4LSMugPAPM24vCgUv3w1TD3Fj6sDg/6oi3flOV9SJZX4vCiUXbDNEuD/p2aQrEXVbaxweFOHjTe7F4iawjXw3nG3SO8rUBHcxbhDDVx5rjYactbW5QvHWiyla6uLb6o8WHBneg2EjTEwAHOZE/rBjcqmAJb2sVp1E0Kwq8ycGmL69vmqJPC1GqVTohAQvmEkaxIPpfq24Yb9ZPrADA7iEXBKuAQ1FphFUVgJBJGJbd60sOV1Rz1T+gUwS4wCNQ4l3LG1S22+wzUVlEku5DXFnT932tatqTyWEthqPqLCt6dL1+qa94XLpeHagXAx2VGe8n8IlcADtxqS+l8xQ4heT12WO9kC316vqvg1mnsI56faup9hb3eT9ZpKyxSBGYOphlTWfV1Y/v64f5PYvTo4aL0IYHyLY/9Qi72vFmOpPeHBYgD5t3j+H2CsiU1PkYsBggOmD7xW8FDuT6HWVvwhEJqeibVPK0Lhyj6tgvlSIAvFUaSMFPlmwFNmwfj/AHUhr9KuTfsBFTZ10yy9TZVgf+EofwnrxHBaWUgdD40aHoY1VjfG33iEuajb6buxG3pYFyPNhJNzeLZisUKIDRMQpUHrsE22EyrFFran3tZGdtcyIEK4Q1F0ULYzJ6T9iY25/ZgPy3pEAAMZCtqo3s+GjX295fWIHfMcnjMgNUHPjExjWBHa+ggK9iQXkFpBVyYB1ga/+0eiIhiek3PlgtvpDrqF7TsLK+ROiBw2GJ7uaO3EEXOj2GpNBuEJ5CdodhZkwzhwMcSatgDHkUuNVu0iVbF6/MxVdOxWXKO+jCYM6PZk/vAhLYqpPzu2T2Uyz4nkDs2Tiq61ez6FoCrzdHIiyIxVTzUQH8G9FgSmtaZ7GCbqlhnurYgcMciwPzxg0hpAQT+NZw1tVEii9vFSpJJbGJqNhORKfKh/Mu1P/9LOQq7Y0P2FIR3x/eUVEQ7CGv2jVtO5ryGSmKeq/P9Fr54wTPaNiqN2K+leACUznCdUWw8kZo/AsBcrOe4OkRX6k8LC3oeJXy06DEToatxEvPYemUauhxiXRw8nfNMqc4LyJq2bbT0zCgJHoqpozPdNg6AYWcoIobgAGu7ZQGq+oE1MT3GZxotMPe/NUJiAc5YE9Thb5Yf3gyno71pyqPTVl/6IQuh4SUz7rkgwF/aVHEnr4aUYNoc0PEzd2Me0jElsA3GAneq1I/wngutOWgTViTK4Nptr5uIzMVQs9H1rOMJNorP8b02t1NDu010rSsib9GaaJJq4r4iy46laQOxWoU0ex26arYnk+jw4833WSCTVBIprTgizZ+fKjoY0xwXvI2oOvGNEUCtGFvKFORTaQrlaXZIg1toa2BBVNicyONbwnI3KIu3MgGJ2SlCVXJn8oHFppVHFCdwgN1uDzGiKAhjvr0sZTUtXin2f2CszPTbbo=|fUhbVKrr8CSKE7TZJneXpDGraj5YhRrq9ESo206S+BY=", PrivateKey = keys.PrivateKey,
}; };
return (organization, keys.Key);
} }
} }
public static class OrgnaizationExtensions public static class OrganizationExtensions
{ {
public static OrganizationUser CreateOrganizationUser(this Organization organization, User user) public static OrganizationUser CreateOrganizationUser(this Organization organization, User user, string orgKey)
{ {
var nativeService = RustSdkServiceFactory.CreateSingleton();
var userOrgKey = nativeService.GenerateUserOrganizationKey(user.PublicKey!, orgKey);
return new OrganizationUser return new OrganizationUser
{ {
Id = Guid.NewGuid(), Id = Guid.NewGuid(),
OrganizationId = organization.Id, OrganizationId = organization.Id,
UserId = user.Id, UserId = user.Id,
Key = userOrgKey,
Key = "4.rY01mZFXHOsBAg5Fq4gyXuklWfm6mQASm42DJpx05a+e2mmp+P5W6r54WU2hlREX0uoTxyP91bKKwickSPdCQQ58J45LXHdr9t2uzOYyjVzpzebFcdMw1eElR9W2DW8wEk9+mvtWvKwu7yTebzND+46y1nRMoFydi5zPVLSlJEf81qZZ4Uh1UUMLwXz+NRWfixnGXgq2wRq1bH0n3mqDhayiG4LJKgGdDjWXC8W8MMXDYx24SIJrJu9KiNEMprJE+XVF9nQVNijNAjlWBqkDpsfaWTUfeVLRLctfAqW1blsmIv4RQ91PupYJZDNc8nO9ZTF3TEVM+2KHoxzDJrLs2Q==",
Type = OrganizationUserType.Admin, Type = OrganizationUserType.Admin,
Status = OrganizationUserStatusType.Confirmed Status = OrganizationUserStatusType.Confirmed
}; };

View File

@@ -9,7 +9,7 @@ namespace Bit.Seeder.Factories;
public class UserSeeder public class UserSeeder
{ {
public static User CreateUser(IPasswordHasher<User> passwordHasher, string email) public static (User user, string userKey) CreateUser(IPasswordHasher<User> passwordHasher, string email)
{ {
var nativeService = RustSdkServiceFactory.CreateSingleton(); var nativeService = RustSdkServiceFactory.CreateSingleton();
var keys = nativeService.GenerateUserKeys(email, "asdfasdfasdf"); var keys = nativeService.GenerateUserKeys(email, "asdfasdfasdf");
@@ -31,6 +31,6 @@ public class UserSeeder
user.MasterPassword = passwordHasher.HashPassword(user, keys.MasterPasswordHash); user.MasterPassword = passwordHasher.HashPassword(user, keys.MasterPasswordHash);
return user; return (user, keys.Key);
} }
} }

View File

@@ -11,28 +11,28 @@ public class OrganizationWithUsersRecipe(DatabaseContext db, IPasswordHasher<Use
{ {
public Guid Seed(string name, int users, string domain) public Guid Seed(string name, int users, string domain)
{ {
var organization = OrganizationSeeder.CreateEnterprise(name, domain, users); var (organization, orgKey) = OrganizationSeeder.CreateEnterprise(name, domain, users);
var user = UserSeeder.CreateUser(passwordHasher, $"admin@{domain}"); var (user, _) = UserSeeder.CreateUser(passwordHasher, $"admin@{domain}");
var orgUser = organization.CreateOrganizationUser(user); var orgUser = organization.CreateOrganizationUser(user, orgKey);
var additionalUsers = new List<User>(); var additionalUsers = new List<User>();
var additionalOrgUsers = new List<OrganizationUser>(); var additionalOrgUsers = new List<OrganizationUser>();
for (var i = 0; i < users; i++) for (var i = 0; i < users; i++)
{ {
var additionalUser = UserSeeder.CreateUser(passwordHasher, $"user{i}@{domain}"); var (additionalUser, _) = UserSeeder.CreateUser(passwordHasher, $"user{i}@{domain}");
additionalUsers.Add(additionalUser); additionalUsers.Add(additionalUser);
additionalOrgUsers.Add(organization.CreateOrganizationUser(additionalUser)); additionalOrgUsers.Add(organization.CreateOrganizationUser(additionalUser, orgKey));
} }
//db.Add(organization); db.Add(organization);
db.Add(user); db.Add(user);
//db.Add(orgUser); db.Add(orgUser);
db.SaveChanges(); db.SaveChanges();
// Use LinqToDB's BulkCopy for significant better performance // Use LinqToDB's BulkCopy for significant better performance
//db.BulkCopy(additionalUsers); db.BulkCopy(additionalUsers);
//db.BulkCopy(additionalOrgUsers); db.BulkCopy(additionalOrgUsers);
return organization.Id; return organization.Id;
} }