1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
|
//! The swarm's write protection policy (`_continuum/access/policy`): whether every node
//! judges received changes by rules by path before applying them (`access::screen`,
//! `access::rules`). Absent, nobody does: what exists keeps working as it did. Written
//! by a user who may write protocol data, as any of it.
//!
//! `{"version": 1, "signatures": "optional"}`: rules enforced; a change is its claimed
//! committer's whether or not a signature proves it (`document::signatures`).
//! `"required"`: a change is applied once a signature by its committer vouches for it,
//! and waits until then; every node signs every change it makes. `"values": "signed"`
//! (strict): signatures required, and what decides who may do what (the user registry,
//! the policy and the rest of `_continuum/access`, bans) and the paths listed in
//! `_continuum/access/userSigned` hold values their users signed (`access::envelope`),
//! so no member writes there but as a user signed it. A policy
//! this node doesn't know (another version, or a setting it lacks) stops it from taking
//! writes through the API, and it receives without judging: it can't judge as the
//! others do, so it writes nothing they might refuse, until it is upgraded.
use serde_json::Value as JsonValue;
use crate::document::{ReadDoc, meta, store};
/// The policy versions this node knows.
pub(crate) const VERSION: u64 = 1;
/// Where the policy is.
pub(crate) fn path() -> Vec<String> {
meta::path(&[meta::ACCESS, "policy"])
}
/// What the policy says.
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum Policy {
/// None: changes aren't judged.
Open,
/// Rules by path enforced; with signatures `required`, only on changes a signature
/// proves.
Enforced { required: bool, strict: bool },
/// One this node doesn't know, and why.
Unknown(String),
}
impl Policy {
pub(crate) fn enforced(&self) -> bool {
matches!(self, Policy::Enforced { .. })
}
/// Whether changes need a signature (`signatures: "required"`).
pub(crate) fn required(&self) -> bool {
matches!(self, Policy::Enforced { required: true, .. })
}
/// Whether values at the user-signed paths need their users' signatures
/// (`values: "signed"`, `access::envelope::signed_paths`).
#[cfg_attr(not(test), allow(dead_code))]
pub(crate) fn strict(&self) -> bool {
matches!(self, Policy::Enforced { strict: true, .. })
}
}
/// The policy `doc` holds.
pub(crate) fn of(doc: &impl ReadDoc) -> Policy {
let value = match store::read_plain(doc, &path()) {
Ok(Some(value)) => value,
Ok(None) => return Policy::Open,
Err(error) => return Policy::Unknown(format!("it doesn't read: {error}")),
};
let version = value.get("version").and_then(JsonValue::as_u64);
if version != Some(VERSION) {
return Policy::Unknown(format!(
"version {}, this node knows {VERSION}",
value
.get("version")
.map_or("none".to_owned(), JsonValue::to_string)
));
}
let strict = match value.get("values").and_then(JsonValue::as_str) {
None | Some("plain") => false,
Some("signed") => true,
Some(other) => return Policy::Unknown(format!("values {other:?}")),
};
match value.get("signatures").and_then(JsonValue::as_str) {
// Signed values: signed changes too.
None | Some("optional") => Policy::Enforced {
required: strict,
strict,
},
Some("required") => Policy::Enforced {
required: true,
strict,
},
Some(other) => Policy::Unknown(format!("signatures {other:?}")),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::document::Document;
use crate::document::testing::change;
use serde_json::json;
fn with(policy: JsonValue) -> Policy {
let mut doc = Document::new();
change(&mut doc, |doc| {
store::write_path(doc, &path(), &policy).unwrap();
});
of(&doc)
}
#[test]
fn what_the_policy_says() {
assert_eq!(of(&Document::new()), Policy::Open);
let optional = Policy::Enforced {
required: false,
strict: false,
};
assert_eq!(with(json!({"version": 1})), optional);
assert_eq!(
with(json!({"version": 1, "signatures": "optional"})),
optional
);
assert_eq!(
with(json!({"version": 1, "signatures": "required"})),
Policy::Enforced {
required: true,
strict: false
}
);
assert_eq!(
with(json!({"version": 1, "values": "signed"})),
Policy::Enforced {
required: true,
strict: true
}
);
assert!(with(json!({"version": 1, "values": "signed"})).strict());
assert!(!with(json!({"version": 1, "signatures": "required"})).strict());
assert!(matches!(
with(json!({"version": 1, "values": "sometimes"})),
Policy::Unknown(_)
));
assert!(matches!(with(json!({"version": 2})), Policy::Unknown(_)));
assert!(matches!(
with(json!({"version": 1, "signatures": "sometimes"})),
Policy::Unknown(_)
));
assert!(matches!(with(json!("on")), Policy::Unknown(_)));
}
}
|