//! Explicit grant approvals honor their limits. Denials suppress duplicate approval prompts. use sausage::cred::{ edits::{Operation, OperationRequest}, grants::Grant, items::Item, requests::{GrantRequest, Overrides}, webauthn::Decision, *, }; fn now() -> i64 { std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_secs() as i64 } fn fixture() -> (tempfile::TempDir, Cred, Caller, Item) { let dir = tempfile::tempdir().unwrap(); let c = Cred::open(dir.path()).unwrap(); let caller = Caller { program: "/bin/test.wasm".into(), pid: 1, conversations: vec!["root".into()], }; let item = c.create_item(serde_json::from_value(serde_json::json!({"name":"test","rules":[],"validate_js":null,"transform_js":"","fields":[]})).unwrap(),&caller,c.unlocked().unwrap()).unwrap(); (dir, c, caller, item) } fn grant(item: &Item) -> Grant { Grant { id: String::new(), items: vec![item.id.clone()], subject: types::Subject::Conversation("root".into()), exp: None, max_uses: None, uses: 0, revoked: false, validator: None, note: None, } } fn operation(c: &Cred, caller: &Caller, key: &str, op: Operation) -> String { c.file_operation_request( caller, key, OperationRequest { item: None, base_version: None, change: vec![op], }, ) .unwrap() } #[test] fn grant_operations_apply_overrides_defaults_and_replay_liveness() { let (_dir, c, caller, item) = fixture(); let op = Operation::GrantCreate { grant: grant(&item), }; let id = operation(&c, &caller, "grant", op.clone()); let before = now(); c.approve( &id, Decision::Approve, Overrides { duration_secs: Some(120), max_uses: Some(1), validator: Some("true".into()), unlimited: false, }, c.unlocked().unwrap(), ) .unwrap(); let g = c.grant_list().unwrap().pop().unwrap(); assert!( g.exp .is_some_and(|e| (before + 120..=now() + 120).contains(&e)) ); assert_eq!(g.max_uses, Some(1)); assert_eq!(g.validator.as_deref(), Some("true")); let revoke = operation( &c, &caller, "revoke", Operation::GrantRevoke { target: g.id }, ); assert!( c.approve( &revoke, Decision::Approve, Overrides { duration_secs: Some(1), ..Default::default() }, c.unlocked().unwrap() ) .is_err() ); c.approve( &revoke, Decision::Approve, Overrides::default(), c.unlocked().unwrap(), ) .unwrap(); assert_ne!(operation(&c, &caller, "grant", op), id); let id = operation( &c, &caller, "default", Operation::GrantCreate { grant: grant(&item), }, ); let before = now(); c.approve( &id, Decision::Approve, Overrides::default(), c.unlocked().unwrap(), ) .unwrap(); assert!( c.grant_list() .unwrap() .last() .unwrap() .exp .is_some_and(|e| (before + 3600..=now() + 3600).contains(&e)) ); } #[test] fn denial_replays_until_expiry_and_requester_duration_is_capped() { let (_dir, c, caller, item) = fixture(); let request = GrantRequest { items: vec![item.id], duration_secs: Some(86401), max_uses: None, validator: None, }; assert!( c.file_request(&caller, "too-long", request.clone()) .is_err() ); let request = GrantRequest { duration_secs: None, ..request }; let id = c.file_request(&caller, "denied", request.clone()).unwrap(); c.deny(&id, Decision::Deny, c.unlocked().unwrap()).unwrap(); assert_eq!(c.file_request(&caller, "denied", request).unwrap(), id); }