1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// Composition toys (javascript/src/compose).
// caller: calls `nsid` with `input` plus a `progress` callback (through `proxy` when given), answers each callback,
// returns the outcome.
// lookup: doubles `n`, calling the `progress` capability it was given first when there is one.
// even: a policy, allows calls whose input `n` is even.
// spy: a policy that tries to invoke the `host` capability in the call it checks; it allows the call
// only when that is refused (a policy run must not hold the caller's capabilities).
use abi::{Call, Event, Handle, Run, cap, cap_token, entries};
use serde_json::{Value, json};
fn caller_handler(run: Run<Value>) -> Result<Handle, String> {
let mut state = if run.state.is_null() { json!({ "progress": [] }) } else { run.state };
Ok(match run.event {
Event::Start => {
let nsid = run.args["nsid"].as_str().ok_or("nsid is required")?;
let mut input = run.args.get("input").cloned().unwrap_or_else(|| json!({}));
input["progress"] = cap("progress");
let call = Call::new("q", nsid).input(input);
let call = match run.args["proxy"].as_str() {
Some(proxy) => call.proxy(proxy),
None => call,
};
Handle::new().call(call).state(state)
}
Event::Callback { cap, input, reply, .. } if cap == "progress" => {
let list = state["progress"].as_array_mut().ok_or("bad state")?;
list.push(input.clone());
let id = format!("reply-{}", list.len());
Handle::new().call(Call::new(id, reply).input(json!({ "seen": input }))).state(state)
}
Event::Result { id, result, .. } if id == "q" => {
let outcome = match result.output() {
Ok(output) => json!({ "ok": true, "output": output }),
Err(error) => json!({ "ok": false, "error": error }),
};
Handle::new().result(json!({ "q": outcome, "progress": state["progress"] }))
}
_ => Handle::new(),
})
}
fn lookup_handler(run: Run<Value>) -> Result<Handle, String> {
let n = run.args["n"].as_i64().ok_or("n is required")?;
let answer = |ack: Value| Handle::new().result(json!({ "doubled": n * 2, "ack": ack, "caller": run.args["caller"] }));
Ok(match (run.event, cap_token(&run.args["progress"])) {
(Event::Start, Some(token)) => Handle::new().call(Call::new("p", token).input(json!({ "step": 1 }))),
(Event::Start, None) => answer(Value::Null),
(Event::Result { id, result, .. }, _) if id == "p" => answer(result.output().cloned().unwrap_or_else(|e| json!({ "error": e }))),
_ => Handle::new(),
})
}
fn even_handler(run: Run<Value>) -> Result<Handle, String> {
let n = run.args["call"]["input"]["n"].as_i64();
let allow = n.is_some_and(|n| n % 2 == 0);
let violations = if allow { json!([]) } else { json!([{ "msg": format!("n {} is not even", n.map_or("missing".into(), |n| n.to_string())), "policyId": "even" }]) };
Ok(Handle::new().result(json!({ "allow": allow, "violations": violations })).done())
}
fn spy_handler(run: Run<Value>) -> Result<Handle, String> {
let verdict = |allow: bool, msg: String| Handle::new().result(json!({ "allow": allow, "violations": [{ "msg": msg, "policyId": "spy" }] })).done();
Ok(match (run.event, cap_token(&run.args["call"]["input"]["host"])) {
(Event::Start, Some(token)) => Handle::new().call(Call::new("h", token).input(json!({ "from": "policy" }))),
(Event::Start, None) => verdict(true, "no host capability".into()),
(Event::Result { id, result, .. }, _) if id == "h" => match result.output() {
Ok(_) => verdict(false, "the policy invoked the caller's capability".into()),
Err(error) => verdict(true, error),
},
_ => Handle::new(),
})
}
entries! { caller => caller_handler, lookup => lookup_handler, even => even_handler, spy => spy_handler }