use std::collections::BTreeSet; mod common; use common::Environment; fn create_environment() -> anyhow::Result<(Environment, String)> { Environment::scooby_gang_bootstrap() } // The keys for the different authorizations in the JSON file. const SIGN_COMMIT: &str = "sign_commit"; const SIGN_TAG: &str = "sign_tag"; const SIGN_ARCHIVE: &str = "sign_archive"; const ADD_USER: &str = "add_user"; const RETIRE_USER: &str = "retire_user"; const AUDIT: &str = "audit"; const CAPS: &[&str] = &[ SIGN_COMMIT, SIGN_TAG, SIGN_ARCHIVE, ADD_USER, RETIRE_USER, AUDIT ]; fn check(e: &Environment, args: &[&str], expected_caps: &[&str]) { let petname = e.buffy.petname; let fpr = e.buffy.fingerprint.to_string(); let openpgp_policy_toml = e.git_state().join("openpgp-policy.toml"); if let Err(err) = std::fs::remove_file(&openpgp_policy_toml) { if std::io::ErrorKind::NotFound != err.kind() { panic!("Removing {}", openpgp_policy_toml.display()); } } let mut sq_git: Vec<&str> = [ "policy", "authorize", &petname, &fpr, ].to_vec(); sq_git.extend(args); eprintln!("Running: sq-git {}", sq_git.join(" ")); e.sq_git(&sq_git).unwrap(); let output = e.sq_git(&[ "policy", "describe", "--output-format", "json" ]).unwrap(); eprintln!("Output:\n{}", String::from_utf8_lossy(&output.stdout)); let status: serde_json::Value = serde_json::from_slice(&output.stdout) .expect("\"sq policy describe\" emits valid json output"); // eprintln!("JSON:\n{:?}", status); let auths = &status["authorization"]; // eprintln!("JSON[\"authorization\"]:\n{:?}", auths); let user = &auths[petname]; eprintln!("JSON[\"authorization\"][\"{}\"]:\n{:?}", petname, user); let caps = BTreeSet::from_iter(CAPS.iter()); let expected_caps_present = BTreeSet::from_iter(expected_caps.iter()); for cap in expected_caps_present.iter() { eprintln!("Checking that {} is true", cap); match &user[cap] { serde_json::Value::Bool(true) => (), v => { panic!("expected {} to be true, but it is: {:?}", cap, v); } } } let expected_caps_missing = caps.difference(&expected_caps_present); for cap in expected_caps_missing { eprintln!("Checking that {} is not set or false", cap); match &user[cap] { serde_json::Value::Null => (), serde_json::Value::Bool(false) => (), v => { panic!("expected {} to be false, but it is: {:?}", cap, v); } } } } #[test] fn check_flags() -> anyhow::Result<()> { let (e, _root) = create_environment()?; // One at a time. check(&e, &["--sign-commit"], &[ SIGN_COMMIT ]); check(&e, &["--sign-archive"], &[ SIGN_ARCHIVE ]); check(&e, &["--sign-tag"], &[ SIGN_TAG ]); check(&e, &["--add-user"], &[ ADD_USER ]); check(&e, &["--retire-user"], &[ RETIRE_USER ]); check(&e, &["--audit"], &[ AUDIT ]); // Mix and match. check(&e, &["--sign-commit", "--sign-archive"], &[ SIGN_COMMIT, SIGN_ARCHIVE ]); check(&e, &["--sign-tag", "--add-user", "--retire-user"], &[ SIGN_TAG, ADD_USER, RETIRE_USER ]); // Add some negatives. The last positive or negative wins. check(&e, &["--no-sign-commit", "--sign-archive"], &[ SIGN_ARCHIVE ]); check(&e, &["--sign-commit", "--no-sign-commit", "--sign-archive"], &[ SIGN_ARCHIVE ]); check(&e, &["--no-sign-commit", "--sign-commit", "--sign-archive"], &[ SIGN_COMMIT, SIGN_ARCHIVE ]); // The meta-capabilities. check(&e, &["--committer"], &[ SIGN_COMMIT ]); check(&e, &["--release-manager"], &[ SIGN_COMMIT, SIGN_TAG, SIGN_ARCHIVE ]); check(&e, &["--project-maintainer"], &[ SIGN_COMMIT, SIGN_TAG, SIGN_ARCHIVE, ADD_USER, RETIRE_USER, AUDIT ]); // Union. check(&e, &["--project-maintainer", "--committer"], &[ SIGN_COMMIT, SIGN_TAG, SIGN_ARCHIVE, ADD_USER, RETIRE_USER, AUDIT ]); check(&e, &["--project-maintainer", "--no-sign-archive"], &[ SIGN_COMMIT, SIGN_TAG, ADD_USER, RETIRE_USER, AUDIT ]); // A meta-capability does not trump a negative capability. check(&e, &["--no-sign-archive", "--project-maintainer"], &[ SIGN_COMMIT, SIGN_TAG, ADD_USER, RETIRE_USER, AUDIT ]); Ok(()) }