diff --git a/.github/scripts/cargo_build.sh b/.github/scripts/cargo_build.sh index da21b06..9ad2dfa 100644 --- a/.github/scripts/cargo_build.sh +++ b/.github/scripts/cargo_build.sh @@ -24,7 +24,9 @@ fi rustup target add "$TARGET" # shellcheck disable=SC2086 -cargo build --target $TARGET $RELEASE $FEATURES +cargo build --target $TARGET $RELEASE + +export RUST_LOG="cosmian_findex_cli=trace,cosmian_findex_server=trace,test_findex_server=trace" # shellcheck disable=SC2086 cargo test --target $TARGET $RELEASE --workspace -- --nocapture $SKIP_SERVICES_TESTS diff --git a/.github/workflows/build_all.yml b/.github/workflows/build_all.yml index a73e182..b93f04f 100644 --- a/.github/workflows/build_all.yml +++ b/.github/workflows/build_all.yml @@ -39,7 +39,7 @@ jobs: archive-name: ${{ matrix.archive-name }} target: ${{ matrix.target }} debug_or_release: ${{ inputs.debug_or_release }} - skip_services_tests: --skip test_findex + skip_services_tests: --skip test_findex --skip test_all_authentications --skip test_server_auth_matrix generic-macos: strategy: @@ -58,7 +58,7 @@ jobs: archive-name: ${{ matrix.archive-name }} target: ${{ matrix.target }} debug_or_release: ${{ inputs.debug_or_release }} - skip_services_tests: --skip test_findex + skip_services_tests: --skip test_findex --skip test_all_authentications --skip test_server_auth_matrix cleanup: needs: diff --git a/Cargo.lock b/Cargo.lock index 575c3ad..3965d74 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -837,6 +837,7 @@ dependencies = [ "tokio", "tracing", "url", + "uuid", "x509-parser", ] diff --git a/Cargo.toml b/Cargo.toml index 7f4d17b..2293817 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,3 +59,4 @@ tracing = "0.1" url = "2.5" x509-parser = "0.16" zeroize = { version = "1.8", default-features = false } +uuid = { version = "1.10", features = ["v4"] } diff --git a/crate/cli/Cargo.toml b/crate/cli/Cargo.toml index 835e658..795a77e 100644 --- a/crate/cli/Cargo.toml +++ b/crate/cli/Cargo.toml @@ -33,8 +33,8 @@ clap = { workspace = true, features = [ "cargo", ] } cloudproof_findex = { workspace = true, features = ["rest-interface"] } -cosmian_rest_client = { path = "../client" } cosmian_logger = { path = "../logger" } +cosmian_rest_client = { path = "../client" } csv = "1.3.0" der = { workspace = true, features = ["pem"] } hex = "0.4" @@ -58,4 +58,5 @@ predicates = "3.1" regex = { version = "1.11", default-features = false } tempfile = "3.13" test_findex_server = { path = "../test_server" } +uuid = { workspace = true, features = ["v4"] } x509-parser = { workspace = true, features = ["verify"] } diff --git a/crate/cli/src/actions/findex/search.rs b/crate/cli/src/actions/findex/search.rs index 44cb774..722f6be 100644 --- a/crate/cli/src/actions/findex/search.rs +++ b/crate/cli/src/actions/findex/search.rs @@ -26,8 +26,8 @@ impl SearchAction { /// /// # Arguments /// - /// * `rest_client` - The Findex server client instance used to - /// communicate with the Findex server server. + /// * `rest_client` - The Findex server client instance used to communicate + /// with the Findex server server. /// /// # Errors /// diff --git a/crate/cli/src/actions/mod.rs b/crate/cli/src/actions/mod.rs index 876c762..802bc76 100644 --- a/crate/cli/src/actions/mod.rs +++ b/crate/cli/src/actions/mod.rs @@ -1,7 +1,7 @@ -pub mod access; pub mod console; pub mod findex; pub mod login; pub mod logout; pub mod markdown; +pub mod permissions; pub mod version; diff --git a/crate/cli/src/actions/access.rs b/crate/cli/src/actions/permissions.rs similarity index 89% rename from crate/cli/src/actions/access.rs rename to crate/cli/src/actions/permissions.rs index 89628fd..2ca83f9 100644 --- a/crate/cli/src/actions/access.rs +++ b/crate/cli/src/actions/permissions.rs @@ -1,6 +1,5 @@ use clap::Parser; use cosmian_rest_client::RestClient; -use tracing::trace; use crate::{ actions::console, @@ -41,7 +40,7 @@ impl AccessAction { pub struct CreateAccess; impl CreateAccess { - /// Create a new Index with a default `admin` role. + /// Create a new Index with a default `admin` permission. /// /// Generates an unique index ID which is returned to the owner. /// This ID will be shared between several users that will be able to: @@ -50,8 +49,8 @@ impl CreateAccess { /// /// # Arguments /// - /// * `rest_client` - A reference to the Findex client used to - /// communicate with the Findex server. + /// * `rest_client` - A reference to the Findex client used to communicate + /// with the Findex server. /// /// # Errors /// @@ -61,8 +60,7 @@ impl CreateAccess { .create_access() .await .with_context(|| "Can't execute the create access query on the findex server")?; - - trace!("cli: New access successfully created: {}", response.success); + // should replace the user configuration file console::Stdout::new(&response.success).write()?; Ok(response.success) @@ -86,9 +84,9 @@ pub struct GrantAccess { #[clap(long, required = true)] pub index_id: String, - /// The role to grant (`reader`, `writer`, `admin`) + /// The permission to grant (`reader`, `writer`, `admin`) #[clap(long, required = true)] - pub role: String, + pub permission: String, } impl GrantAccess { @@ -96,15 +94,15 @@ impl GrantAccess { /// /// # Arguments /// - /// * `rest_client` - A reference to the Findex client used to - /// communicate with the Findex server. + /// * `rest_client` - A reference to the Findex client used to communicate + /// with the Findex server. /// /// # Errors /// /// Returns an error if the query execution on the Findex server fails. pub async fn run(&self, rest_client: RestClient) -> CliResult { let response = rest_client - .grant_access(&self.user, &self.role, &self.index_id) + .grant_access(&self.user, &self.permission, &self.index_id) .await .with_context(|| "Can't execute the grant access query on the findex server")?; @@ -133,8 +131,8 @@ impl RevokeAccess { /// /// # Arguments /// - /// * `rest_client` - A reference to the Findex client used to - /// communicate with the Findex server. + /// * `rest_client` - A reference to the Findex client used to communicate + /// with the Findex server. /// /// # Errors /// diff --git a/crate/cli/src/actions/version.rs b/crate/cli/src/actions/version.rs index 237e190..c5ea090 100644 --- a/crate/cli/src/actions/version.rs +++ b/crate/cli/src/actions/version.rs @@ -14,8 +14,8 @@ impl ServerVersionAction { /// /// # Arguments /// - /// * `rest_client` - The Findex server client instance used to - /// communicate with the Findex server server. + /// * `rest_client` - The Findex server client instance used to communicate + /// with the Findex server server. /// /// # Errors /// diff --git a/crate/cli/src/error/mod.rs b/crate/cli/src/error/mod.rs index faf0f91..038ccec 100644 --- a/crate/cli/src/error/mod.rs +++ b/crate/cli/src/error/mod.rs @@ -45,7 +45,7 @@ pub enum CliError { Unauthorized(String), // A cryptographic error - #[error("Cryptographic error: {0}")] + #[error("CLI Cryptographic error: {0}")] Cryptographic(String), // Conversion errors diff --git a/crate/cli/src/main.rs b/crate/cli/src/main.rs index 56dd0a9..a20db72 100644 --- a/crate/cli/src/main.rs +++ b/crate/cli/src/main.rs @@ -3,11 +3,11 @@ use std::{path::PathBuf, process}; use clap::{CommandFactory, Parser, Subcommand}; use cosmian_findex_cli::{ actions::{ - access::AccessAction, findex::{add_or_delete::AddOrDeleteAction, search::SearchAction}, login::LoginAction, logout::LogoutAction, markdown::MarkdownAction, + permissions::AccessAction, version::ServerVersionAction, }, error::result::CliResult, diff --git a/crate/cli/src/tests/auth_tests.rs b/crate/cli/src/tests/auth_tests.rs index 857e571..6ed897b 100644 --- a/crate/cli/src/tests/auth_tests.rs +++ b/crate/cli/src/tests/auth_tests.rs @@ -14,7 +14,7 @@ use tracing::{info, trace}; use crate::{error::result::CliResult, tests::PROG_NAME}; // let us not make other test cases fail -const PORT: u16 = 9999; +const PORT: u16 = 6666; #[tokio::test] #[allow(clippy::needless_return)] diff --git a/crate/cli/src/tests/findex/mod.rs b/crate/cli/src/tests/findex/mod.rs index 3dda429..1a815f1 100644 --- a/crate/cli/src/tests/findex/mod.rs +++ b/crate/cli/src/tests/findex/mod.rs @@ -5,14 +5,15 @@ use test_findex_server::{ start_default_test_findex_server, start_default_test_findex_server_with_cert_auth, }; use tracing::trace; +use uuid::Uuid; use crate::{ actions::{ - access::{GrantAccess, RevokeAccess}, findex::{add_or_delete::AddOrDeleteAction, search::SearchAction, FindexParameters}, + permissions::{GrantAccess, RevokeAccess}, }, error::result::CliResult, - tests::access::{create_access_cmd, grant_access_cmd, revoke_access_cmd}, + tests::permissions::{create_access_cmd, grant_access_cmd, revoke_access_cmd}, }; pub(crate) mod add_or_delete; @@ -65,16 +66,17 @@ fn search(cli_conf_path: &str, index_id: &str) -> CliResult { } #[allow(clippy::panic_in_result_fn)] -fn findex(cli_conf_path: &str, index_id: &str) -> CliResult<()> { - // todo(manu): rename index_id to zone (or something else) +fn add_search_delete(cli_conf_path: &str, index_id: &str) -> CliResult<()> { add(cli_conf_path, index_id)?; + // make sure searching returns the expected results let search_results = search(cli_conf_path, index_id)?; assert!(search_results.contains("States9686")); // for Southborough assert!(search_results.contains("States14061")); // for Northbridge delete(cli_conf_path, index_id)?; + // make sure no results are returned after deletion let search_results = search(cli_conf_path, index_id)?; assert!(!search_results.contains("States9686")); // for Southborough assert!(!search_results.contains("States14061")); // for Northbridge @@ -86,7 +88,10 @@ fn findex(cli_conf_path: &str, index_id: &str) -> CliResult<()> { pub(crate) async fn test_findex_no_auth() -> CliResult<()> { log_init(None); let ctx = start_default_test_findex_server().await; - findex(&ctx.owner_client_conf_path, "my_owned_index")?; + add_search_delete( + &ctx.owner_client_conf_path, + Uuid::new_v4().to_string().as_str(), + )?; Ok(()) } @@ -96,9 +101,9 @@ pub(crate) async fn test_findex_cert_auth() -> CliResult<()> { let ctx = start_default_test_findex_server_with_cert_auth().await; let index_id = create_access_cmd(&ctx.owner_client_conf_path)?; - trace!("zone: {index_id}"); + trace!("index_id: {index_id}"); - findex(&ctx.owner_client_conf_path, &index_id)?; + add_search_delete(&ctx.owner_client_conf_path, &index_id)?; Ok(()) } @@ -119,7 +124,8 @@ pub(crate) async fn test_findex_grant_read_access() -> CliResult<()> { GrantAccess { user: "user.client@acme.com".to_owned(), index_id: index_id.clone(), - role: "reader".to_owned(), + permission: "reader".to_owned(), /* todo(manu): use a mutual struct between server and + * client */ }, )?; @@ -137,7 +143,7 @@ pub(crate) async fn test_findex_grant_read_access() -> CliResult<()> { GrantAccess { user: "user.client@acme.com".to_owned(), index_id: index_id.clone(), - role: "writer".to_owned(), + permission: "writer".to_owned(), }, )?; @@ -155,7 +161,7 @@ pub(crate) async fn test_findex_grant_read_access() -> CliResult<()> { GrantAccess { user: "user.client@acme.com".to_owned(), index_id: index_id.clone(), - role: "admin".to_owned(), + permission: "admin".to_owned(), }, ) .unwrap_err(); @@ -179,9 +185,6 @@ pub(crate) async fn test_findex_no_access() -> CliResult<()> { log_init(None); let ctx = start_default_test_findex_server_with_cert_auth().await; - assert!(findex(&ctx.user_client_conf_path, "whatever").is_err()); + assert!(add_search_delete(&ctx.user_client_conf_path, "whatever").is_err()); Ok(()) } - -// todo(manu): -// - grant_access twice diff --git a/crate/cli/src/tests/mod.rs b/crate/cli/src/tests/mod.rs index 8e65df1..cc31530 100644 --- a/crate/cli/src/tests/mod.rs +++ b/crate/cli/src/tests/mod.rs @@ -1,6 +1,6 @@ -mod access; mod auth_tests; mod findex; +mod permissions; mod utils; const PROG_NAME: &str = "cosmian_findex_cli"; diff --git a/crate/cli/src/tests/access.rs b/crate/cli/src/tests/permissions.rs similarity index 85% rename from crate/cli/src/tests/access.rs rename to crate/cli/src/tests/permissions.rs index b5e5a7c..efa85d8 100644 --- a/crate/cli/src/tests/access.rs +++ b/crate/cli/src/tests/permissions.rs @@ -6,7 +6,7 @@ use regex::{Regex, RegexBuilder}; use tracing::{debug, trace}; use crate::{ - actions::access::{GrantAccess, RevokeAccess}, + actions::permissions::{GrantAccess, RevokeAccess}, error::{result::CliResult, CliError}, tests::{utils::recover_cmd_logs, PROG_NAME}, }; @@ -14,7 +14,7 @@ use crate::{ /// Extract the `key_uid` (prefixed by a pattern) from a text #[allow(clippy::unwrap_used)] pub(crate) fn extract_uid<'a>(text: &'a str, pattern: &'a str) -> Option<&'a str> { - let formatted = format!(r"^\s*{pattern}: (?P.+?)[\s\.]*?$"); + let formatted = format!(r"\[\S+\] {pattern}: (?P[0-9a-fA-F-]+)"); let uid_regex: Regex = RegexBuilder::new(formatted.as_str()) .multi_line(true) .build() @@ -35,10 +35,11 @@ pub(crate) fn create_access_cmd(cli_conf_path: &str) -> CliResult { if output.status.success() { let findex_output = std::str::from_utf8(&output.stdout)?; trace!("findex_output: {}", findex_output); - let unique_identifier = extract_uid(findex_output, "New access successfully created") - .ok_or_else(|| { - CliError::Default("failed extracting the unique identifier".to_owned()) - })?; + let unique_identifier = extract_uid( + findex_output, + "New admin access successfully created on index", + ) + .ok_or_else(|| CliError::Default("failed extracting the unique identifier".to_owned()))?; return Ok(unique_identifier.to_owned()); } Err(CliError::Default( @@ -46,6 +47,7 @@ pub(crate) fn create_access_cmd(cli_conf_path: &str) -> CliResult { )) } +#[allow(dead_code)] pub(crate) fn grant_access_cmd(cli_conf_path: &str, action: GrantAccess) -> CliResult { let mut cmd = Command::cargo_bin(PROG_NAME)?; let args = vec![ @@ -54,8 +56,8 @@ pub(crate) fn grant_access_cmd(cli_conf_path: &str, action: GrantAccess) -> CliR action.user.clone(), "--index-id".to_owned(), action.index_id, - "--role".to_owned(), - action.role, + "--permission".to_owned(), + action.permission, ]; cmd.env(FINDEX_CLI_CONF_ENV, cli_conf_path); @@ -71,6 +73,7 @@ pub(crate) fn grant_access_cmd(cli_conf_path: &str, action: GrantAccess) -> CliR )) } +#[allow(dead_code)] pub(crate) fn revoke_access_cmd(cli_conf_path: &str, action: RevokeAccess) -> CliResult { let mut cmd = Command::cargo_bin(PROG_NAME)?; let args = vec![ diff --git a/crate/client/src/config.rs b/crate/client/src/config.rs index 5355be1..a9220e6 100644 --- a/crate/client/src/config.rs +++ b/crate/client/src/config.rs @@ -5,8 +5,6 @@ use std::{ path::PathBuf, }; -// #[cfg(target_os = "linux")] -// use log::info; use serde::{Deserialize, Serialize}; use tracing::info; @@ -84,23 +82,6 @@ pub struct Oauth2Conf { pub scopes: Vec, } -/// The configuration that is used by the google command -/// to perform actions over Gmail API. -#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] -pub struct GmailApiConf { - pub account_type: String, - pub project_id: String, - pub private_key_id: String, - pub private_key: String, - pub client_email: String, - pub client_id: String, - pub auth_uri: String, - pub token_uri: String, - pub auth_provider_x509_cert_url: String, - pub client_x509_cert_url: String, - pub universe_domain: String, -} - #[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] pub struct ClientConf { // accept_invalid_certs is useful if the cli needs to connect to an HTTPS Findex server @@ -294,6 +275,11 @@ impl ClientConf { let findex_server_url = findex_server_url.unwrap_or(&self.findex_server_url); let accept_invalid_certs = accept_invalid_certs.unwrap_or(self.accept_invalid_certs); + info!( + "Initializing Findex REST client with server URL: {findex_server_url}, \ + accept_invalid_certs: {accept_invalid_certs}" + ); + // Instantiate a Findex server REST client with the given configuration let rest_client = RestClient::instantiate( findex_server_url, @@ -326,18 +312,13 @@ mod tests { let conf_path = ClientConf::location(None).unwrap(); ClientConf::load(&conf_path).unwrap(); - // another valid conf - unsafe { - env::set_var(FINDEX_CLI_CONF_ENV, "test_data/configs/findex_partial.json"); - } - let conf_path = ClientConf::location(None).unwrap(); - ClientConf::load(&conf_path).unwrap(); - // Default conf file unsafe { env::remove_var(FINDEX_CLI_CONF_ENV); } - fs::remove_file(get_default_conf_path().unwrap()).unwrap(); + if get_default_conf_path().unwrap().exists() { + fs::remove_file(get_default_conf_path().unwrap()).unwrap(); + } let conf_path = ClientConf::location(None).unwrap(); ClientConf::load(&conf_path).unwrap(); assert!(get_default_conf_path().unwrap().exists()); diff --git a/crate/client/src/lib.rs b/crate/client/src/lib.rs index fdfb86d..aa289a4 100644 --- a/crate/client/src/lib.rs +++ b/crate/client/src/lib.rs @@ -49,7 +49,7 @@ clippy::significant_drop_tightening )] -pub use config::{ClientConf, GmailApiConf, FINDEX_CLI_CONF_ENV}; +pub use config::{ClientConf, FINDEX_CLI_CONF_ENV}; pub use error::ClientError; pub use file_utils::{ read_bytes_from_file, read_from_json_file, write_bytes_to_file, write_json_object_to_file, diff --git a/crate/client/src/rest_client.rs b/crate/client/src/rest_client.rs index 7ed71c9..b8056c2 100644 --- a/crate/client/src/rest_client.rs +++ b/crate/client/src/rest_client.rs @@ -117,10 +117,10 @@ impl RestClient { pub async fn grant_access( &self, user_id: &str, - role: &str, + permission: &str, index_id: &str, ) -> ClientResult { - let endpoint = format!("/access/grant/{user_id}/{role}/{index_id}"); + let endpoint = format!("/access/grant/{user_id}/{permission}/{index_id}"); let server_url = format!("{}{endpoint}", self.server_url); trace!("POST grant_access: {server_url}"); let response = self.client.post(server_url).send().await?; diff --git a/crate/client/test_data/configs/findex.json b/crate/client/test_data/configs/findex.json index 59029df..c6a6c46 100644 --- a/crate/client/test_data/configs/findex.json +++ b/crate/client/test_data/configs/findex.json @@ -1,4 +1,4 @@ { - "findex_server_url": "http://127.0.0.1:666{}", + "findex_server_url": "http://127.0.0.1:6660", "findex_access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjVVU1FrSVlULW9QMWZrcjQtNnRrciJ9.eyJuaWNrbmFtZSI6InRlY2giLCJuYW1lIjoidGVjaEBjb3NtaWFuLmNvbSIsInBpY3R1cmUiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci81MmZiMzFjOGNjYWQzNDU4MTIzZDRmYWQxNDA4NTRjZj9zPTQ4MCZyPXBnJmQ9aHR0cHMlM0ElMkYlMkZjZG4uYXV0aDAuY29tJTJGYXZhdGFycyUyRnRlLnBuZyIsInVwZGF0ZWRfYXQiOiIyMDIzLTA1LTMwVDA5OjMxOjExLjM4NloiLCJlbWFpbCI6InRlY2hAY29zbWlhbi5jb20iLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImlzcyI6Imh0dHBzOi8va21zLWNvc21pYW4uZXUuYXV0aDAuY29tLyIsImF1ZCI6IkszaXhldXhuVDVrM0Roa0tocWhiMXpYbjlFNjJGRXdJIiwiaWF0IjoxNjg1NDM5MDc0LCJleHAiOjE2ODU0NzUwNzQsInN1YiI6ImF1dGgwfDYzZDNkM2VhOTNmZjE2NDJjNzdkZjkyOCIsInNpZCI6ImJnVUNuTTNBRjVxMlpaVHFxMTZwclBCMi11Z0NNaUNPIiwibm9uY2UiOiJVRUZWTlZWeVluWTVUbHBwWjJScGNqSmtVMEZ4TmxkUFEwc3dTVGMwWHpaV2RVVmtkVnBEVGxSMldnPT0ifQ.HmU9fFwZ-JjJVlSy_PTei3ys0upeWQbWWiESmKBtRSClGnAXJNCpwuP4Jw7fgKn-8IBf-PYmP1_54u2Rw3RcJFVl7EblVoGMghYxVq5hViGpd00st3VwZmyCwOUz2CE5RBnBAoES4C8xA3zWg6oau0xjFQbC3jNU20eyFYMDewXA8UXCHQrEiQ56ylqSbyqlBbQIWbmOO4m5w2WDkx0bVyyJ893JfIJr_NANEQMJITYo8Mp_iHCyKp7llsfgCt07xN8ZqnsrMsJ15zC1n50bHGrTQisxURS1dpuFXF1hfrxhzogxYMX8CEISjsFgROjPY84GRMmvpYZfyaJbDDql3A" } diff --git a/crate/client/test_data/configs/findex_partial.json b/crate/client/test_data/configs/findex_partial.json deleted file mode 100644 index 59029df..0000000 --- a/crate/client/test_data/configs/findex_partial.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "findex_server_url": "http://127.0.0.1:666{}", - "findex_access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjVVU1FrSVlULW9QMWZrcjQtNnRrciJ9.eyJuaWNrbmFtZSI6InRlY2giLCJuYW1lIjoidGVjaEBjb3NtaWFuLmNvbSIsInBpY3R1cmUiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci81MmZiMzFjOGNjYWQzNDU4MTIzZDRmYWQxNDA4NTRjZj9zPTQ4MCZyPXBnJmQ9aHR0cHMlM0ElMkYlMkZjZG4uYXV0aDAuY29tJTJGYXZhdGFycyUyRnRlLnBuZyIsInVwZGF0ZWRfYXQiOiIyMDIzLTA1LTMwVDA5OjMxOjExLjM4NloiLCJlbWFpbCI6InRlY2hAY29zbWlhbi5jb20iLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImlzcyI6Imh0dHBzOi8va21zLWNvc21pYW4uZXUuYXV0aDAuY29tLyIsImF1ZCI6IkszaXhldXhuVDVrM0Roa0tocWhiMXpYbjlFNjJGRXdJIiwiaWF0IjoxNjg1NDM5MDc0LCJleHAiOjE2ODU0NzUwNzQsInN1YiI6ImF1dGgwfDYzZDNkM2VhOTNmZjE2NDJjNzdkZjkyOCIsInNpZCI6ImJnVUNuTTNBRjVxMlpaVHFxMTZwclBCMi11Z0NNaUNPIiwibm9uY2UiOiJVRUZWTlZWeVluWTVUbHBwWjJScGNqSmtVMEZ4TmxkUFEwc3dTVGMwWHpaV2RVVmtkVnBEVGxSMldnPT0ifQ.HmU9fFwZ-JjJVlSy_PTei3ys0upeWQbWWiESmKBtRSClGnAXJNCpwuP4Jw7fgKn-8IBf-PYmP1_54u2Rw3RcJFVl7EblVoGMghYxVq5hViGpd00st3VwZmyCwOUz2CE5RBnBAoES4C8xA3zWg6oau0xjFQbC3jNU20eyFYMDewXA8UXCHQrEiQ56ylqSbyqlBbQIWbmOO4m5w2WDkx0bVyyJ893JfIJr_NANEQMJITYo8Mp_iHCyKp7llsfgCt07xN8ZqnsrMsJ15zC1n50bHGrTQisxURS1dpuFXF1hfrxhzogxYMX8CEISjsFgROjPY84GRMmvpYZfyaJbDDql3A" -} diff --git a/crate/server/Cargo.toml b/crate/server/Cargo.toml index a8c8d5b..8ca1e12 100644 --- a/crate/server/Cargo.toml +++ b/crate/server/Cargo.toml @@ -73,6 +73,6 @@ thiserror = { workspace = true } tokio = { workspace = true, features = ["full"] } toml = "0.8" tracing = { workspace = true } -uuid = { version = "1.10", features = ["v4"] } url = { workspace = true } +uuid = { workspace = true, features = ["v4"] } x509-parser = { workspace = true } diff --git a/crate/server/src/core/implementation.rs b/crate/server/src/core/implementation.rs index 0eb8dd3..f7a5bde 100644 --- a/crate/server/src/core/implementation.rs +++ b/crate/server/src/core/implementation.rs @@ -1,13 +1,14 @@ use actix_web::{HttpMessage, HttpRequest}; use tracing::{debug, instrument, trace}; -use super::Role; +use super::Permission; use crate::{ config::{DbParams, ServerParams}, database::{Database, Redis}, error::result::FResult, findex_server_bail, middlewares::{JwtAuthClaim, PeerCommonName}, + routes::get_index_id, }; #[allow(dead_code)] @@ -67,11 +68,21 @@ impl FindexServer { #[allow(dead_code)] #[instrument(ret(Display), err, skip(self))] - pub(crate) async fn get_access(&self, user_id: &str, index_id: &str) -> FResult { + pub(crate) async fn get_permission( + &self, + user_id: &str, + index_id: &str, + ) -> FResult { if user_id == self.params.default_username { - trace!("User is the default user, granting admin access"); - return Ok(Role::Admin); + trace!("User is the default user and has admin access"); + return Ok(Permission::Admin); } - self.db.get_access(user_id, index_id).await + + let permission = self + .db + .get_permission(user_id, &get_index_id(index_id)?) + .await?; + trace!("User {user_id} has: {permission}"); + Ok(permission) } } diff --git a/crate/server/src/core/mod.rs b/crate/server/src/core/mod.rs index d780e98..916569f 100644 --- a/crate/server/src/core/mod.rs +++ b/crate/server/src/core/mod.rs @@ -1,5 +1,5 @@ -pub(crate) mod access; pub(crate) mod implementation; +pub(crate) mod permissions; -pub(crate) use access::Role; pub(crate) use implementation::FindexServer; +pub(crate) use permissions::Permission; diff --git a/crate/server/src/core/access.rs b/crate/server/src/core/permissions.rs similarity index 71% rename from crate/server/src/core/access.rs rename to crate/server/src/core/permissions.rs index a8edd08..4d4bb4c 100644 --- a/crate/server/src/core/access.rs +++ b/crate/server/src/core/permissions.rs @@ -6,21 +6,21 @@ use crate::{ }; #[repr(u8)] -#[derive(PartialEq, Eq, PartialOrd, Ord)] -pub(crate) enum Role { +#[derive(PartialEq, Eq, PartialOrd, Ord, Clone)] +pub(crate) enum Permission { Read = 0, Write = 1, Admin = 2, } #[allow(clippy::as_conversions)] -impl From for u8 { - fn from(table: Role) -> Self { +impl From for u8 { + fn from(table: Permission) -> Self { table as Self } } -impl TryFrom for Role { +impl TryFrom for Permission { type Error = FindexServerError; fn try_from(value: u8) -> FResult { @@ -28,12 +28,12 @@ impl TryFrom for Role { 0 => Ok(Self::Read), 1 => Ok(Self::Write), 2 => Ok(Self::Admin), - _ => findex_server_bail!("Invalid role: {}", value), + _ => findex_server_bail!("Invalid permission: {}", value), } } } -impl FromStr for Role { +impl FromStr for Permission { type Err = FindexServerError; fn from_str(s: &str) -> FResult { @@ -41,12 +41,12 @@ impl FromStr for Role { "reader" => Ok(Self::Read), "writer" => Ok(Self::Write), "admin" => Ok(Self::Admin), - _ => findex_server_bail!("Invalid role: {}", s), + _ => findex_server_bail!("Invalid permission: {}", s), } } } -impl Display for Role { +impl Display for Permission { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let s = match self { Self::Read => "reader", diff --git a/crate/server/src/database/database_trait.rs b/crate/server/src/database/database_trait.rs index 4f83b8a..5c921c8 100644 --- a/crate/server/src/database/database_trait.rs +++ b/crate/server/src/database/database_trait.rs @@ -1,3 +1,5 @@ +use std::{collections::HashMap, fmt::Display}; + use async_trait::async_trait; use cloudproof_findex::{ db_interfaces::{redis::FindexTable, rest::UpsertData}, @@ -5,48 +7,133 @@ use cloudproof_findex::{ TokenToEncryptedValueMap, TokenWithEncryptedValueList, Tokens, ENTRY_LENGTH, LINK_LENGTH, }, }; +use uuid::Uuid; + +use crate::{ + core::Permission, + error::{result::FResult, server::FindexServerError}, +}; + +const PERMISSION_LENGTH: usize = 1; +const INDEX_ID_LENGTH: usize = 16; + +pub(crate) struct Permissions { + pub permissions: HashMap, +} + +impl Display for Permissions { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for (index_id, permission) in &self.permissions { + writeln!(f, "Index ID: {index_id}, Permission: {permission}")?; + } + Ok(()) + } +} + +impl Permissions { + pub(crate) fn new(index_id: Uuid, permission: Permission) -> Self { + let mut permissions = HashMap::new(); + permissions.insert(index_id, permission); + Self { permissions } + } + + pub(crate) fn grant_permission(&mut self, index_id: Uuid, permission: Permission) { + self.permissions.insert(index_id, permission); + } + + pub(crate) fn revoke_permission(&mut self, index_id: &Uuid) { + self.permissions.remove(index_id); + } -use crate::{core::Role, error::result::FResult}; + pub(crate) fn serialize(&self) -> Vec { + let mut bytes = + Vec::with_capacity(self.permissions.len() * (PERMISSION_LENGTH + INDEX_ID_LENGTH)); + for (index_id, permission) in &self.permissions { + bytes.extend_from_slice(&[u8::from(permission.clone())]); + bytes.extend_from_slice(index_id.as_bytes().as_ref()); + } + bytes + } + + pub(crate) fn deserialize(bytes: &[u8]) -> FResult { + let mut permissions = HashMap::new(); + let mut i = 0; + while i < bytes.len() { + let permission_u8 = bytes.get(i).ok_or_else(|| { + FindexServerError::Deserialization("Failed to deserialize Permission".to_owned()) + })?; + let permission = Permission::try_from(*permission_u8)?; + i += PERMISSION_LENGTH; + let uuid_slice = bytes.get(i..i + INDEX_ID_LENGTH).ok_or_else(|| { + FindexServerError::Deserialization( + "Failed to extract {INDEX_ID_LENGTH} bytes from Uuid".to_owned(), + ) + })?; + let index_id = Uuid::from_slice(uuid_slice).map_err(|e| { + FindexServerError::Deserialization(format!( + "Failed to deserialize Uuid. Error: {e}" + )) + })?; + i += INDEX_ID_LENGTH; + permissions.insert(index_id, permission); + } + Ok(Self { permissions }) + } + + pub(crate) fn get_permission(&self, index_id: &Uuid) -> Option { + self.permissions.get(index_id).cloned() + } +} #[async_trait] pub(crate) trait Database: Sync + Send { async fn fetch_entries( &self, - index_id: &str, + index_id: &Uuid, tokens: Tokens, ) -> FResult>; async fn fetch_chains( &self, - index_id: &str, + index_id: &Uuid, tokens: Tokens, ) -> FResult>; async fn upsert_entries( &self, - index_id: &str, + index_id: &Uuid, upsert_data: UpsertData, ) -> FResult>; async fn insert_chains( &self, - index_id: &str, + index_id: &Uuid, items: TokenToEncryptedValueMap, ) -> FResult<()>; async fn delete( &self, - index_id: &str, + index_id: &Uuid, findex_table: FindexTable, tokens: Tokens, ) -> FResult<()>; - async fn dump_tokens(&self, index_id: &str) -> FResult; + async fn dump_tokens(&self, index_id: &Uuid) -> FResult; + + async fn create_index_id(&self, user_id: &str) -> FResult; + + async fn get_permissions(&self, user_id: &str) -> FResult; + + async fn get_permission(&self, user_id: &str, index_id: &Uuid) -> FResult; + + #[allow(dead_code)] // todo(manu): to remove + async fn grant_permission( + &self, + user_id: &str, + permission: Permission, + index_id: &Uuid, + ) -> FResult<()>; - async fn create_access(&self, user_id: &str) -> FResult; - async fn get_access(&self, user_id: &str, index_id: &str) -> FResult; - #[allow(dead_code)] - async fn grant_access(&self, user_id: &str, role: Role, index_id: &str) -> FResult; #[allow(dead_code)] - async fn revoke_access(&self, user_id: &str) -> FResult; + async fn revoke_permission(&self, user_id: &str, index_id: &Uuid) -> FResult<()>; } diff --git a/crate/server/src/database/redis.rs b/crate/server/src/database/redis.rs index 33d9ec8..b505a28 100644 --- a/crate/server/src/database/redis.rs +++ b/crate/server/src/database/redis.rs @@ -18,9 +18,9 @@ use redis::{aio::ConnectionManager, pipe, AsyncCommands, Script}; use tracing::{instrument, trace}; use uuid::Uuid; -use super::Database; +use super::{database_trait::Permissions, Database}; use crate::{ - core::Role, + core::Permission, error::{result::FResult, server::FindexServerError}, }; @@ -38,13 +38,8 @@ const CONDITIONAL_UPSERT_SCRIPT: &str = r" "; /// Generate a key for the entry table or chain table -fn build_key(index_id: &str, table: FindexTable, uid: &[u8]) -> Vec { - [index_id.as_bytes(), &[0x00, u8::from(table)], uid].concat() -} - -#[allow(dead_code)] -fn build_value(role: Role, index_id: &str) -> Vec { - [&[0x00, u8::from(role)], index_id.as_bytes()].concat() +fn build_key(index_id: &Uuid, table: FindexTable, uid: &[u8]) -> Vec { + [index_id.as_bytes().as_ref(), &[0x00, u8::from(table)], uid].concat() } #[allow(dead_code)] @@ -80,7 +75,7 @@ impl Database for Redis { #[instrument(ret(Display), err, skip_all)] async fn fetch_entries( &self, - index_id: &str, + index_id: &Uuid, tokens: Tokens, ) -> FResult> { trace!("fetch_entries: number of tokens: {}", tokens.len()); @@ -116,7 +111,7 @@ impl Database for Redis { #[instrument(ret(Display), err, skip_all)] async fn fetch_chains( &self, - index_id: &str, + index_id: &Uuid, tokens: Tokens, ) -> FResult> { trace!("fetch_chains: number of tokens: {}", tokens.len()); @@ -152,7 +147,7 @@ impl Database for Redis { #[instrument(ret(Display), err, skip_all)] async fn upsert_entries( &self, - index_id: &str, + index_id: &Uuid, upsert_data: UpsertData, ) -> FResult> { trace!( @@ -203,7 +198,7 @@ impl Database for Redis { #[instrument(ret, err, skip_all)] async fn insert_chains( &self, - index_id: &str, + index_id: &Uuid, items: TokenToEncryptedValueMap, ) -> FResult<()> { let mut pipe = pipe(); @@ -219,7 +214,7 @@ impl Database for Redis { #[instrument(ret, err, skip_all)] async fn delete( &self, - index_id: &str, + index_id: &Uuid, findex_table: FindexTable, entry_uids: Tokens, ) -> FResult<()> { @@ -236,7 +231,7 @@ impl Database for Redis { #[instrument(ret(Display), err, skip_all)] #[allow(clippy::indexing_slicing)] - async fn dump_tokens(&self, index_id: &str) -> FResult { + async fn dump_tokens(&self, index_id: &Uuid) -> FResult { let keys: Vec> = self .mgr .clone() @@ -258,55 +253,78 @@ impl Database for Redis { #[allow(dependency_on_unit_never_type_fallback)] #[instrument(ret(Display), err, skip(self))] - async fn create_access(&self, user_id: &str) -> FResult { + async fn create_index_id(&self, user_id: &str) -> FResult { + let uuid = Uuid::new_v4(); let key = user_id.as_bytes().to_vec(); - let uuid = Uuid::new_v4().to_string(); - self.mgr - .clone() - .set(key, build_value(Role::Admin, uuid.as_str())) - .await?; + let permissions = (self.get_permissions(user_id).await).map_or_else( + |_| Permissions::new(uuid, Permission::Admin), + |mut permissions| { + permissions.grant_permission(uuid, Permission::Admin); + permissions + }, + ); + self.mgr.clone().set(key, permissions.serialize()).await?; + Ok(uuid) } #[instrument(ret(Display), err, skip(self))] - async fn get_access(&self, user_id: &str, index_id: &str) -> FResult { + async fn get_permissions(&self, user_id: &str) -> FResult { let key = user_id.as_bytes().to_vec(); let value: Option> = self.mgr.clone().get(key).await?; - - if value.is_none() { - return Err(FindexServerError::Unauthorized(format!( - "No access for {user_id} since no role found for index {index_id}" - ))); - } - trace!("get_access: value: {:?}", value); + trace!("get_permissions: value: {:?}", value); let serialized_value = value.ok_or_else(|| { FindexServerError::Unauthorized(format!( - "No access for {user_id} since no role found for index {index_id}" + "No access for {user_id} since unwrapping serialized value failed" )) })?; - let role_u8 = serialized_value.get(1).ok_or_else(|| { - FindexServerError::Unauthorized(format!( - "No access for {user_id} since invalid serialized role found for index {index_id}" - )) + Permissions::deserialize(&serialized_value) + } + + #[allow(dead_code)] + #[instrument(ret(Display), err, skip(self))] + async fn get_permission(&self, user_id: &str, index_id: &Uuid) -> FResult { + let permissions = self.get_permissions(user_id).await?; + let permission = permissions.get_permission(index_id).ok_or_else(|| { + FindexServerError::Unauthorized(format!("No access for {user_id} on index {index_id}")) })?; - let role = Role::try_from(*role_u8)?; - Ok(role) + + Ok(permission) } #[allow(dependency_on_unit_never_type_fallback)] - async fn grant_access(&self, user_id: &str, role: Role, index_id: &str) -> FResult { + async fn grant_permission( + &self, + user_id: &str, + permission: Permission, + index_id: &Uuid, + ) -> FResult<()> { let key = user_id.as_bytes().to_vec(); - self.mgr - .clone() - .set(key, build_value(role, index_id)) - .await?; - Ok(user_id.to_owned()) + let permissions = match self.get_permissions(user_id).await { + Ok(mut permissions) => { + permissions.grant_permission(*index_id, permission); + permissions + } + Err(_) => Permissions::new(*index_id, permission), + }; + + self.mgr.clone().set(key, permissions.serialize()).await?; + Ok(()) } #[allow(dependency_on_unit_never_type_fallback)] - async fn revoke_access(&self, user_id: &str) -> FResult { + async fn revoke_permission(&self, user_id: &str, index_id: &Uuid) -> FResult<()> { let key = user_id.as_bytes().to_vec(); - self.mgr.clone().del(key).await?; - Ok(user_id.to_owned()) + match self.get_permissions(user_id).await { + Ok(mut permissions) => { + permissions.revoke_permission(index_id); + self.mgr.clone().set(key, permissions.serialize()).await?; + } + Err(_) => { + trace!("Nothing to revoke since no permission found for index {index_id}"); + } + }; + + Ok(()) } } diff --git a/crate/server/src/error/server.rs b/crate/server/src/error/server.rs index 20da202..a42ff42 100644 --- a/crate/server/src/error/server.rs +++ b/crate/server/src/error/server.rs @@ -52,6 +52,9 @@ pub enum FindexServerError { #[error("Invalid URL: {0}")] UrlError(String), + + #[error("Serialization: {0}")] + Deserialization(String), } impl From> for FindexServerError { diff --git a/crate/server/src/routes/error.rs b/crate/server/src/routes/error.rs index 2f90a9c..3ee12a8 100644 --- a/crate/server/src/routes/error.rs +++ b/crate/server/src/routes/error.rs @@ -21,6 +21,7 @@ impl actix_web::error::ResponseError for FindexServerError { | Self::Redis(_) | Self::Findex(_) | Self::Certificate(_) + | Self::Deserialization(_) | Self::ServerError(_) => StatusCode::INTERNAL_SERVER_ERROR, Self::InvalidRequest(_) | Self::ClientConnectionError(_) | Self::UrlError(_) => { diff --git a/crate/server/src/routes/findex.rs b/crate/server/src/routes/findex.rs index 926dde1..4b8200e 100644 --- a/crate/server/src/routes/findex.rs +++ b/crate/server/src/routes/findex.rs @@ -15,24 +15,28 @@ use cloudproof_findex::{ use tracing::{debug, info, trace}; use crate::{ - core::{FindexServer, Role}, + core::{FindexServer, Permission}, error::server::FindexServerError, - routes::error::{Response, ResponseBytes}, + routes::{ + error::{Response, ResponseBytes}, + get_index_id, + }, }; -#[post("/indexes/{id}/fetch_entries")] +#[post("/indexes/{index_id}/fetch_entries")] pub(crate) async fn fetch_entries( req: HttpRequest, - id: web::Path, + index_id: web::Path, bytes: Bytes, findex_server: Data>, ) -> ResponseBytes { let user = findex_server.get_user(&req); - info!("user {user}: POST /indexes/{id}/fetch_entries"); + info!("user {user}: POST /indexes/{index_id}/fetch_entries"); - if findex_server.get_access(&user, &id).await? < Role::Read { + // todo(manu): log permission + if findex_server.get_permission(&user, &index_id).await? < Permission::Read { return Err(FindexServerError::Unauthorized(format!( - "User {user} is not allowed to read index {id} (fetch_entries)", + "User {user} is not allowed to read index {index_id} (fetch_entries)", ))); } @@ -44,7 +48,7 @@ pub(crate) async fn fetch_entries( // Collect into a vector to fix the order. let uids_and_values = findex_server .db - .fetch_entries(&id.into_inner(), tokens) + .fetch_entries(&get_index_id(index_id.as_str())?, tokens) .await?; trace!( "fetch_entries: number of uids_and_values: {}:", @@ -59,19 +63,19 @@ pub(crate) async fn fetch_entries( .body(bytes)) } -#[post("/indexes/{id}/fetch_chains")] +#[post("/indexes/{index_id}/fetch_chains")] pub(crate) async fn fetch_chains( req: HttpRequest, - id: web::Path, + index_id: web::Path, bytes: Bytes, findex_server: Data>, ) -> ResponseBytes { let user = findex_server.get_user(&req); - info!("user {user}: POST /indexes/{id}/fetch_chains"); + info!("user {user}: POST /indexes/{index_id}/fetch_chains"); - if findex_server.get_access(&user, &id).await? < Role::Read { + if findex_server.get_permission(&user, &index_id).await? < Permission::Read { return Err(FindexServerError::Unauthorized(format!( - "User {user} is not allowed to read index {id} (fetch_chains)", + "User {user} is not allowed to read index {index_id} (fetch_chains)", ))); } @@ -81,7 +85,7 @@ pub(crate) async fn fetch_chains( let uids_and_values = findex_server .db - .fetch_chains(&id.into_inner(), tokens) + .fetch_chains(&get_index_id(index_id.as_str())?, tokens) .await?; trace!( "fetch_chains: number of uids_and_values: {}:", @@ -95,21 +99,21 @@ pub(crate) async fn fetch_chains( .body(bytes)) } -#[post("/indexes/{id}/upsert_entries")] +#[post("/indexes/{index_id}/upsert_entries")] pub(crate) async fn upsert_entries( req: HttpRequest, - id: web::Path, + index_id: web::Path, bytes: Bytes, findex_server: Data>, ) -> ResponseBytes { let user = findex_server.get_user(&req); - info!("user {user}: POST /indexes/{id}/upsert_entries",); + info!("user {user}: POST /indexes/{index_id}/upsert_entries",); - let user_role = findex_server.get_access(&user, &id).await?; - debug!("user {user} has role: {user_role}"); - if user_role < Role::Write { + let user_permission = findex_server.get_permission(&user, &index_id).await?; + debug!("user {user} has permission: {user_permission}"); + if user_permission < Permission::Write { return Err(FindexServerError::Unauthorized(format!( - "User {user} is not allowed to write on index {id} (upsert_entries)", + "User {user} is not allowed to write on index {index_id} (upsert_entries)", ))); } @@ -120,7 +124,7 @@ pub(crate) async fn upsert_entries( let rejected = findex_server .db - .upsert_entries(&id.into_inner(), upsert_data) + .upsert_entries(&get_index_id(index_id.as_str())?, upsert_data) .await?; let bytes = rejected.serialize()?.to_vec(); @@ -129,19 +133,19 @@ pub(crate) async fn upsert_entries( .body(bytes)) } -#[post("/indexes/{id}/insert_chains")] +#[post("/indexes/{index_id}/insert_chains")] pub(crate) async fn insert_chains( req: HttpRequest, - id: web::Path, + index_id: web::Path, bytes: Bytes, findex_server: Data>, ) -> Response<()> { let user = findex_server.get_user(&req); - info!("user {user}: POST /indexes/{id}/insert_chains",); + info!("user {user}: POST /indexes/{index_id}/insert_chains",); - if findex_server.get_access(&user, &id).await? < Role::Write { + if findex_server.get_permission(&user, &index_id).await? < Permission::Write { return Err(FindexServerError::Unauthorized(format!( - "User {user} is not allowed to write on index {id} (insert_chains)", + "User {user} is not allowed to write on index {index_id} (insert_chains)", ))); } @@ -150,25 +154,28 @@ pub(crate) async fn insert_chains( findex_server .db - .insert_chains(&id.into_inner(), token_to_value_encrypted_value_map) + .insert_chains( + &get_index_id(index_id.as_str())?, + token_to_value_encrypted_value_map, + ) .await?; Ok(Json(())) } -#[post("/indexes/{id}/delete_entries")] +#[post("/indexes/{index_id}/delete_entries")] pub(crate) async fn delete_entries( req: HttpRequest, - id: web::Path, + index_id: web::Path, bytes: Bytes, findex_server: Data>, ) -> Response<()> { let user = findex_server.get_user(&req); - info!("user {user}: POST /indexes/{id}/delete_entries",); + info!("user {user}: POST /indexes/{index_id}/delete_entries",); - if findex_server.get_access(&user, &id).await? < Role::Write { + if findex_server.get_permission(&user, &index_id).await? < Permission::Write { return Err(FindexServerError::Unauthorized(format!( - "User {user} is not allowed to write on index {id} (delete_entries)", + "User {user} is not allowed to write on index {index_id} (delete_entries)", ))); } @@ -178,25 +185,29 @@ pub(crate) async fn delete_entries( findex_server .db - .delete(&id.into_inner(), FindexTable::Entry, tokens) + .delete( + &get_index_id(index_id.as_str())?, + FindexTable::Entry, + tokens, + ) .await?; Ok(Json(())) } -#[post("/indexes/{id}/delete_chains")] +#[post("/indexes/{index_id}/delete_chains")] pub(crate) async fn delete_chains( req: HttpRequest, - id: web::Path, + index_id: web::Path, bytes: Bytes, findex_server: Data>, ) -> Response<()> { let user = findex_server.get_user(&req); - info!("user {user}: POST /indexes/{id}/delete_chains",); + info!("user {user}: POST /indexes/{index_id}/delete_chains",); - if findex_server.get_access(&user, &id).await? < Role::Write { + if findex_server.get_permission(&user, &index_id).await? < Permission::Write { return Err(FindexServerError::Unauthorized(format!( - "User {user} is not allowed to write on index {id} (delete_chains)", + "User {user} is not allowed to write on index {index_id} (delete_chains)", ))); } @@ -206,28 +217,35 @@ pub(crate) async fn delete_chains( findex_server .db - .delete(&id.into_inner(), FindexTable::Chain, tokens) + .delete( + &get_index_id(index_id.as_str())?, + FindexTable::Chain, + tokens, + ) .await?; Ok(Json(())) } -#[post("/indexes/{id}/dump_tokens")] +#[post("/indexes/{index_id}/dump_tokens")] pub(crate) async fn dump_tokens( req: HttpRequest, - id: web::Path, + index_id: web::Path, findex_server: Data>, ) -> ResponseBytes { let user = findex_server.get_user(&req); - info!("user {user}: POST /indexes/{id}/dump_tokens"); + info!("user {user}: POST /indexes/{index_id}/dump_tokens"); - if findex_server.get_access(&user, &id).await? < Role::Read { + if findex_server.get_permission(&user, &index_id).await? < Permission::Read { return Err(FindexServerError::Unauthorized(format!( - "User {user} is not allowed to read index {id} (dump_tokens)", + "User {user} is not allowed to read index {index_id} (dump_tokens)", ))); } - let tokens = findex_server.db.dump_tokens(&id.into_inner()).await?; + let tokens = findex_server + .db + .dump_tokens(&get_index_id(index_id.as_str())?) + .await?; trace!("dump_tokens: number of tokens: {}:", tokens.len()); let bytes = tokens.serialize()?.to_vec(); diff --git a/crate/server/src/routes/mod.rs b/crate/server/src/routes/mod.rs index 034423a..cf6fb7d 100644 --- a/crate/server/src/routes/mod.rs +++ b/crate/server/src/routes/mod.rs @@ -1,11 +1,13 @@ -mod access; mod error; mod findex; +mod permissions; +mod utils; mod version; -pub(crate) use access::{create_access, grant_access, revoke_access}; pub(crate) use findex::{ delete_chains, delete_entries, dump_tokens, fetch_chains, fetch_entries, insert_chains, upsert_entries, }; +pub(crate) use permissions::{create_access, grant_access, revoke_access}; +pub(crate) use utils::get_index_id; pub(crate) use version::get_version; diff --git a/crate/server/src/routes/access.rs b/crate/server/src/routes/permissions.rs similarity index 56% rename from crate/server/src/routes/access.rs rename to crate/server/src/routes/permissions.rs index 559db92..af54852 100644 --- a/crate/server/src/routes/access.rs +++ b/crate/server/src/routes/permissions.rs @@ -6,11 +6,12 @@ use actix_web::{ HttpRequest, }; use serde::{Deserialize, Serialize}; -use tracing::{info, trace}; +use tracing::info; use crate::{ - core::{FindexServer, Role}, + core::{FindexServer, Permission}, error::{result::FResult, server::FindexServerError}, + routes::get_index_id, }; #[derive(Deserialize, Serialize, Debug)] // Debug is required by ok_json() @@ -27,40 +28,44 @@ pub(crate) async fn create_access( info!("user {user}: POST /access/create"); // Check if the user has the right to grant access: only admins can do that - let index_id = findex_server.db.create_access(&user).await?; - trace!("New access successfully created: {index_id}"); + let index_id = findex_server.db.create_index_id(&user).await?; Ok(Json(SuccessResponse { - success: format!("New access successfully created: {index_id}"), + success: format!("[{user}] New admin access successfully created on index: {index_id}"), })) } -#[post("/access/grant/{user_id}/{role}/{index_id}")] +#[post("/access/grant/{user_id}/{permission}/{index_id}")] pub(crate) async fn grant_access( req: HttpRequest, params: web::Path<(String, String, String)>, findex_server: Data>, ) -> FResult> { let user = findex_server.get_user(&req); - let (user_id, role, index_id) = params.into_inner(); - info!("user {user}: POST /access/grant/{user_id}/{role}/{index_id}"); + let (user_id, permission, index_id) = params.into_inner(); + info!("user {user}: POST /access/grant/{user_id}/{permission}/{index_id}"); // Check if the user has the right to grant access: only admins can do that - let user_role = findex_server.get_access(&user, &index_id).await?; - if Role::Admin != user_role { + let user_permission = findex_server.get_permission(&user, &index_id).await?; + if Permission::Admin != user_permission { return Err(FindexServerError::Unauthorized(format!( - "Delegating access to an index requires an admin role. User {user} with role \ - {user_role} does not allow granting access to index {index_id} with role {role}", + "Delegating access to an index requires an admin permission. User {user} with \ + permission {user_permission} does not allow granting access to index {index_id} with \ + permission {permission}", ))); } findex_server .db - .grant_access(&user_id, Role::from_str(role.as_str())?, &index_id) + .grant_permission( + &user_id, + Permission::from_str(permission.as_str())?, + &get_index_id(&index_id)?, + ) .await?; Ok(Json(SuccessResponse { - success: format!("Access for {user_id} on index {index_id} successfully added"), + success: format!("[{user_id}] Access {permission} on index {index_id} successfully added"), })) } @@ -75,15 +80,18 @@ pub(crate) async fn revoke_access( info!("user {user}: POST /access/revoke/{user_id}/{index_id}"); // Check if the user has the right to revoke access: only admins can do that - let user_role = findex_server.get_access(&user, &index_id).await?; - if Role::Admin != user_role { + let user_permission = findex_server.get_permission(&user, &index_id).await?; + if Permission::Admin != user_permission { return Err(FindexServerError::Unauthorized(format!( - "Revoking access to an index requires an admin role. User {user} with role \ - {user_role} does not allow revoking access to index {index_id}", + "Revoking access to an index requires an admin permission. User {user} with \ + permission {user_permission} does not allow revoking access to index {index_id}", ))); } - findex_server.db.revoke_access(&user_id).await?; + findex_server + .db + .revoke_permission(&user_id, &get_index_id(&index_id)?) + .await?; Ok(Json(SuccessResponse { success: format!("Access for {user_id} on index {index_id} successfully added"), diff --git a/crate/server/src/routes/utils.rs b/crate/server/src/routes/utils.rs new file mode 100644 index 0000000..1067c35 --- /dev/null +++ b/crate/server/src/routes/utils.rs @@ -0,0 +1,9 @@ +use uuid::Uuid; + +use crate::error::{result::FResult, server::FindexServerError}; + +pub(crate) fn get_index_id(index_id: &str) -> FResult { + Uuid::parse_str(index_id).map_err(|e| { + FindexServerError::Deserialization(format!("Invalid index_id: {index_id}. Error: {e}")) + }) +} diff --git a/crate/test_server/Cargo.toml b/crate/test_server/Cargo.toml index fd2003a..0a9124c 100644 --- a/crate/test_server/Cargo.toml +++ b/crate/test_server/Cargo.toml @@ -14,11 +14,11 @@ doctest = false [dependencies] actix-server = { workspace = true } -cosmian_rest_client = { path = "../client" } cosmian_findex_server = { path = "../server", features = [ "insecure", ], default-features = false } cosmian_logger = { path = "../logger" } +cosmian_rest_client = { path = "../client" } tokio = { workspace = true, features = ["rt-multi-thread"] } tracing = { workspace = true } diff --git a/crate/test_server/src/test_server.rs b/crate/test_server/src/test_server.rs index e0732fe..aea60a3 100644 --- a/crate/test_server/src/test_server.rs +++ b/crate/test_server/src/test_server.rs @@ -30,12 +30,12 @@ pub(crate) static ONCE: OnceCell = OnceCell::const_new(); pub(crate) static ONCE_SERVER_WITH_AUTH: OnceCell = OnceCell::const_new(); fn redis_db_config() -> DBConfig { - trace!("TESTS: using redis-findex"); let url = if let Ok(var_env) = env::var("REDIS_HOST") { format!("redis://{var_env}:6379") } else { "redis://localhost:6379".to_owned() }; + trace!("TESTS: using redis on {url}"); DBConfig { database_type: Some(DatabaseType::Redis), clear_database: true, @@ -349,7 +349,6 @@ mod test { start_test_server_with_options, test_server::redis_db_config, AuthenticationOptions, }; - #[allow(clippy::needless_return)] #[tokio::test] async fn test_server_auth_matrix() -> Result<(), ClientError> { let test_cases = vec![ @@ -363,7 +362,7 @@ mod test { trace!("Running test case: {}", description); let context = start_test_server_with_options( redis_db_config(), - 6660, + 6662, AuthenticationOptions { use_https, use_jwt_token,