151 lines
4.0 KiB
Rust
151 lines
4.0 KiB
Rust
//! 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);
|
|
}
|