mirror of
https://github.com/bitwarden/server
synced 2025-12-19 09:43:25 +00:00
Wire up crypto logic for sharing org key
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Headers;
|
||||
using Bit.Api.IntegrationTest.Factories;
|
||||
using Bit.Infrastructure.EntityFramework.Models;
|
||||
using Bit.Seeder.Recipes;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
@@ -18,7 +20,8 @@ public class OrganizationUsersControllerPerformanceTest(ITestOutputHelper testOu
|
||||
var client = factory.CreateClient();
|
||||
|
||||
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");
|
||||
|
||||
|
||||
@@ -7,11 +7,26 @@ namespace Bit.RustSDK;
|
||||
public class UserKeys
|
||||
{
|
||||
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 PublicKey { 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>
|
||||
/// Service implementation that provides a C# friendly interface to the Rust SDK
|
||||
/// </summary>
|
||||
@@ -38,42 +53,31 @@ public class RustSdkService
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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)
|
||||
public unsafe OrganizationKeys GenerateOrganizationKeys()
|
||||
{
|
||||
// Convert strings to null-terminated byte arrays
|
||||
var emailBytes = StringToRustString(email);
|
||||
var passwordBytes = StringToRustString(password);
|
||||
var resultPtr = NativeMethods.generate_organization_keys();
|
||||
|
||||
try
|
||||
var result = TakeAndDestroyRustString(resultPtr);
|
||||
|
||||
return JsonSerializer.Deserialize<OrganizationKeys>(result, CaseInsensitiveOptions)!;
|
||||
}
|
||||
|
||||
public unsafe string GenerateUserOrganizationKey(string userKey, string orgKey)
|
||||
{
|
||||
fixed (byte* emailPtr = emailBytes)
|
||||
fixed (byte* passwordPtr = passwordBytes)
|
||||
var userKeyBytes = StringToRustString(userKey);
|
||||
var orgKeyBytes = StringToRustString(orgKey);
|
||||
|
||||
fixed (byte* userKeyPtr = userKeyBytes)
|
||||
fixed (byte* orgKeyPtr = orgKeyBytes)
|
||||
{
|
||||
var resultPtr = NativeMethods.hash_password(emailPtr, passwordPtr);
|
||||
var resultPtr = NativeMethods.generate_user_organization_key(userKeyPtr, orgKeyPtr);
|
||||
|
||||
var result = TakeAndDestroyRustString(resultPtr);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
catch (RustSdkException)
|
||||
{
|
||||
throw; // Re-throw our custom exceptions
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new RustSdkException($"Failed to hash password: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static byte[] StringToRustString(string str)
|
||||
{
|
||||
|
||||
@@ -5,15 +5,6 @@
|
||||
/// </summary>
|
||||
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>
|
||||
/// Creates a singleton instance of the Rust SDK service (thread-safe)
|
||||
/// </summary>
|
||||
@@ -25,6 +16,6 @@ public static class RustSdkServiceFactory
|
||||
|
||||
private static class SingletonHolder
|
||||
{
|
||||
internal static readonly RustSdkService Instance = new RustSdkService();
|
||||
internal static readonly RustSdkService Instance = new();
|
||||
}
|
||||
}
|
||||
|
||||
27
util/RustSdk/rust/Cargo.lock
generated
27
util/RustSdk/rust/Cargo.lock
generated
@@ -135,7 +135,7 @@ checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
||||
[[package]]
|
||||
name = "bitwarden-api-api"
|
||||
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 = [
|
||||
"reqwest",
|
||||
"serde",
|
||||
@@ -149,7 +149,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "bitwarden-api-identity"
|
||||
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 = [
|
||||
"reqwest",
|
||||
"serde",
|
||||
@@ -163,8 +163,9 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "bitwarden-core"
|
||||
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 = [
|
||||
"async-trait",
|
||||
"base64",
|
||||
"bitwarden-api-api",
|
||||
"bitwarden-api-identity",
|
||||
@@ -181,10 +182,11 @@ dependencies = [
|
||||
"rustls-platform-verifier",
|
||||
"schemars 0.8.22",
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
"serde_json",
|
||||
"serde_qs",
|
||||
"serde_repr",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 1.0.69",
|
||||
"uuid",
|
||||
"zeroize",
|
||||
]
|
||||
@@ -192,7 +194,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "bitwarden-crypto"
|
||||
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 = [
|
||||
"aes",
|
||||
"argon2",
|
||||
@@ -220,7 +222,7 @@ dependencies = [
|
||||
"sha1",
|
||||
"sha2",
|
||||
"subtle",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 1.0.69",
|
||||
"typenum",
|
||||
"uuid",
|
||||
"zeroize",
|
||||
@@ -230,7 +232,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "bitwarden-error"
|
||||
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 = [
|
||||
"bitwarden-error-macro",
|
||||
]
|
||||
@@ -238,7 +240,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "bitwarden-error-macro"
|
||||
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 = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
@@ -249,16 +251,16 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "bitwarden-state"
|
||||
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 = [
|
||||
"async-trait",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitwarden-uuid"
|
||||
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 = [
|
||||
"bitwarden-uuid-macro",
|
||||
]
|
||||
@@ -266,7 +268,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "bitwarden-uuid-macro"
|
||||
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 = [
|
||||
"quote",
|
||||
"syn",
|
||||
@@ -1894,6 +1896,7 @@ dependencies = [
|
||||
name = "sdk"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bitwarden-core",
|
||||
"bitwarden-crypto",
|
||||
"csbindgen",
|
||||
|
||||
@@ -12,8 +12,9 @@ repository = "https://github.com/bitwarden/server"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
bitwarden-core = { git = "https://github.com/bitwarden/sdk-internal.git", rev = "b0c950dad701bc419c76e8a7d37bf5c17a6909d6" }
|
||||
bitwarden-crypto = { git = "https://github.com/bitwarden/sdk-internal.git", rev = "b0c950dad701bc419c76e8a7d37bf5c17a6909d6" }
|
||||
base64 = "0.22.1"
|
||||
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_json = "=1.0.141"
|
||||
|
||||
|
||||
@@ -4,7 +4,12 @@ use std::{
|
||||
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]
|
||||
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)
|
||||
.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!({
|
||||
"masterPasswordHash": master_password_hash,
|
||||
"key": user_key.0.to_base64(),
|
||||
"encryptedUserKey": encrypted_user_key.to_string(),
|
||||
"publicKey": keys.public.to_string(),
|
||||
"privateKey": keys.private.to_string(),
|
||||
"publicKey": keypair.public.to_string(),
|
||||
"privateKey": keypair.private.to_string(),
|
||||
})
|
||||
.to_string();
|
||||
|
||||
@@ -44,31 +50,51 @@ pub unsafe extern "C" fn generate_user_keys(
|
||||
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]
|
||||
pub unsafe extern "C" fn hash_password(
|
||||
email: *const c_char,
|
||||
password: *const c_char,
|
||||
pub unsafe extern "C" fn generate_organization_keys() -> *const c_char {
|
||||
let key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
|
||||
|
||||
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 {
|
||||
let kdf = Kdf::PBKDF2 {
|
||||
iterations: NonZeroU32::new(600_000).unwrap(),
|
||||
};
|
||||
let user_public_key = CStr::from_ptr(user_public_key).to_str().unwrap().to_owned();
|
||||
let organization_key = CStr::from_ptr(organization_key)
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_owned();
|
||||
|
||||
let email = CStr::from_ptr(email).to_str().unwrap();
|
||||
let password = CStr::from_ptr(password).to_str().unwrap();
|
||||
let user_public_key = STANDARD.decode(user_public_key).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
|
||||
.derive_master_key_hash(password.as_bytes(), HashPurpose::ServerAuthorization)
|
||||
let encrypted_key = UnsignedSharedKey::encapsulate_key_unsigned(
|
||||
&SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(organization_key)).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
|
||||
|
||||
@@ -2,14 +2,19 @@
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Infrastructure.EntityFramework.AdminConsole.Models;
|
||||
using Bit.Infrastructure.EntityFramework.Models;
|
||||
using Bit.RustSDK;
|
||||
|
||||
namespace Bit.Seeder.Factories;
|
||||
|
||||
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(),
|
||||
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.
|
||||
// TODO: These should be dynamically generated by the SDK.
|
||||
PublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmIJbGMk6eZqVE7UxhZ46Weu2jKciqOiOkSVYtGvs61rfe9AXxtLaaZEKN4d4DmkZcF6dna2eXNxZmb7U4pwlttye8ksqISe6IUAZQox7auBpjopdCEPhKRg3BD/u8ks9UxSxgWe+fpebjt6gd5hsl1/5HOObn7SeU6EEU04cp3/eH7a4OTdXxB8oN62HGV9kM/ubM1goILgjoSJDbihMK0eb7b8hPHwcA/YOgKKiu/N3FighccdSMD5Pk+HfjacsFNZQa2EsqW09IvvSZ+iL6HQeZ1vwc/6TO1J7EOfJZFQcjoEL9LVI693efYoMZSmrPEWziZ4PvwpOOGo6OObyMQIDAQAB",
|
||||
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=",
|
||||
PublicKey = keys.PublicKey,
|
||||
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
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrganizationId = organization.Id,
|
||||
UserId = user.Id,
|
||||
|
||||
Key = "4.rY01mZFXHOsBAg5Fq4gyXuklWfm6mQASm42DJpx05a+e2mmp+P5W6r54WU2hlREX0uoTxyP91bKKwickSPdCQQ58J45LXHdr9t2uzOYyjVzpzebFcdMw1eElR9W2DW8wEk9+mvtWvKwu7yTebzND+46y1nRMoFydi5zPVLSlJEf81qZZ4Uh1UUMLwXz+NRWfixnGXgq2wRq1bH0n3mqDhayiG4LJKgGdDjWXC8W8MMXDYx24SIJrJu9KiNEMprJE+XVF9nQVNijNAjlWBqkDpsfaWTUfeVLRLctfAqW1blsmIv4RQ91PupYJZDNc8nO9ZTF3TEVM+2KHoxzDJrLs2Q==",
|
||||
Key = userOrgKey,
|
||||
Type = OrganizationUserType.Admin,
|
||||
Status = OrganizationUserStatusType.Confirmed
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Bit.Seeder.Factories;
|
||||
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 keys = nativeService.GenerateUserKeys(email, "asdfasdfasdf");
|
||||
@@ -31,6 +31,6 @@ public class UserSeeder
|
||||
|
||||
user.MasterPassword = passwordHasher.HashPassword(user, keys.MasterPasswordHash);
|
||||
|
||||
return user;
|
||||
return (user, keys.Key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,28 +11,28 @@ public class OrganizationWithUsersRecipe(DatabaseContext db, IPasswordHasher<Use
|
||||
{
|
||||
public Guid Seed(string name, int users, string domain)
|
||||
{
|
||||
var organization = OrganizationSeeder.CreateEnterprise(name, domain, users);
|
||||
var user = UserSeeder.CreateUser(passwordHasher, $"admin@{domain}");
|
||||
var orgUser = organization.CreateOrganizationUser(user);
|
||||
var (organization, orgKey) = OrganizationSeeder.CreateEnterprise(name, domain, users);
|
||||
var (user, _) = UserSeeder.CreateUser(passwordHasher, $"admin@{domain}");
|
||||
var orgUser = organization.CreateOrganizationUser(user, orgKey);
|
||||
|
||||
var additionalUsers = new List<User>();
|
||||
var additionalOrgUsers = new List<OrganizationUser>();
|
||||
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);
|
||||
additionalOrgUsers.Add(organization.CreateOrganizationUser(additionalUser));
|
||||
additionalOrgUsers.Add(organization.CreateOrganizationUser(additionalUser, orgKey));
|
||||
}
|
||||
|
||||
//db.Add(organization);
|
||||
db.Add(organization);
|
||||
db.Add(user);
|
||||
//db.Add(orgUser);
|
||||
db.Add(orgUser);
|
||||
|
||||
db.SaveChanges();
|
||||
|
||||
// Use LinqToDB's BulkCopy for significant better performance
|
||||
//db.BulkCopy(additionalUsers);
|
||||
//db.BulkCopy(additionalOrgUsers);
|
||||
db.BulkCopy(additionalUsers);
|
||||
db.BulkCopy(additionalOrgUsers);
|
||||
|
||||
return organization.Id;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user