aboutsummaryrefslogtreecommitdiffstats
path: root/src/access/policy.rs
blob: f8ccfc22b09171bd967adcf3ebbd19f0bd44701c (plain)
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
//! 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. 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 },
    /// 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 })
    }
}

/// The policy `doc` holds.
pub(crate) fn of(doc: &impl ReadDoc) -> Policy {
    let value = match store::read_path(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)
        ));
    }
    match value.get("signatures").and_then(JsonValue::as_str) {
        None | Some("optional") => Policy::Enforced { required: false },
        Some("required") => Policy::Enforced { required: true },
        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 };
        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 }
        );
        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(_)));
    }
}