mod key_management;
use std::sync::Arc;
use crate::biome::key_management::store::KeyStore;
use crate::rest_api::{Resource, RestResourceProvider};
pub struct BiomeKeyManagementRestResourceProvider {
key_store: Arc<dyn KeyStore>,
}
impl BiomeKeyManagementRestResourceProvider {
pub fn new(key_store: Arc<dyn KeyStore>) -> Self {
Self { key_store }
}
}
impl RestResourceProvider for BiomeKeyManagementRestResourceProvider {
fn resources(&self) -> Vec<Resource> {
vec![
key_management::make_key_management_route(self.key_store.clone()),
key_management::make_key_management_route_with_public_key(self.key_store.clone()),
]
}
}
#[cfg(feature = "biome-credentials")]
#[cfg(test)]
mod tests {
use super::*;
use std::{panic, thread};
use reqwest::blocking::Client;
use crate::biome::{
credentials::rest_api::{
BiomeCredentialsRestConfigBuilder, BiomeCredentialsRestResourceProviderBuilder,
},
MemoryCredentialsStore, MemoryKeyStore, MemoryRefreshTokenStore,
};
#[cfg(feature = "authorization")]
use crate::error::InternalError;
use crate::rest_api::actix_web_1::{AuthConfig, RestApiBuilder, RestApiShutdownHandle};
#[cfg(feature = "authorization")]
use crate::rest_api::auth::{
authorization::{AuthorizationHandler, AuthorizationHandlerResult},
identity::Identity,
};
#[derive(Serialize)]
struct UsernamePassword {
pub username: String,
pub hashed_password: String,
}
#[derive(Deserialize)]
struct LoginResponse {
#[serde(rename = "message")]
pub _message: String,
#[serde(rename = "user_id")]
pub _user_id: String,
pub token: String,
#[serde(rename = "refresh_token")]
pub _refresh_token: String,
}
#[derive(Serialize, Debug, PartialEq, Eq)]
struct PostKey {
pub public_key: String,
pub encrypted_private_key: String,
pub display_name: String,
}
#[derive(Deserialize)]
struct Key {
pub public_key: String,
#[serde(rename = "user_id")]
pub _user_id: String,
pub display_name: String,
pub encrypted_private_key: String,
}
#[derive(Deserialize)]
struct PostKeyResponse {
#[serde(rename = "message")]
pub _message: String,
pub data: Key,
}
#[derive(Deserialize)]
struct GetKeyResponse {
pub data: Key,
}
#[derive(Deserialize)]
struct GetKeysResponse {
pub data: Vec<Key>,
}
#[derive(Deserialize, Serialize)]
struct PatchKey {
pub public_key: String,
pub new_display_name: String,
}
fn start_biome_rest_api() -> (RestApiShutdownHandle, thread::JoinHandle<()>) {
let refresh_token_store = MemoryRefreshTokenStore::new();
let cred_store = MemoryCredentialsStore::new();
let key_store = MemoryKeyStore::new(cred_store.clone());
let config = BiomeCredentialsRestConfigBuilder::default()
.with_password_encryption_cost("low")
.build()
.unwrap();
let biome_credentials_resource_provider =
BiomeCredentialsRestResourceProviderBuilder::default()
.with_refresh_token_store(refresh_token_store)
.with_credentials_store(cred_store)
.with_credentials_config(config)
.with_key_store(key_store.clone())
.build()
.unwrap();
let biome_key_management_resource_provider =
BiomeKeyManagementRestResourceProvider::new(Arc::new(key_store));
let mut rest_api_builder = RestApiBuilder::new();
#[cfg(not(feature = "https-bind"))]
let bind = "127.0.0.1:0";
#[cfg(feature = "https-bind")]
let bind = crate::rest_api::BindConfig::Http("127.0.0.1:0".into());
rest_api_builder = rest_api_builder
.with_bind(bind)
.with_auth_configs(vec![AuthConfig::Biome {
biome_credentials_resource_provider,
}])
.add_resources(biome_key_management_resource_provider.resources());
#[cfg(feature = "authorization")]
{
rest_api_builder = rest_api_builder
.with_authorization_handlers(vec![Box::new(AlwaysAllowAuthorizationHandler)]);
}
rest_api_builder.build().unwrap().run().unwrap()
}
#[cfg(feature = "authorization")]
#[derive(Clone)]
struct AlwaysAllowAuthorizationHandler;
#[cfg(feature = "authorization")]
impl AuthorizationHandler for AlwaysAllowAuthorizationHandler {
fn has_permission(
&self,
_identity: &Identity,
_permission_id: &str,
) -> Result<AuthorizationHandlerResult, InternalError> {
Ok(AuthorizationHandlerResult::Allow)
}
fn clone_box(&self) -> Box<dyn AuthorizationHandler> {
Box::new(self.clone())
}
}
fn create_and_authorize_user(
url: &str,
client: &Client,
username: &str,
password: &str,
) -> LoginResponse {
let registration_response = client
.post(&format!("{}/biome/register", url))
.json(&UsernamePassword {
username: username.to_string(),
hashed_password: password.to_string(),
})
.send()
.unwrap();
assert!(registration_response.status().is_success());
let login_response = client
.post(&format!("{}/biome/login", url))
.json(&UsernamePassword {
username: username.to_string(),
hashed_password: password.to_string(),
})
.send()
.unwrap();
assert!(login_response.status().is_success());
login_response.json::<LoginResponse>().unwrap()
}
fn run_test<F>(f: F)
where
F: FnOnce(&str, Client) -> () + panic::UnwindSafe,
{
let (handle, join_handle) = start_biome_rest_api();
let port_no = handle.port_numbers()[0];
let result = panic::catch_unwind(move || {
let client = Client::new();
f(&format!("http://127.0.0.1:{}", port_no), client)
});
handle.shutdown().unwrap();
join_handle.join().unwrap();
assert!(result.is_ok());
}
#[test]
fn test_post_key() {
run_test(|url, client| {
let login =
create_and_authorize_user(url, &client, "test_post_key@gmail.com", "Admin2193!");
let expected_key = PostKey {
public_key: "<public_key>".to_string(),
encrypted_private_key: "<private_key>".to_string(),
display_name: "test_post_key@gmail.com".to_string(),
};
let key = client
.post(&format!("{}/biome/keys", url))
.header("Authorization", format!("Bearer {}", login.token))
.json(&expected_key)
.send()
.unwrap()
.json::<PostKeyResponse>()
.unwrap();
assert_eq!(expected_key.public_key, key.data.public_key);
assert_eq!(
expected_key.encrypted_private_key,
key.data.encrypted_private_key
);
assert_eq!(expected_key.display_name, key.data.display_name);
})
}
#[test]
fn test_put_key() {
run_test(|url, client| {
let login =
create_and_authorize_user(url, &client, "test_post_key@gmail.com", "Admin2193!");
let expected_key = PostKey {
public_key: "<public_key>".to_string(),
encrypted_private_key: "<private_key>".to_string(),
display_name: "test_post_key@gmail.com".to_string(),
};
let key = client
.post(&format!("{}/biome/keys", url))
.header("Authorization", format!("Bearer {}", login.token))
.json(&expected_key)
.send()
.unwrap()
.json::<PostKeyResponse>()
.unwrap();
assert_eq!(expected_key.public_key, key.data.public_key);
let expected_keys: Vec<PostKey> = vec![
PostKey {
public_key: "<public_key2>".to_string(),
encrypted_private_key: "<private_key2>".to_string(),
display_name: "test_post_key2@gmail.com".to_string(),
},
PostKey {
public_key: "<public_key3>".to_string(),
encrypted_private_key: "<private_key3>".to_string(),
display_name: "test_post_key3@gmail.com".to_string(),
},
PostKey {
public_key: "<public_key4>".to_string(),
encrypted_private_key: "<private_key4>".to_string(),
display_name: "test_post_key4@gmail.com".to_string(),
},
];
assert_eq!(
client
.put(&format!("{}/biome/keys", url))
.header("Authorization", format!("Bearer {}", login.token))
.json(&expected_keys)
.send()
.unwrap()
.status()
.as_u16(),
200
);
let get_keys_response = client
.get(&format!("{}/biome/keys", url))
.header("Authorization", format!("Bearer {}", login.token))
.send()
.unwrap();
assert_eq!(get_keys_response.status().as_u16(), 200);
let mut actual_keys = get_keys_response
.json::<GetKeysResponse>()
.unwrap()
.data
.into_iter()
.map(|key| PostKey {
public_key: key.public_key,
encrypted_private_key: key.encrypted_private_key,
display_name: key.display_name,
})
.collect::<Vec<PostKey>>();
actual_keys.sort_by(|a, b| a.public_key.partial_cmp(&b.public_key).unwrap());
assert_eq!(actual_keys, expected_keys);
})
}
#[test]
fn test_put_key_version() {
run_test(|url, client| {
let login =
create_and_authorize_user(url, &client, "test_post_key@gmail.com", "Admin2193!");
let test_keys: Vec<PostKey> = vec![PostKey {
public_key: "<public_key>".to_string(),
encrypted_private_key: "<private_key>".to_string(),
display_name: "test_post_key@gmail.com".to_string(),
}];
assert_eq!(
client
.put(&format!("{}/biome/keys", url))
.header("Authorization", format!("Bearer {}", login.token))
.header("SplinterProtocolVersion", "2")
.json(&test_keys)
.send()
.unwrap()
.status()
.as_u16(),
200
);
assert_eq!(
client
.put(&format!("{}/biome/keys", url))
.header("Authorization", format!("Bearer {}", login.token))
.header("SplinterProtocolVersion", "1")
.json(&test_keys)
.send()
.unwrap()
.status()
.as_u16(),
400
);
});
}
#[test]
fn test_get_keys_pub_key() {
run_test(|url, client| {
let login = create_and_authorize_user(
url,
&client,
"test_get_keys_pub_key@gmail.com",
"Admin2193!",
);
let expected_key = PostKey {
public_key: "<public_key>".to_string(),
encrypted_private_key: "<private_key>".to_string(),
display_name: "test_get_keys_pub@gmail.com".to_string(),
};
let created_key_response = client
.post(&format!("{}/biome/keys", url))
.header("Authorization", format!("Bearer {}", login.token))
.json(&expected_key)
.send()
.unwrap();
assert_eq!(created_key_response.status().as_u16(), 200);
let created_key = created_key_response.json::<PostKeyResponse>().unwrap();
let get_key_response = client
.get(&format!(
"{}/biome/keys/{}",
url, created_key.data.public_key
))
.header("Authorization", format!("Bearer {}", login.token))
.send()
.unwrap();
assert_eq!(get_key_response.status().as_u16(), 200);
let actual_key = get_key_response.json::<GetKeyResponse>().unwrap();
assert_eq!(expected_key.public_key, actual_key.data.public_key);
assert_eq!(
expected_key.encrypted_private_key,
actual_key.data.encrypted_private_key
);
assert_eq!(expected_key.display_name, actual_key.data.display_name);
})
}
#[test]
fn test_get_keys() {
run_test(|url, client| {
let login =
create_and_authorize_user(url, &client, "test_get_keys@gmail.com", "Admin2193!");
let expected_key = PostKey {
public_key: "<public_key>".to_string(),
encrypted_private_key: "<private_key>".to_string(),
display_name: "test_get_keys_pub@gmail.com".to_string(),
};
assert_eq!(
client
.post(&format!("{}/biome/keys", url))
.header("Authorization", format!("Bearer {}", login.token))
.json(&expected_key)
.send()
.unwrap()
.status()
.as_u16(),
200
);
let get_keys_response = client
.get(&format!("{}/biome/keys", url))
.header("Authorization", format!("Bearer {}", login.token))
.send()
.unwrap();
assert_eq!(get_keys_response.status().as_u16(), 200);
let actual_keys = get_keys_response.json::<GetKeysResponse>().unwrap();
assert!(actual_keys.data.iter().any(|key| {
expected_key.public_key == key.public_key
&& expected_key.encrypted_private_key == key.encrypted_private_key
&& expected_key.display_name == key.display_name
}));
})
}
#[test]
fn test_patch_keys() {
run_test(|url, client| {
let login =
create_and_authorize_user(url, &client, "test_patch_keys@gmail.com", "Admin2193!");
assert_eq!(
client
.post(&format!("{}/biome/keys", url))
.header("Authorization", format!("Bearer {}", login.token))
.json(&PostKey {
public_key: "<public_key>".to_string(),
encrypted_private_key: "<private_key>".to_string(),
display_name: "test_patch_keys@gmail.com".to_string(),
})
.send()
.unwrap()
.status()
.as_u16(),
200
);
assert_eq!(
client
.patch(&format!("{}/biome/keys", url))
.header("Authorization", format!("Bearer {}", login.token))
.json(&PatchKey {
public_key: "<public_key>".to_string(),
new_display_name: "new_test_patch_keys@gmail.com".to_string(),
})
.send()
.unwrap()
.status()
.as_u16(),
200
);
let expected_key = PostKey {
public_key: "<public_key>".to_string(),
encrypted_private_key: "<private_key>".to_string(),
display_name: "new_test_patch_keys@gmail.com".to_string(),
};
let get_key_response = client
.get(&format!("{}/biome/keys/{}", url, expected_key.public_key))
.header("Authorization", format!("Bearer {}", login.token))
.send()
.unwrap();
assert_eq!(get_key_response.status().as_u16(), 200);
let actual_key = get_key_response.json::<GetKeyResponse>().unwrap();
assert_eq!(expected_key.public_key, actual_key.data.public_key);
assert_eq!(
expected_key.encrypted_private_key,
actual_key.data.encrypted_private_key
);
assert_eq!(expected_key.display_name, actual_key.data.display_name);
})
}
#[test]
fn test_delete_key() {
run_test(|url, client| {
let login =
create_and_authorize_user(url, &client, "test_delete_key@gmail.com", "Admin2193!");
let new_key = PostKey {
public_key: "<public_key>".to_string(),
encrypted_private_key: "<private_key>".to_string(),
display_name: "test_delete_key@gmail.com".to_string(),
};
assert_eq!(
client
.post(&format!("{}/biome/keys", url))
.header("Authorization", format!("Bearer {}", login.token))
.json(&new_key)
.send()
.unwrap()
.status()
.as_u16(),
200
);
assert_eq!(
client
.get(&format!("{}/biome/keys/{}", url, new_key.public_key))
.header("Authorization", format!("Bearer {}", login.token))
.send()
.unwrap()
.status()
.as_u16(),
200
);
assert_eq!(
client
.delete(&format!("{}/biome/keys/{}", url, new_key.public_key))
.header("Authorization", format!("Bearer {}", login.token))
.send()
.unwrap()
.status()
.as_u16(),
200
);
assert_eq!(
client
.get(&format!("{}/biome/keys/{}", url, new_key.public_key))
.header("Authorization", format!("Bearer {}", login.token))
.send()
.unwrap()
.status()
.as_u16(),
404
);
});
}
}