use crate::context::{Capabilities, CapabilityBit, NativeFnGate};
pub trait CapabilityGate: Send + Sync {
fn check(&self, cap: CapabilityBit) -> Result<(), CapabilityBit>;
fn check_gate(&self, gate: &NativeFnGate) -> Result<(), CapabilityBit> {
if gate.reads_fs {
self.check(CapabilityBit::ReadsFs)?;
}
if gate.writes_fs {
self.check(CapabilityBit::WritesFs)?;
}
if gate.network {
self.check(CapabilityBit::Network)?;
}
if gate.reads_clock {
self.check(CapabilityBit::ReadsClock)?;
}
if gate.reads_env {
self.check(CapabilityBit::ReadsEnv)?;
}
if gate.uses_rng {
self.check(CapabilityBit::UsesRng)?;
}
Ok(())
}
}
impl CapabilityGate for Capabilities {
fn check(&self, cap: CapabilityBit) -> Result<(), CapabilityBit> {
let granted = match cap {
CapabilityBit::ReadsFs => self.reads_fs,
CapabilityBit::WritesFs => self.writes_fs,
CapabilityBit::Network => self.network,
CapabilityBit::ReadsClock => self.reads_clock,
CapabilityBit::ReadsEnv => self.reads_env,
CapabilityBit::UsesRng => self.uses_rng,
};
if granted {
Ok(())
} else {
Err(cap)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_capabilities_deny_every_bit() {
let caps = Capabilities::default();
for bit in [
CapabilityBit::ReadsFs,
CapabilityBit::WritesFs,
CapabilityBit::Network,
CapabilityBit::ReadsClock,
CapabilityBit::ReadsEnv,
CapabilityBit::UsesRng,
] {
let denied = caps.check(bit).expect_err("must deny");
assert_eq!(denied, bit);
}
}
#[test]
fn all_granted_satisfies_every_bit() {
let caps = Capabilities::all_granted();
for bit in [
CapabilityBit::ReadsFs,
CapabilityBit::WritesFs,
CapabilityBit::Network,
CapabilityBit::ReadsClock,
CapabilityBit::ReadsEnv,
CapabilityBit::UsesRng,
] {
caps.check(bit).expect("must grant");
}
}
#[test]
fn check_gate_short_circuits_on_first_missing_bit() {
let caps = Capabilities::default();
let mut gate = NativeFnGate::default();
gate.reads_fs = true;
gate.network = true;
let denied = caps.check_gate(&gate).expect_err("must deny");
assert_eq!(denied, CapabilityBit::ReadsFs);
}
#[test]
fn check_gate_passes_when_every_required_bit_granted() {
let mut caps = Capabilities::default();
caps.reads_fs = true;
caps.network = true;
let mut gate = NativeFnGate::default();
gate.reads_fs = true;
gate.network = true;
caps.check_gate(&gate).expect("must allow");
}
#[test]
fn pure_gate_passes_against_zero_grant() {
let caps = Capabilities::default();
let gate = NativeFnGate::default();
caps.check_gate(&gate).expect("pure gate must always pass");
}
#[test]
fn deny_message_carries_capability_name() {
assert!(CapabilityBit::Network.deny_message().contains("network"));
assert!(CapabilityBit::ReadsFs.deny_message().contains("reads_fs"));
}
struct DenyAllGate;
impl CapabilityGate for DenyAllGate {
fn check(&self, cap: CapabilityBit) -> Result<(), CapabilityBit> {
Err(cap)
}
}
#[test]
fn host_supplied_gate_can_override_policy() {
let mut gate = NativeFnGate::default();
gate.reads_fs = true;
let denied = DenyAllGate.check_gate(&gate).expect_err("must deny");
assert_eq!(denied, CapabilityBit::ReadsFs);
}
}