217 lines
6.7 KiB
Rust
217 lines
6.7 KiB
Rust
//! D57 retention preserves pending work and all versions. Revocation only removes access.
|
|
use sausage::cred::{
|
|
edits::{Edit, Operation, OperationRequest},
|
|
grants::Grant,
|
|
items::Item,
|
|
requests::{GrantRequest, Overrides},
|
|
webauthn::Decision,
|
|
*,
|
|
};
|
|
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, subject: types::Subject) -> Grant {
|
|
Grant {
|
|
id: String::new(),
|
|
items: vec![item.id.clone()],
|
|
subject,
|
|
exp: None,
|
|
max_uses: None,
|
|
uses: 0,
|
|
revoked: false,
|
|
validator: None,
|
|
note: None,
|
|
}
|
|
}
|
|
fn request(item: &Item) -> GrantRequest {
|
|
GrantRequest {
|
|
items: vec![item.id.clone()],
|
|
duration_secs: None,
|
|
max_uses: None,
|
|
validator: None,
|
|
}
|
|
}
|
|
#[test]
|
|
fn prune_obeys_cutoffs_and_preserves_pending_live_and_versions() {
|
|
let (dir, c, caller, item) = fixture();
|
|
let now = 200 * 86400;
|
|
let db = rusqlite::Connection::open(dir.path().join("cred.sqlite")).unwrap();
|
|
let old = c.file_request(&caller, "old", request(&item)).unwrap();
|
|
c.approve(
|
|
&old,
|
|
Decision::Approve,
|
|
Overrides::default(),
|
|
c.unlocked().unwrap(),
|
|
)
|
|
.unwrap();
|
|
let expired = c.grant_list().unwrap()[0].id.clone();
|
|
db.execute(
|
|
"UPDATE requests SET decided_at=? WHERE id=?",
|
|
rusqlite::params![now - 31 * 86400, old],
|
|
)
|
|
.unwrap();
|
|
db.execute(
|
|
"UPDATE grants SET exp=? WHERE id=?",
|
|
rusqlite::params![now - 31 * 86400, expired],
|
|
)
|
|
.unwrap();
|
|
let pending = c.file_request(&caller, "pending", request(&item)).unwrap();
|
|
db.execute("UPDATE requests SET expires=0 WHERE id=?", [&pending])
|
|
.unwrap();
|
|
let edge = c.file_request(&caller, "edge", request(&item)).unwrap();
|
|
c.deny(&edge, Decision::Deny, c.unlocked().unwrap())
|
|
.unwrap();
|
|
db.execute(
|
|
"UPDATE requests SET decided_at=? WHERE id=?",
|
|
rusqlite::params![now - 30 * 86400, edge],
|
|
)
|
|
.unwrap();
|
|
let live = c
|
|
.grant_create(
|
|
grant(&item, types::Subject::Conversation("root".into())),
|
|
&caller,
|
|
c.unlocked().unwrap(),
|
|
)
|
|
.unwrap();
|
|
let revoked = c
|
|
.grant_create(
|
|
grant(&item, types::Subject::Conversation("root".into())),
|
|
&caller,
|
|
c.unlocked().unwrap(),
|
|
)
|
|
.unwrap();
|
|
c.revoke_grant(&revoked.id, &caller).unwrap();
|
|
db.execute(
|
|
"UPDATE grants SET revoked_at=? WHERE id=?",
|
|
rusqlite::params![now - 31 * 86400, revoked.id],
|
|
)
|
|
.unwrap();
|
|
let protected = c
|
|
.grant_create(
|
|
grant(&item, types::Subject::Conversation("root".into())),
|
|
&caller,
|
|
c.unlocked().unwrap(),
|
|
)
|
|
.unwrap();
|
|
db.execute(
|
|
"UPDATE grants SET exp=? WHERE id=?",
|
|
rusqlite::params![now - 31 * 86400, protected.id],
|
|
)
|
|
.unwrap();
|
|
c.file_operation_request(
|
|
&caller,
|
|
"protected",
|
|
OperationRequest {
|
|
item: None,
|
|
base_version: None,
|
|
change: vec![Operation::GrantRevoke {
|
|
target: protected.id.clone(),
|
|
}],
|
|
},
|
|
)
|
|
.unwrap();
|
|
let mut config = item.clone();
|
|
config.name = "renamed".into();
|
|
let edit = c
|
|
.file_edit_request(
|
|
&caller,
|
|
"edit",
|
|
Edit {
|
|
item: item.id.clone(),
|
|
base_version: item.version,
|
|
config,
|
|
},
|
|
)
|
|
.unwrap();
|
|
c.approve(
|
|
&edit,
|
|
Decision::Approve,
|
|
Overrides::default(),
|
|
c.unlocked().unwrap(),
|
|
)
|
|
.unwrap();
|
|
db.execute("UPDATE versions SET created=0", []).unwrap();
|
|
db.execute(
|
|
"INSERT INTO audit(time,event,pid,program) VALUES(?,'old-marker',1,'test')",
|
|
[now - 91 * 86400],
|
|
)
|
|
.unwrap();
|
|
db.execute(
|
|
"INSERT INTO audit(time,event,pid,program) VALUES(?,'edge-marker',1,'test')",
|
|
[now - 90 * 86400],
|
|
)
|
|
.unwrap();
|
|
c.prune(now).unwrap();
|
|
let requests = c.request_list().unwrap();
|
|
assert!(!requests.iter().any(|r| r.id == old));
|
|
assert!(requests.iter().any(|r| r.id == pending));
|
|
assert!(requests.iter().any(|r| r.id == edge));
|
|
let grants = c.grant_list().unwrap();
|
|
assert!(!grants.iter().any(|g| g.id == expired || g.id == revoked.id));
|
|
assert!(grants.iter().any(|g| g.id == live.id));
|
|
assert!(grants.iter().any(|g| g.id == protected.id));
|
|
let audit = c.audit_list().unwrap();
|
|
assert!(!audit.iter().any(|r| r.event == "old-marker"));
|
|
assert!(audit.iter().any(|r| r.event == "edge-marker"));
|
|
assert_eq!(c.item_versions(&item.id).unwrap().len(), 1);
|
|
}
|
|
#[test]
|
|
fn revoke_ids_programs_and_full_subtrees_without_widening_access() {
|
|
let (dir, c, caller, item) = fixture();
|
|
let root = c
|
|
.grant_create(
|
|
grant(&item, types::Subject::Conversation("root".into())),
|
|
&caller,
|
|
c.unlocked().unwrap(),
|
|
)
|
|
.unwrap();
|
|
let child = c
|
|
.grant_create(
|
|
grant(&item, types::Subject::Conversation("child".into())),
|
|
&caller,
|
|
c.unlocked().unwrap(),
|
|
)
|
|
.unwrap();
|
|
c.revoke_subjects(
|
|
&["root".into(), "child".into()],
|
|
&caller,
|
|
c.unlocked().unwrap(),
|
|
)
|
|
.unwrap();
|
|
assert!(c.grant_status(&root.id).unwrap().revoked);
|
|
assert!(c.grant_status(&child.id).unwrap().revoked);
|
|
let program = c
|
|
.grant_create(
|
|
grant(&item, types::Subject::Program("/bin/service.wasm".into())),
|
|
&caller,
|
|
c.unlocked().unwrap(),
|
|
)
|
|
.unwrap();
|
|
let other = c
|
|
.grant_create(
|
|
grant(&item, types::Subject::Program("/bin/other.wasm".into())),
|
|
&caller,
|
|
c.unlocked().unwrap(),
|
|
)
|
|
.unwrap();
|
|
let db = rusqlite::Connection::open(dir.path().join("cred.sqlite")).unwrap();
|
|
db.execute(
|
|
"INSERT INTO passkey_credentials VALUES('synthetic','test','{}')",
|
|
[],
|
|
)
|
|
.unwrap();
|
|
c.revoke_program("/bin/service.wasm", &caller).unwrap();
|
|
assert!(c.grant_status(&program.id).unwrap().revoked);
|
|
assert!(!c.grant_status(&other.id).unwrap().revoked);
|
|
c.revoke_grant(&other.id, &caller).unwrap();
|
|
assert!(c.grant_status(&other.id).unwrap().revoked);
|
|
}
|