use std::sync::{Arc, Mutex};
use syncbat::{
Core, Ctx, EffectClass, Handler, HandlerResult, OperationDescriptor, OperationEffectRow,
ReceiptEnvelope, ReceiptOutcome, ReceiptSink, ReceiptSinkError, RecordedReceipt, RuntimeError,
};
const REQUIRED_CAP: &str = "cap.elevated.v1";
const SCHEMA_IN: &str = "schema.cap.input.v1";
const SCHEMA_OUT: &str = "schema.cap.output.v1";
const RECEIPT_KIND: &str = "receipt.cap.v1";
struct EchoHandler;
impl Handler for EchoHandler {
fn handle(&mut self, input: &[u8], _cx: &mut Ctx<'_>) -> HandlerResult {
Ok(input.to_vec())
}
}
fn privileged_descriptor() -> OperationDescriptor {
OperationDescriptor::new(
"cap.op",
EffectClass::Inspect,
SCHEMA_IN,
SCHEMA_OUT,
RECEIPT_KIND,
)
.with_effect_row(OperationEffectRow::new().requires_capability(REQUIRED_CAP))
}
#[derive(Clone)]
struct CapturingSink {
seen: Arc<Mutex<Vec<ReceiptEnvelope>>>,
}
impl CapturingSink {
fn new() -> Self {
Self {
seen: Arc::new(Mutex::new(Vec::new())),
}
}
}
impl ReceiptSink for CapturingSink {
fn record_receipt(
&self,
envelope: &ReceiptEnvelope,
) -> Result<RecordedReceipt, ReceiptSinkError> {
self.seen
.lock()
.expect("capture lock")
.push(envelope.clone());
Ok(RecordedReceipt::new(envelope.clone()))
}
}
#[test]
fn dispatch_denies_operation_requiring_an_ungranted_capability(
) -> Result<(), Box<dyn std::error::Error>> {
let sink = CapturingSink::new();
let mut builder = Core::builder();
builder
.register(privileged_descriptor(), EchoHandler)
.expect("register");
builder.receipt_sink(sink.clone());
let mut core = builder.build().expect("build");
let err = match core.invoke("cap.op", b"payload".to_vec()) {
Ok(_) => {
return Err(std::io::Error::other(
"PROPERTY: an operation requiring an ungranted capability must be denied",
)
.into())
}
Err(error) => error,
};
assert!(
matches!(
err,
RuntimeError::Denied { ref name, ref code, .. }
if name == "cap.op" && code == "capability.denied"
),
"expected a capability.denied denial, got {err:?}"
);
let receipts = sink.seen.lock().expect("capture lock");
assert_eq!(receipts.len(), 1, "exactly one denial receipt is recorded");
let outcome = &receipts[0].outcome;
assert!(
matches!(
outcome,
ReceiptOutcome::Denied { code, message }
if code == "capability.denied" && message.contains(REQUIRED_CAP)
),
"denial must record a capability.denied outcome naming the token, got {outcome:?}"
);
Ok(())
}
#[test]
fn grant_capability_admits_the_same_operation() {
let mut builder = Core::builder();
builder
.register(privileged_descriptor(), EchoHandler)
.expect("register");
builder.grant_capability(REQUIRED_CAP);
builder.without_receipts();
let mut core = builder.build().expect("build");
let payload = b"payload".to_vec();
let result = core
.invoke("cap.op", payload.clone())
.expect("granted invoke succeeds");
assert_eq!(result.output(), payload.as_slice());
}
#[test]
fn grant_capabilities_admits_the_same_operation() {
let mut builder = Core::builder();
builder
.register(privileged_descriptor(), EchoHandler)
.expect("register");
builder.grant_capabilities([REQUIRED_CAP.to_owned(), "cap.other.v1".to_owned()]);
builder.without_receipts();
let mut core = builder.build().expect("build");
let payload = b"granted".to_vec();
let result = core
.invoke("cap.op", payload.clone())
.expect("granted invoke succeeds");
assert_eq!(result.output(), payload.as_slice());
}
#[test]
fn operation_without_extra_capabilities_runs_on_an_ungranted_core() {
let descriptor = OperationDescriptor::new(
"cap.plain",
EffectClass::Inspect,
SCHEMA_IN,
SCHEMA_OUT,
RECEIPT_KIND,
);
let mut builder = Core::builder();
builder.register(descriptor, EchoHandler).expect("register");
builder.without_receipts();
let mut core = builder.build().expect("build");
let payload = b"plain".to_vec();
let result = core
.invoke("cap.plain", payload.clone())
.expect("plain invoke succeeds");
assert_eq!(result.output(), payload.as_slice());
}