pub(in crate::biome) mod models;
mod operations;
mod schema;
use std::sync::{Arc, RwLock};
use diesel::r2d2::{ConnectionManager, Pool};
#[cfg(feature = "biome-credentials")]
use crate::biome::credentials::store::PasswordEncryptionCost;
use crate::biome::key_management::store::{KeyStore, KeyStoreError};
use crate::biome::key_management::Key;
use crate::store::pool::ConnectionPool;
#[cfg(feature = "biome-credentials")]
use operations::update_keys_and_password::KeyStoreUpdateKeysAndPasswordOperation as _;
use operations::{
fetch_key::KeyStoreFetchKeyOperation as _, insert_key::KeyStoreInsertKeyOperation as _,
list_keys::KeyStoreListKeysOperation as _, list_keys::KeyStoreListKeysWithUserIdOperation as _,
remove_key::KeyStoreRemoveKeyOperation as _, update_key::KeyStoreUpdateKeyOperation as _,
KeyStoreOperations,
};
pub struct DieselKeyStore<C: diesel::Connection + 'static> {
connection_pool: ConnectionPool<C>,
}
impl<C: diesel::Connection> DieselKeyStore<C> {
pub fn new(connection_pool: Pool<ConnectionManager<C>>) -> Self {
DieselKeyStore {
connection_pool: connection_pool.into(),
}
}
pub fn new_with_write_exclusivity(
connection_pool: Arc<RwLock<Pool<ConnectionManager<C>>>>,
) -> Self {
Self {
connection_pool: connection_pool.into(),
}
}
}
#[cfg(feature = "postgres")]
impl KeyStore for DieselKeyStore<diesel::pg::PgConnection> {
fn add_key(&self, key: Key) -> Result<(), KeyStoreError> {
self.connection_pool
.execute_write(|conn| KeyStoreOperations::new(conn).insert_key(key))
}
fn update_key(
&self,
public_key: &str,
user_id: &str,
new_display_name: &str,
) -> Result<(), KeyStoreError> {
self.connection_pool.execute_write(|conn| {
KeyStoreOperations::new(conn).update_key(public_key, user_id, new_display_name)
})
}
fn remove_key(&self, public_key: &str, user_id: &str) -> Result<Key, KeyStoreError> {
self.connection_pool
.execute_write(|conn| KeyStoreOperations::new(conn).remove_key(public_key, user_id))
}
fn fetch_key(&self, public_key: &str, user_id: &str) -> Result<Key, KeyStoreError> {
self.connection_pool
.execute_read(|conn| KeyStoreOperations::new(conn).fetch_key(public_key, user_id))
}
fn list_keys(&self, user_id: Option<&str>) -> Result<Vec<Key>, KeyStoreError> {
self.connection_pool.execute_read(|conn| match user_id {
Some(user_id) => KeyStoreOperations::new(conn).list_keys_with_user_id(user_id),
None => KeyStoreOperations::new(conn).list_keys(),
})
}
#[cfg(feature = "biome-credentials")]
fn update_keys_and_password(
&self,
user_id: &str,
updated_password: &str,
password_encryption_cost: PasswordEncryptionCost,
keys: &[Key],
) -> Result<(), KeyStoreError> {
self.connection_pool.execute_write(|conn| {
KeyStoreOperations::new(conn).update_keys_and_password(
user_id,
updated_password,
password_encryption_cost,
keys,
)
})
}
}
#[cfg(feature = "sqlite")]
impl KeyStore for DieselKeyStore<diesel::sqlite::SqliteConnection> {
fn add_key(&self, key: Key) -> Result<(), KeyStoreError> {
self.connection_pool
.execute_write(|conn| KeyStoreOperations::new(conn).insert_key(key))
}
fn update_key(
&self,
public_key: &str,
user_id: &str,
new_display_name: &str,
) -> Result<(), KeyStoreError> {
self.connection_pool.execute_write(|conn| {
KeyStoreOperations::new(conn).update_key(public_key, user_id, new_display_name)
})
}
fn remove_key(&self, public_key: &str, user_id: &str) -> Result<Key, KeyStoreError> {
self.connection_pool
.execute_write(|conn| KeyStoreOperations::new(conn).remove_key(public_key, user_id))
}
fn fetch_key(&self, public_key: &str, user_id: &str) -> Result<Key, KeyStoreError> {
self.connection_pool
.execute_read(|conn| KeyStoreOperations::new(conn).fetch_key(public_key, user_id))
}
fn list_keys(&self, user_id: Option<&str>) -> Result<Vec<Key>, KeyStoreError> {
self.connection_pool.execute_read(|conn| match user_id {
Some(user_id) => KeyStoreOperations::new(conn).list_keys_with_user_id(user_id),
None => KeyStoreOperations::new(conn).list_keys(),
})
}
#[cfg(feature = "biome-credentials")]
fn update_keys_and_password(
&self,
user_id: &str,
updated_password: &str,
password_encryption_cost: PasswordEncryptionCost,
keys: &[Key],
) -> Result<(), KeyStoreError> {
self.connection_pool.execute_write(|conn| {
KeyStoreOperations::new(conn).update_keys_and_password(
user_id,
updated_password,
password_encryption_cost,
keys,
)
})
}
}
#[cfg(all(test, feature = "sqlite"))]
pub mod tests {
use super::*;
#[cfg(feature = "biome-credentials")]
use crate::biome::credentials::store::{
diesel::DieselCredentialsStore, CredentialsBuilder, CredentialsStore,
PasswordEncryptionCost,
};
use crate::migrations::run_sqlite_migrations;
use diesel::{
r2d2::{ConnectionManager, Pool},
sqlite::SqliteConnection,
};
#[test]
fn sqlite_add_and_fetch() {
let pool = create_connection_pool_and_migrate();
let store = DieselKeyStore::new(pool);
let key1 = Key::new("pubkey1", "privkey1", "user1", "name1");
store.add_key(key1.clone()).expect("Failed to add key1");
let key2 = Key::new("pubkey2", "privkey2", "user1", "name2");
store.add_key(key2.clone()).expect("Failed to add key2");
let key3 = Key::new("pubkey3", "privkey3", "user2", "name3");
store.add_key(key3.clone()).expect("Failed to add key3");
assert_eq!(
store
.fetch_key("pubkey1", "user1")
.expect("Failed to fetch key1"),
key1,
);
assert_eq!(
store
.fetch_key("pubkey2", "user1")
.expect("Failed to fetch key2"),
key2,
);
assert_eq!(
store
.fetch_key("pubkey3", "user2")
.expect("Failed to fetch key3"),
key3,
);
match store.fetch_key("pubkey4", "user4") {
Err(KeyStoreError::NotFoundError(_)) => {}
res => panic!(
"Expected Err(KeyStoreError::NotFoundError), got {:?} instead",
res
),
}
match store.fetch_key("pubkey1", "user2") {
Err(KeyStoreError::NotFoundError(_)) => {}
res => panic!(
"Expected Err(KeyStoreError::NotFoundError), got {:?} instead",
res
),
}
}
#[test]
fn sqlite_list() {
let pool = create_connection_pool_and_migrate();
let store = DieselKeyStore::new(pool);
let key1 = Key::new("pubkey1", "privkey1", "user1", "name1");
store.add_key(key1.clone()).expect("Failed to add key1");
let key2 = Key::new("pubkey2", "privkey2", "user1", "name2");
store.add_key(key2.clone()).expect("Failed to add key2");
let key3 = Key::new("pubkey3", "privkey3", "user2", "name3");
store.add_key(key3.clone()).expect("Failed to add key3");
let keys = store
.list_keys(None)
.expect("Failed to list keys for user1");
assert_eq!(keys.len(), 3);
assert!(keys.contains(&key1));
assert!(keys.contains(&key2));
assert!(keys.contains(&key3));
let keys = store
.list_keys(Some("user1"))
.expect("Failed to list keys for user1");
assert_eq!(keys.len(), 2);
assert!(keys.contains(&key1));
assert!(keys.contains(&key2));
let keys = store
.list_keys(Some("user2"))
.expect("Failed to list keys for user2");
assert_eq!(keys.len(), 1);
assert!(keys.contains(&key3));
let keys = store
.list_keys(Some("user3"))
.expect("Failed to list keys for user3");
assert!(keys.is_empty());
}
#[test]
fn sqlite_update_key() {
let pool = create_connection_pool_and_migrate();
let store = DieselKeyStore::new(pool);
let mut key = Key::new("pubkey", "privkey", "user", "name1");
store.add_key(key.clone()).expect("Failed to add key");
assert_eq!(
store
.fetch_key("pubkey", "user")
.expect("Failed to fetch key"),
key,
);
key.display_name = "name2".into();
store
.update_key("pubkey", "user", "name2")
.expect("Failed to update key");
assert_eq!(
store
.fetch_key("pubkey", "user")
.expect("Failed to fetch key"),
key,
);
}
#[test]
fn sqlite_remove() {
let pool = create_connection_pool_and_migrate();
let store = DieselKeyStore::new(pool);
let key1 = Key::new("pubkey1", "privkey1", "user1", "name1");
store.add_key(key1.clone()).expect("Failed to add key1");
let key2 = Key::new("pubkey2", "privkey2", "user1", "name2");
store.add_key(key2.clone()).expect("Failed to add key2");
let key3 = Key::new("pubkey3", "privkey3", "user2", "name3");
store.add_key(key3.clone()).expect("Failed to add key3");
store
.remove_key("pubkey3", "user2")
.expect("Failed to remove key3");
match store.fetch_key("pubkey3", "user2") {
Err(KeyStoreError::NotFoundError(_)) => {}
res => panic!(
"Expected Err(KeyStoreError::NotFoundError), got {:?} instead",
res
),
}
assert!(store
.list_keys(Some("user2"))
.expect("Failed to list keys")
.is_empty());
}
#[cfg(feature = "biome-credentials")]
#[test]
fn sqlite_update_keys_and_password() {
let pool = create_connection_pool_and_migrate();
let cred_store = DieselCredentialsStore::new(pool.clone());
let key_store = DieselKeyStore::new(pool);
let key1 = Key::new("pubkey1", "privkey1", "user", "name1");
key_store.add_key(key1.clone()).expect("Failed to add key1");
assert_eq!(
key_store
.fetch_key("pubkey1", "user")
.expect("Failed to fetch key1"),
key1,
);
let cred = CredentialsBuilder::default()
.with_user_id("user")
.with_username("username")
.with_password("pwd1")
.with_password_encryption_cost(PasswordEncryptionCost::Low)
.build()
.expect("Failed to build cred");
cred_store
.add_credentials(cred.clone())
.expect("Failed to add cred");
assert_eq!(
cred_store
.fetch_credential_by_user_id("user")
.expect("Failed to fetch cred"),
cred,
);
let key2 = Key::new("pubkey2", "privkey2", "user", "name2");
let key3 = Key::new("pubkey3", "privkey3", "user", "name3");
key_store
.update_keys_and_password(
"user",
"pwd2",
PasswordEncryptionCost::Low,
&[key2.clone(), key3.clone()],
)
.expect("Failed to update keys and password");
let keys = key_store
.list_keys(Some("user"))
.expect("Failed to list keys");
assert_eq!(keys.len(), 2);
assert!(keys.contains(&key2));
assert!(keys.contains(&key3));
let cred = cred_store
.fetch_credential_by_user_id("user")
.expect("Failed to fetch cred");
assert!(cred
.verify_password("pwd2")
.expect("Failed to verify password"));
}
fn create_connection_pool_and_migrate() -> Pool<ConnectionManager<SqliteConnection>> {
let connection_manager = ConnectionManager::<SqliteConnection>::new(":memory:");
let pool = Pool::builder()
.max_size(1)
.build(connection_manager)
.expect("Failed to build connection pool");
run_sqlite_migrations(&*pool.get().expect("Failed to get connection for migrations"))
.expect("Failed to run migrations");
pool
}
}