mod common;
use std::sync::Arc;
use common::*;
use ikigai_core::{Capability, Error, Verb};
use ikigai_script::authority::{
same_for_all, Ceiling, ANONYMOUS, CAP_READ_PUBLIC, CAP_RUN_PUBLIC, UNSTAMPED,
};
use ikigai_script::{MemoryBackend, Run};
fn publisher(name: &str, more: &[&str]) -> Capability {
let write = format!("urn:cap:script:write:{name}");
let mut scopes = vec![write.as_str(), "urn:cap:lisp"];
scopes.extend_from_slice(more);
cap(&scopes)
}
fn runner(name: &str, more: &[&str]) -> Capability {
let run = format!("urn:cap:script:run:{name}");
let mut scopes = vec![run.as_str(), "urn:cap:lisp"];
scopes.extend_from_slice(more);
cap(&scopes)
}
fn lines(text: &str) -> Vec<&str> {
text.lines().collect()
}
#[test]
fn a_publisher_cannot_give_a_script_more_than_they_hold() {
let host = host();
let message = denied(call(
&host.kernel,
&publisher("grab", &[]),
Verb::Sink,
"urn:script:grab",
&[("content", "1"), ("requires", CAP_VAULT)],
));
assert!(message.contains(CAP_VAULT), "{message}");
assert!(
matches!(
call(&host.kernel, &Capability::root(), Verb::Exists, "urn:script:grab", &[]),
Ok(ref t) if t == "false\n"
),
"a refused publish writes nothing"
);
call(
&host.kernel,
&publisher("grab", &[CAP_VAULT]),
Verb::Sink,
"urn:script:grab",
&[("content", "1"), ("requires", CAP_VAULT)],
)
.unwrap();
}
#[test]
fn a_publisher_who_may_not_run_lisp_may_not_publish_it() {
let host = host();
let message = denied(call(
&host.kernel,
&cap(&["urn:cap:script:write:x"]),
Verb::Sink,
"urn:script:x",
&[("content", "1")],
));
assert!(message.contains("urn:cap:lisp"), "{message}");
}
#[test]
fn writing_and_retiring_need_their_own_grants_for_that_script() {
let host = host();
publish(&host.kernel, "mine", "1", &[]);
denied(call(
&host.kernel,
&publisher("other", &[]),
Verb::Sink,
"urn:script:mine",
&[("content", "2")],
));
denied(call(
&host.kernel,
&cap(&["urn:cap:lisp"]),
Verb::Sink,
"urn:script:mine",
&[("content", "2")],
));
denied(call(
&host.kernel,
&publisher("mine", &["urn:cap:script:delete:other"]),
Verb::Delete,
"urn:script:mine",
&[],
));
call(
&host.kernel,
&cap(&["urn:cap:script:delete:mine"]),
Verb::Delete,
"urn:script:mine",
&[],
)
.unwrap();
}
#[test]
fn a_runner_without_the_run_grant_is_refused() {
let host = host();
publish(&host.kernel, "job", "(+ 1 2)", &[]);
for iri in ["urn:script:job:result", "urn:script:job:runs"] {
let verb = if iri.ends_with("runs") {
Verb::Sink
} else {
Verb::Source
};
denied(call(&host.kernel, &cap(&["urn:cap:lisp"]), verb, iri, &[]));
denied(call(&host.kernel, &runner("other", &[]), verb, iri, &[]));
denied(call(
&host.kernel,
&cap(&["urn:cap:lisp", "urn:cap:script:read:job"]),
verb,
iri,
&[],
));
}
assert_eq!(host.evals(), 0);
assert_eq!(
call(
&host.kernel,
&runner("job", &[]),
Verb::Source,
"urn:script:job:result",
&[]
)
.unwrap(),
"3"
);
}
#[test]
fn a_refused_run_records_nothing() {
let host = host();
publish(&host.kernel, "job", "1", &[]);
denied(call(
&host.kernel,
&runner("other", &[]),
Verb::Sink,
"urn:script:job:runs",
&[],
));
assert_eq!(
ok(&host.kernel, Verb::Exists, "urn:script:job:run:1", &[]),
"false\n"
);
}
#[test]
fn a_runner_without_the_language_grant_is_refused() {
let host = host();
publish(&host.kernel, "job", "1", &[]);
let message = denied(call(
&host.kernel,
&cap(&["urn:cap:script:run:job"]),
Verb::Source,
"urn:script:job:result",
&[],
));
assert!(message.contains("urn:cap:lisp"), "{message}");
}
#[test]
fn a_run_holds_only_what_both_runner_and_script_hold() {
let host = host();
publish(
&host.kernel,
"probe",
"(source \"urn:test:whoami\")",
&[("requires", CAP_VAULT)],
);
let rich = runner("probe", &[CAP_VAULT, "urn:cap:mail:send"]);
assert_eq!(
lines(
&call(
&host.kernel,
&rich,
Verb::Source,
"urn:script:probe:result",
&[]
)
.unwrap()
),
vec!["urn:cap:lisp", CAP_VAULT]
);
let poor = runner("probe", &[]);
assert_eq!(
lines(
&call(
&host.kernel,
&poor,
Verb::Source,
"urn:script:probe:result",
&[]
)
.unwrap()
),
vec!["urn:cap:lisp"]
);
publish(
&host.kernel,
"stash",
"(sink \"urn:test:vault\" \"x\")",
&[("requires", CAP_VAULT)],
);
denied(call(
&host.kernel,
&runner("stash", &[]),
Verb::Source,
"urn:script:stash:result",
&[],
));
call(
&host.kernel,
&runner("stash", &[CAP_VAULT]),
Verb::Source,
"urn:script:stash:result",
&[],
)
.unwrap();
}
#[test]
fn the_host_ceiling_clamps() {
let host = host_with(
Arc::new(MemoryBackend::new()),
same_for_all(Ceiling::scoped(["urn:cap:lisp"])),
);
publish(
&host.kernel,
"stash",
"(sink \"urn:test:vault\" \"x\")",
&[("requires", CAP_VAULT)],
);
let message = denied(call(
&host.kernel,
&Capability::root(),
Verb::Source,
"urn:script:stash:result",
&[],
));
assert!(message.contains(CAP_VAULT), "{message}");
publish(
&host.kernel,
"probe",
"(source \"urn:test:whoami\")",
&[("requires", CAP_VAULT)],
);
assert_eq!(
ok(&host.kernel, Verb::Source, "urn:script:probe:result", &[]),
"urn:cap:lisp"
);
}
#[test]
fn a_host_that_fails_closed_runs_nothing() {
let host = host_with(
Arc::new(MemoryBackend::new()),
same_for_all(Ceiling::nothing()),
);
publish(&host.kernel, "pure", "(+ 1 2)", &[]);
let message = denied(call(
&host.kernel,
&Capability::root(),
Verb::Source,
"urn:script:pure:result",
&[],
));
assert!(message.contains("urn:cap:lisp"), "{message}");
}
#[test]
fn the_ceiling_is_asked_per_script() {
let policy: ikigai_script::authority::CeilingPolicy = Arc::new(|name: &str| {
if name == "trusted" {
Ceiling::unbounded()
} else {
Ceiling::scoped(["urn:cap:lisp"])
}
});
let host = host_with(Arc::new(MemoryBackend::new()), policy);
for name in ["trusted", "untrusted"] {
publish(
&host.kernel,
name,
"(source \"urn:test:whoami\")",
&[("requires", CAP_VAULT)],
);
}
assert_eq!(
ok(&host.kernel, Verb::Source, "urn:script:trusted:result", &[]),
format!("urn:cap:lisp\n{CAP_VAULT}")
);
assert_eq!(
ok(
&host.kernel,
Verb::Source,
"urn:script:untrusted:result",
&[]
),
"urn:cap:lisp"
);
}
#[test]
fn the_publishers_exclusions_travel_with_the_script() {
let host = host();
let read_root = "urn:cap:fs:read:/root";
let not_secret = "urn:cap:fs:read:-/root/secret";
call(
&host.kernel,
&publisher("reader", &[read_root, not_secret]),
Verb::Sink,
"urn:script:reader",
&[
("content", "(source \"urn:test:whoami\")"),
("requires", read_root),
],
)
.unwrap();
assert_eq!(
lines(&ok(
&host.kernel,
Verb::Source,
"urn:script:reader:result",
&[]
)),
vec![not_secret, read_root, "urn:cap:lisp"]
);
}
fn anonymous() -> Capability {
cap(&[CAP_RUN_PUBLIC, CAP_READ_PUBLIC, "urn:cap:lisp"])
}
#[test]
fn an_anonymous_caller_runs_only_a_public_script() {
let host = host();
publish(&host.kernel, "open", "(+ 1 2)", &[("public", "true")]);
publish(&host.kernel, "closed", "(+ 1 2)", &[]);
publish(
&host.kernel,
"unfinished",
"(+ 1 2)",
&[("public", "true"), ("state", "draft")],
);
assert_eq!(
call(
&host.kernel,
&anonymous(),
Verb::Source,
"urn:script:open:result",
&[]
)
.unwrap(),
"3"
);
call(
&host.kernel,
&anonymous(),
Verb::Sink,
"urn:script:open:runs",
&[],
)
.unwrap();
for name in ["closed", "unfinished", "absent"] {
let iri = format!("urn:script:{name}:result");
denied(call(&host.kernel, &anonymous(), Verb::Source, &iri, &[]));
}
denied(call(
&host.kernel,
&anonymous(),
Verb::Sink,
"urn:script:open",
&[("content", "(+ 2 2)")],
));
}
#[test]
fn an_anonymous_caller_reads_only_a_public_script_and_learns_nothing_else() {
let host = host();
publish(&host.kernel, "open", "(+ 1 2)", &[("public", "true")]);
publish(&host.kernel, "closed", "(+ 1 2)", &[]);
assert_eq!(
call(
&host.kernel,
&anonymous(),
Verb::Source,
"urn:script:open",
&[]
)
.unwrap(),
"(+ 1 2)"
);
let private = denied(call(
&host.kernel,
&anonymous(),
Verb::Source,
"urn:script:closed",
&[],
));
let missing = denied(call(
&host.kernel,
&anonymous(),
Verb::Source,
"urn:script:absent",
&[],
));
assert_eq!(
private.replace("closed", "NAME"),
missing.replace("absent", "NAME")
);
call(
&host.kernel,
&anonymous(),
Verb::Sink,
"urn:script:open:runs",
&[],
)
.unwrap();
denied(call(
&host.kernel,
&anonymous(),
Verb::Source,
"urn:script:open:run:1",
&[],
));
let catalog = call(
&host.kernel,
&anonymous(),
Verb::Source,
"urn:script:catalog",
&[],
)
.unwrap();
assert!(catalog.contains("urn:script:open "), "{catalog}");
assert!(!catalog.contains("closed"), "{catalog}");
}
#[test]
fn eval_runs_under_the_callers_own_authority_and_nothing_more() {
let host = host();
let caller = cap(&["urn:cap:lisp"]);
assert_eq!(
call(
&host.kernel,
&caller,
Verb::Sink,
"urn:script:eval",
&[("content", "(source \"urn:test:whoami\")")],
)
.unwrap(),
"urn:cap:lisp"
);
denied(call(
&host.kernel,
&caller,
Verb::Sink,
"urn:script:eval",
&[("content", "(sink \"urn:test:vault\" \"x\")")],
));
call(
&host.kernel,
&cap(&["urn:cap:lisp", CAP_VAULT]),
Verb::Sink,
"urn:script:eval",
&[("content", "(sink \"urn:test:vault\" \"x\")")],
)
.unwrap();
denied(call(
&host.kernel,
&cap(&[CAP_VAULT]),
Verb::Sink,
"urn:script:eval",
&[("content", "1")],
));
}
#[test]
fn eval_offers_only_the_language_its_floor_is_for() {
let host = host();
let contract: ikigai_core::Description = serde_json::from_str(&ok(
&host.kernel,
Verb::Meta,
"urn:script:eval",
&[("as", "application/json")],
))
.unwrap();
let sink = contract
.action_specs()
.into_iter()
.find(|a| a.verb == Verb::Sink)
.expect("a Sink action");
assert_eq!(sink.requires, vec!["urn:cap:lisp".to_string()]);
let language = sink
.inputs
.iter()
.find(|i| i.name == "language")
.expect("a language argument");
assert_eq!(language.one_of, vec!["lisp".to_string()]);
for (language, points_at) in [("plan", "urn:plan:eval"), ("sparql", "Protocol")] {
match call(
&host.kernel,
&Capability::root(),
Verb::Sink,
"urn:script:eval",
&[("content", "x"), ("language", language)],
) {
Err(Error::InvalidArgument { name, detail }) => {
assert_eq!(name, "language");
assert!(detail.contains(points_at), "{detail}");
}
other => panic!("{language}: expected InvalidArgument, got {other:?}"),
}
}
}
#[test]
fn eval_saves_a_draft_only_with_that_scripts_write_grant() {
let host = host();
denied(call(
&host.kernel,
&cap(&["urn:cap:lisp"]),
Verb::Sink,
"urn:script:eval",
&[("content", "(+ 1 2)"), ("save", "keeper")],
));
assert_eq!(
ok(&host.kernel, Verb::Exists, "urn:script:keeper", &[]),
"false\n"
);
assert_eq!(
call(
&host.kernel,
&cap(&["urn:cap:lisp", "urn:cap:script:write:keeper"]),
Verb::Sink,
"urn:script:eval",
&[("content", "(+ 1 2)"), ("save", "keeper")],
)
.unwrap(),
"3"
);
let record: serde_json::Value = serde_json::from_str(&ok(
&host.kernel,
Verb::Source,
"urn:script:keeper",
&[("as", "application/json")],
))
.unwrap();
assert_eq!(record["state"], "draft");
assert_eq!(record["source"], "(+ 1 2)");
}
#[test]
fn saving_a_draft_cuts_a_cached_read_of_the_script() {
let host = host();
publish(&host.kernel, "notes", "1", &[]);
assert_eq!(ok(&host.kernel, Verb::Source, "urn:script:notes", &[]), "1");
ok(
&host.kernel,
Verb::Sink,
"urn:script:eval",
&[("content", "2"), ("save", "notes")],
);
assert_eq!(ok(&host.kernel, Verb::Source, "urn:script:notes", &[]), "2");
}
#[test]
fn the_principal_is_the_capabilitys_never_the_callers() {
let host = host();
let brian = "urn:test:person:brian";
call(
&host.kernel,
&publisher("job", &[]).with_principal(brian).unwrap(),
Verb::Sink,
"urn:script:job",
&[
("content", "1"),
("public", "true"),
("principal", "urn:test:person:mallory"),
],
)
.unwrap();
call(
&host.kernel,
&anonymous().with_principal(ANONYMOUS).unwrap(),
Verb::Sink,
"urn:script:job:runs",
&[("principal", brian)],
)
.unwrap();
call(
&host.kernel,
&runner("job", &[]).with_principal(brian).unwrap(),
Verb::Sink,
"urn:script:job:runs",
&[],
)
.unwrap();
ok(&host.kernel, Verb::Sink, "urn:script:job:runs", &[]);
call(
&host.kernel,
&runner("job", &[]),
Verb::Sink,
"urn:script:job:runs",
&[],
)
.unwrap();
let principal = |id: u64| {
let run: Run = serde_json::from_str(&ok(
&host.kernel,
Verb::Source,
&format!("urn:script:job:run:{id}"),
&[("as", "application/json")],
))
.unwrap();
run.principal
};
assert_eq!(principal(1), ANONYMOUS);
assert_eq!(principal(2), brian);
assert_eq!(principal(3), UNSTAMPED);
assert_eq!(principal(4), UNSTAMPED);
let record: serde_json::Value = serde_json::from_str(&ok(
&host.kernel,
Verb::Source,
"urn:script:job",
&[("as", "application/json")],
))
.unwrap();
assert_eq!(record["publisher"], brian);
}
#[test]
fn reading_needs_the_read_grant_for_that_script() {
let host = host();
publish(&host.kernel, "secret", "(+ 1 2)", &[]);
denied(call(
&host.kernel,
&cap(&["urn:cap:script:read:other"]),
Verb::Source,
"urn:script:secret",
&[],
));
denied(call(
&host.kernel,
&runner("secret", &["urn:cap:script:read:other"]),
Verb::Source,
"urn:script:secret",
&[],
));
assert_eq!(
call(
&host.kernel,
&cap(&["urn:cap:script:read:secret"]),
Verb::Source,
"urn:script:secret",
&[],
)
.unwrap(),
"(+ 1 2)"
);
let missing = call(
&host.kernel,
&cap(&["urn:cap:script:read:absent"]),
Verb::Source,
"urn:script:absent",
&[],
);
assert!(matches!(missing, Err(Error::NotFound(_))), "{missing:?}");
}