use std::{collections::BTreeSet, fmt, sync::Arc};
use crate::{
error::{Error, Result},
id::Symbol,
};
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct CapabilityName(Arc<str>);
impl CapabilityName {
pub fn new(name: impl Into<Arc<str>>) -> Self {
Self(name.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn as_symbol(&self) -> Symbol {
Symbol::qualified("capability", self.as_str().to_owned())
}
}
impl fmt::Display for CapabilityName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct CapabilitySet {
granted: BTreeSet<CapabilityName>,
}
impl CapabilitySet {
pub fn new() -> Self {
Self::default()
}
pub fn grant(mut self, capability: CapabilityName) -> Self {
self.granted.insert(capability);
self
}
pub fn insert(&mut self, capability: CapabilityName) {
self.granted.insert(capability);
}
pub fn contains(&self, capability: &CapabilityName) -> bool {
self.granted.contains(capability)
}
pub fn iter(&self) -> impl Iterator<Item = &CapabilityName> {
self.granted.iter()
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum TrustLevel {
#[default]
Untrusted,
TrustedSource,
HostInternal,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ReadPolicy {
pub trust: TrustLevel,
pub capabilities: CapabilitySet,
}
impl ReadPolicy {
pub fn require(&self, capability: &CapabilityName) -> Result<()> {
if !self.capabilities.contains(capability) {
return Err(Error::CapabilityDenied {
capability: capability.clone(),
});
}
if self.trust == TrustLevel::Untrusted && capability == &read_eval_capability() {
return Err(Error::TrustDenied {
capability: capability.clone(),
trust: self.trust,
});
}
Ok(())
}
}
pub fn read_construct_capability() -> CapabilityName {
CapabilityName::new("read-construct")
}
pub fn read_eval_capability() -> CapabilityName {
CapabilityName::new("read-eval")
}
pub fn native_dynamic_load_capability() -> CapabilityName {
CapabilityName::new("loader.native")
}
pub fn macro_expand_capability() -> CapabilityName {
CapabilityName::new("macro.expand")
}
pub fn macro_expand_compile_capability() -> CapabilityName {
CapabilityName::new("macro.expand.compile")
}
pub fn macro_expand_eval_capability() -> CapabilityName {
CapabilityName::new("macro.expand.eval")
}
pub fn macro_expand_read_capability() -> CapabilityName {
CapabilityName::new("macro.expand.read")
}
pub fn eval_fabric_capability() -> CapabilityName {
CapabilityName::new("eval.fabric")
}
pub fn eval_remote_capability() -> CapabilityName {
CapabilityName::new("eval.remote")
}
pub fn control_prompt_capability() -> CapabilityName {
CapabilityName::new("control.prompt")
}
pub fn control_capture_capability() -> CapabilityName {
CapabilityName::new("control.capture")
}
pub fn control_resume_capability() -> CapabilityName {
CapabilityName::new("control.resume")
}
pub fn control_multishot_capability() -> CapabilityName {
CapabilityName::new("control.multishot")
}
pub fn fact_private_capability() -> CapabilityName {
CapabilityName::new("kernel.fact.private")
}
pub fn browse_read_capability() -> CapabilityName {
CapabilityName::new("browse.read")
}
pub fn browse_run_tests_capability() -> CapabilityName {
CapabilityName::new("browse.run-tests")
}
pub fn browse_internal_capability() -> CapabilityName {
CapabilityName::new("browse.internal")
}
pub fn logic_db_write_capability() -> CapabilityName {
CapabilityName::new("logic.db.write")
}
pub fn logic_consult_file_capability() -> CapabilityName {
CapabilityName::new("logic.consult.file")
}
pub fn logic_tool_call_capability() -> CapabilityName {
CapabilityName::new("logic.tool-call")
}
pub fn config_list_impl_capability() -> CapabilityName {
CapabilityName::new("config.list.impl")
}
pub fn config_table_impl_capability() -> CapabilityName {
CapabilityName::new("config.table.impl")
}
pub fn list_force_unbounded_capability() -> CapabilityName {
CapabilityName::new("list.force.unbounded")
}
pub fn table_fs_capability() -> CapabilityName {
CapabilityName::new("table.fs")
}
pub fn table_fs_read_capability() -> CapabilityName {
CapabilityName::new("table.fs.read")
}
pub fn table_fs_write_capability() -> CapabilityName {
CapabilityName::new("table.fs.write")
}
pub fn table_fs_mkdir_capability() -> CapabilityName {
CapabilityName::new("table.fs.mkdir")
}
pub fn table_fs_rmdir_capability() -> CapabilityName {
CapabilityName::new("table.fs.rmdir")
}
pub fn table_db_capability() -> CapabilityName {
CapabilityName::new("table.db")
}
pub fn table_db_read_capability() -> CapabilityName {
CapabilityName::new("table.db.read")
}
pub fn table_db_write_capability() -> CapabilityName {
CapabilityName::new("table.db.write")
}
pub fn table_db_mkdir_capability() -> CapabilityName {
CapabilityName::new("table.db.mkdir")
}
pub fn table_db_rmdir_capability() -> CapabilityName {
CapabilityName::new("table.db.rmdir")
}
pub fn table_remote_capability() -> CapabilityName {
CapabilityName::new("table.remote")
}
pub fn registry_catalog_read_capability() -> CapabilityName {
CapabilityName::new("registry.catalog.read")
}
#[cfg(test)]
mod tests {
use super::{
CapabilitySet, ReadPolicy, TrustLevel, read_construct_capability, read_eval_capability,
};
use crate::Error;
fn policy(trust: TrustLevel, capabilities: &[crate::CapabilityName]) -> ReadPolicy {
ReadPolicy {
trust,
capabilities: capabilities
.iter()
.cloned()
.fold(CapabilitySet::new(), |set, capability| {
set.grant(capability)
}),
}
}
#[test]
fn untrusted_policy_denies_read_eval_even_when_capability_is_present() {
let err = policy(TrustLevel::Untrusted, &[read_eval_capability()])
.require(&read_eval_capability())
.unwrap_err();
assert!(matches!(
err,
Error::TrustDenied { capability, trust }
if capability == read_eval_capability() && trust == TrustLevel::Untrusted
));
}
#[test]
fn trusted_policy_allows_read_eval_when_capability_is_present() {
policy(TrustLevel::TrustedSource, &[read_eval_capability()])
.require(&read_eval_capability())
.unwrap();
}
#[test]
fn non_eval_capabilities_remain_capability_gated() {
policy(TrustLevel::Untrusted, &[read_construct_capability()])
.require(&read_construct_capability())
.unwrap();
}
}