From f165c481e6f42c2239fc840bc738067c0f0c0b8f Mon Sep 17 00:00:00 2001 From: Zoran Regvart Date: Mon, 31 Aug 2026 17:57:55 +0200 Subject: [PATCH] Create OIDC group mappings via CLI Adds new CLI subcommand `map-group` to create a mapping of OIDC group to a role. --- Cargo.lock | 75 +++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 3 ++ src/main.rs | 53 ++++++++++++++++++++++++++++++ tests/cli/config.toml | 1 + 4 files changed, 132 insertions(+) create mode 100644 tests/cli/config.toml diff --git a/Cargo.lock b/Cargo.lock index 7af00b9..4f66178 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -179,6 +179,21 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "assert_cmd" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aa3a22042e45de04255c7bf3626e239f450200fd0493c1e382263544b20aea6" +dependencies = [ + "anstyle", + "bstr", + "libc", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + [[package]] name = "async-trait" version = "0.1.91" @@ -464,6 +479,17 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "bstr" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f" +dependencies = [ + "memchr", + "regex-automata", + "serde_core", +] + [[package]] name = "bumpalo" version = "3.20.3" @@ -945,6 +971,12 @@ dependencies = [ "cipher 0.5.2", ] +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + [[package]] name = "digest" version = "0.10.7" @@ -2692,6 +2724,33 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "predicates" +version = "3.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe" +dependencies = [ + "anstyle", + "difflib", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144" + +[[package]] +name = "predicates-tree" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2" +dependencies = [ + "predicates-core", + "termtree", +] + [[package]] name = "primefield" version = "0.14.0" @@ -3256,6 +3315,7 @@ name = "rustguac" version = "1.9.10" dependencies = [ "aes", + "assert_cmd", "axum", "axum-server", "base64 0.23.1", @@ -3999,6 +4059,12 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "termtree" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" + [[package]] name = "thiserror" version = "1.0.69" @@ -4545,6 +4611,15 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + [[package]] name = "want" version = "0.3.1" diff --git a/Cargo.toml b/Cargo.toml index 777080c..d214ecf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -93,3 +93,6 @@ serde_urlencoded = "0.7" [build-dependencies] pulldown-cmark = "0.13" + +[dev-dependencies] +assert_cmd = "2.2.2" diff --git a/src/main.rs b/src/main.rs index 6aacad6..bebc2c8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -88,6 +88,14 @@ enum Command { name: String, }, + /// Map an OIDC group to a role (admin, poweruser, operator, viewer) + MapGroup { + #[arg(long)] + group: String, + #[arg(long)] + role: String, + }, + /// Rotate an admin's API key (generates new key, invalidates old) RotateKey { #[arg(long)] @@ -217,6 +225,7 @@ async fn main() { Some(Command::DisableAdmin { name }) => cmd_disable_admin(&database, &name), Some(Command::EnableAdmin { name }) => cmd_enable_admin(&database, &name), Some(Command::DeleteAdmin { name }) => cmd_delete_admin(&database, &name), + Some(Command::MapGroup { group, role }) => cmd_map_group(&database, &group, &role), Some(Command::RotateKey { name }) => cmd_rotate_key(&database, &name), Some(Command::GenerateCert { hostname, @@ -476,6 +485,20 @@ fn cmd_delete_user(database: &Db, email: &str) { } } +fn cmd_map_group(database: &Db, group: &str, role: &str) { + if !["admin", "poweruser", "operator", "viewer"].contains(&role) { + eprintln!("Role must be admin, poweruser, operator, or viewer."); + std::process::exit(1); + } + match db::create_group_mapping(database, group, role) { + Ok(_) => println!("Group '{}' mapped to role '{}'.", group, role), + Err(e) => { + eprintln!("Error: {}", e); + std::process::exit(1); + } + } +} + /// Whether TLS is enabled (used by security headers middleware). #[derive(Clone)] struct TlsEnabled(bool); @@ -1390,6 +1413,9 @@ fn rewrite_branding(html: &str, site_title: &str, logo_url: Option<&str>) -> Str #[cfg(test)] mod tests { + use assert_cmd::Command; + use std::path::Path; + use super::*; #[test] @@ -1472,4 +1498,31 @@ mod tests { "no requests were throttled — rate-limit not applied" ); } + #[test] + fn cli_map_group() { + if std::path::Path::new("tests/cli/rustguac.db").exists() { + std::fs::remove_file("tests/cli/rustguac.db") + .expect("Failed to delete old test configuration file"); + } + let mut cmd = Command::cargo_bin("rustguac").unwrap(); + cmd.current_dir("tests/cli") + .args(&[ + "--config", + "config.toml", + "map-group", + "--group", + "superhumans", + "--role", + "admin", + ]) + .assert() + .success(); + let database = + db::init_db(Path::new("tests/cli/rustguac.db")).expect("Failed to open database"); + let mappings = db::list_group_mappings(&database).expect("Failed to list group mappings"); + assert_eq!(mappings.len(), 1); + let mapping = &mappings[0]; + assert_eq!(mapping.oidc_group, "superhumans"); + assert_eq!(mapping.role, "admin"); + } } diff --git a/tests/cli/config.toml b/tests/cli/config.toml new file mode 100644 index 0000000..84aa063 --- /dev/null +++ b/tests/cli/config.toml @@ -0,0 +1 @@ +db_path = "./rustguac.db"