aboutsummaryrefslogtreecommitdiffstats
path: root/src/access/rules.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/access/rules.rs')
-rw-r--r--src/access/rules.rs93
1 files changed, 93 insertions, 0 deletions
diff --git a/src/access/rules.rs b/src/access/rules.rs
index 1ce22ee..f285328 100644
--- a/src/access/rules.rs
+++ b/src/access/rules.rs
@@ -19,6 +19,8 @@
//! * User data: by a user who may write there.
//! * Under a strict policy (`access::policy`), the values their users sign
//! (`access::envelope`) only as they signed them (`signed_values`).
+//! * Where the swarm declares values sealed (`access::sealed`), only those sealed as
+//! declared (`sealed_values`).
//!
//! In a swarm without a user registry (`access::registry`), whatever a user would need,
//! any member may.
@@ -157,6 +159,40 @@ pub(crate) fn judge(doc: &Document, epoch: Epoch, change: &Change<'_>) -> Result
if super::policy::of(&at).strict() {
signed_values(&at, change.ops).or_else(refuse)?;
}
+ let declared = super::sealed::declared(&at);
+ if !declared.is_empty() {
+ sealed_values(&at, &declared, change.ops).or_else(refuse)?;
+ }
+ Ok(())
+}
+
+/// Where values are declared sealed (`declared`), `ops` write only sealed values as
+/// declared, judged at `at`, as their writer saw it (`sealed::check`): at such a place, a
+/// string put there that is one (or deleting what is there); nothing within one; nothing
+/// in an object nowhere to be found, which might be one.
+fn sealed_values(
+ at: &At<'_>,
+ declared: &[super::sealed::Declared],
+ ops: &[Op],
+) -> Result<(), String> {
+ for op in ops {
+ let path = &op.path;
+ if op.action == Action::Nowhere {
+ return Err("it writes in an object nowhere to be found".to_owned());
+ }
+ if let Some(place) = super::sealed::place(declared, path) {
+ match &op.action {
+ Action::PutString(text) => {
+ super::sealed::check(at, place, &serde_json::Value::String(text.clone()))
+ .map_err(|why| format!("{}: {why}", shown(path)))?;
+ }
+ Action::Delete => {}
+ _ => return Err(format!("{} holds a sealed value", shown(path))),
+ }
+ } else if super::sealed::within(declared, path) {
+ return Err(format!("{} is within a sealed value", shown(path)));
+ }
+ }
Ok(())
}
@@ -606,6 +642,63 @@ mod tests {
assert!(refused(judged(&doc, vec![op("", Action::Nowhere)])));
}
+ /// Where values are declared sealed: only sealed ones, as declared, with or without
+ /// a strict policy.
+ #[test]
+ fn sealed_values_as_declared() {
+ use crate::access::sealed::testing::sealed_for;
+ use crate::access::sshsig::PublicKey;
+ let key = PublicKey([8; 32]);
+ let mut doc = swarm(true);
+ let mut alice = registry::entry("a", 2, &[(Vec::new(), &[Right::Write][..])]);
+ alice["keys"] = json!({ "k": key.line() });
+ write(&mut doc, "_continuum/users/alice", alice);
+ write(
+ &mut doc,
+ "_continuum/access/sealed",
+ json!([{"path": ["secrets", "*"], "readers": ["alice"]}]),
+ );
+ let judged = |ops: Vec<Op>| {
+ let deps = doc.get_heads();
+ let touched: BTreeSet<Path> = ops.iter().map(|op| op.path.clone()).collect();
+ judge(
+ &doc,
+ Epoch::GENESIS,
+ &Change {
+ hash: ChangeHash([9; 32]),
+ deps: &deps,
+ seen: &deps,
+ committer: Some("a"),
+ user: Some("alice"),
+ paths: &touched,
+ ops: &ops,
+ },
+ )
+ };
+ let op = |at: &str, action: Action| Op {
+ path: path(at),
+ action,
+ };
+ let sealed = sealed_for(&[key]);
+ assert!(judged(vec![op("secrets/db", Action::PutString(sealed))]).is_ok());
+ assert!(judged(vec![op("secrets/db", Action::Delete)]).is_ok());
+ assert!(refused(judged(vec![op(
+ "secrets/db",
+ Action::PutString("hunter2".to_owned())
+ )])));
+ let stray = sealed_for(&[key, PublicKey([9; 32])]);
+ assert!(refused(judged(vec![op(
+ "secrets/db",
+ Action::PutString(stray)
+ )])));
+ assert!(refused(judged(vec![op("secrets/db", Action::MakeMap)])));
+ assert!(refused(judged(vec![op(
+ "secrets/db/password",
+ Action::Other
+ )])));
+ assert!(judged(vec![op("notes/x", Action::PutString("plain".to_owned()))]).is_ok());
+ }
+
/// Roots: the first change of a swarm's first epoch, a later epoch's snapshot.
#[test]
fn roots() {