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:
@@ -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");
|
||||||
|
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
27
util/RustSdk/rust/Cargo.lock
generated
27
util/RustSdk/rust/Cargo.lock
generated
@@ -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",
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user