From 0f5bfa3d5eb56b250e1432f0ff20a017e1e09a84 Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Sat, 24 May 2025 18:45:33 +0100 Subject: [PATCH] Validate user config --- backend/Cargo.lock | 7 ++ backend/config-e2e.yml | 7 +- backend/sync_server/Cargo.toml | 1 + backend/sync_server/src/config/user_config.rs | 113 +++++++++++++++++- 4 files changed, 122 insertions(+), 6 deletions(-) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 553ef415..f33914fb 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -362,6 +362,12 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "bimap" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7" + [[package]] name = "bit-set" version = "0.5.3" @@ -2563,6 +2569,7 @@ dependencies = [ "axum-extra", "axum-jsonschema", "axum_typed_multipart", + "bimap", "chrono", "clap", "clap-verbosity-flag", diff --git a/backend/config-e2e.yml b/backend/config-e2e.yml index 40df4c89..17b745ea 100644 --- a/backend/config-e2e.yml +++ b/backend/config-e2e.yml @@ -10,12 +10,17 @@ server: response_timeout_seconds: 60 users: - user_tokens: + user_configs: - name: admin token: test-token-change-me vault_access: type: allow_access_to_all + - name: other-admin + token: test-token-change-me2 + vault_access: + type: allow_access_to_all + - name: test token: other-test-token vault_access: diff --git a/backend/sync_server/Cargo.toml b/backend/sync_server/Cargo.toml index b8ff1549..a483ed5c 100644 --- a/backend/sync_server/Cargo.toml +++ b/backend/sync_server/Cargo.toml @@ -36,6 +36,7 @@ clap = { version = "4.5.38", features = ["derive"] } futures = "0.3.31" serde_json = "1.0.140" clap-verbosity-flag = "3.0.3" +bimap = "0.6.3" [lints] workspace = true diff --git a/backend/sync_server/src/config/user_config.rs b/backend/sync_server/src/config/user_config.rs index 2450c3aa..ed7ecc23 100644 --- a/backend/sync_server/src/config/user_config.rs +++ b/backend/sync_server/src/config/user_config.rs @@ -1,17 +1,47 @@ +use bimap::BiHashMap; use rand::{Rng, distr::Alphanumeric, rng}; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize, de::Error}; use crate::app_state::database::models::VaultId; #[derive(Debug, Deserialize, Serialize, Clone)] pub struct UserConfig { - #[serde(default = "default_users")] - pub user_tokens: Vec, + #[serde(default = "default_users", deserialize_with = "validate_users")] + pub user_configs: Vec, +} + +fn validate_users<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let users = Vec::::deserialize(deserializer)?; + + let mut user_token_map = BiHashMap::new(); + for user in &users { + if let Some(existing_name) = user_token_map.get_by_right(&user.token) { + return Err(D::Error::custom(format!( + "Duplicate user token found: '{}' for users '{}' and '{}'. User tokens must be \ + unique.", + user.token, existing_name, user.name + ))); + } + + if user_token_map.contains_left(&user.name) { + return Err(D::Error::custom(format!( + "Duplicate user name found: '{}'. User names must be unique.", + user.name + ))); + } + + user_token_map.insert(user.name.clone(), user.token.clone()); + } + + Ok(users) } impl UserConfig { pub fn get_user(&self, token: &str) -> Option<&User> { - self.user_tokens.iter().find(|u| u.token == token) + self.user_configs.iter().find(|u| u.token == token) } } @@ -25,7 +55,7 @@ pub struct User { impl Default for UserConfig { fn default() -> Self { Self { - user_tokens: default_users(), + user_configs: default_users(), } } } @@ -59,3 +89,76 @@ pub fn get_random_token() -> String { .map(char::from) .collect() } +#[cfg(test)] +mod tests { + use serde_json::json; + + use super::*; + + #[test] + fn test_validate_users_unique_names_and_tokens() { + let config_json = json!({ + "user_configs": [ + { + "name": "alice", + "token": "token1", + "vault_access": { "type": "allow_access_to_all" } + }, + { + "name": "bob", + "token": "token2", + "vault_access": { "type": "allow_access_to_all" } + } + ] + }); + + let config: Result = serde_json::from_value(config_json); + assert!(config.is_ok()); + } + + #[test] + fn test_validate_users_duplicate_names() { + let config_json = json!({ + "user_configs": [ + { + "name": "alice", + "token": "token1", + "vault_access": { "type": "allow_access_to_all" } + }, + { + "name": "alice", + "token": "token2", + "vault_access": { "type": "allow_access_to_all" } + } + ] + }); + + let config: Result = serde_json::from_value(config_json); + assert!(config.is_err()); + let err = config.unwrap_err().to_string(); + assert!(err.contains("Duplicate user name found")); + } + + #[test] + fn test_validate_users_duplicate_tokens() { + let config_json = json!({ + "user_configs": [ + { + "name": "alice", + "token": "token1", + "vault_access": { "type": "allow_access_to_all" } + }, + { + "name": "bob", + "token": "token1", + "vault_access": { "type": "allow_access_to_all" } + } + ] + }); + + let config: Result = serde_json::from_value(config_json); + assert!(config.is_err()); + let err = config.unwrap_err().to_string(); + assert!(err.contains("Duplicate user token found")); + } +}