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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
|
//! Who may do what. The users and what each may do are the swarm's: a registry in the
//! document (`_continuum/users`, `registry`), each user with capabilities, each a path
//! and the rights it gives there and below (`Capability::covers`). How a user proves who
//! it is stays each node's (`--htpasswd`, `--user-header`: `api::auth`). Enforced at
//! the HTTP API ([`access.md`](../../docs/access.md)).
// Used by the genesis node and the API from the next patches on.
#![cfg_attr(not(test), allow(dead_code))]
use std::collections::BTreeSet;
use serde::{Deserialize, Serialize};
use crate::document::meta;
pub(crate) mod registry;
/// What a capability lets its holder do, at its path and below.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) enum Right {
Read,
Write,
}
impl Right {
pub(crate) fn as_str(self) -> &'static str {
match self {
Self::Read => "read",
Self::Write => "write",
}
}
}
/// A path, and what it lets its holder do there and below: as the registry records it
/// (`registry::User::caps`).
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub(crate) struct Capability {
/// Segments, as the store's API takes them (`[]`: everything).
pub(crate) path: Vec<String>,
/// `read`, `write`. Others, from a later version (`grant`), are kept and give
/// nothing here.
pub(crate) rights: BTreeSet<String>,
/// Who granted it: a user (`user:<name>`), or for those the genesis node records, the
/// node (its node ID). Kept so that granting on, by delegation, can come later
/// without a new format (`future-work.md`, Authentication across nodes).
#[serde(default)]
pub(crate) by: String,
/// When it was granted (Unix ms).
#[serde(default)]
pub(crate) since: u64,
}
impl Capability {
/// Whether it lets its holder do `right` at `path`: its path is a prefix of `path`
/// (segment by segment) and it gives `right`. One that isn't within protocol data
/// (`_continuum`) never covers it: operators are those given protocol data itself.
pub(crate) fn covers(&self, right: Right, path: &[String]) -> bool {
self.gives(right)
&& path.starts_with(&self.path)
&& (meta::is_protocol(&self.path) || !meta::is_protocol(path))
}
/// Whether `path` leads to a place where it lets its holder do `right`: its path
/// lies below `path` (or is it, or above it). What a listing of `path` shows of a
/// tree its holder may only partly read (`Caps::reaches`).
pub(crate) fn reaches(&self, right: Right, path: &[String]) -> bool {
self.gives(right)
&& (self.path.starts_with(path) || self.covers(right, path))
&& (meta::is_protocol(&self.path) || !meta::is_protocol(path))
}
fn gives(&self, right: Right) -> bool {
self.rights.contains(right.as_str())
}
}
/// Everything a user may do: its capabilities, as its registry entry has them; none if
/// it has none, or is disabled.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub(crate) struct Caps(Vec<Capability>);
impl Caps {
pub(crate) fn new(caps: Vec<Capability>) -> Self {
Self(caps)
}
/// Whether one of them lets its holder do `right` at `path`.
pub(crate) fn allows(&self, right: Right, path: &[String]) -> bool {
self.0.iter().any(|cap| cap.covers(right, path))
}
/// Whether `path` leads to a place where one of them lets its holder do `right`: a
/// listing of `path` shows the way there, as a directory one may pass through but not
/// list does (`Capability::reaches`).
pub(crate) fn reaches(&self, right: Right, path: &[String]) -> bool {
self.0.iter().any(|cap| cap.reaches(right, path))
}
pub(crate) fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn path(text: &str) -> Vec<String> {
text.split('/')
.filter(|segment| !segment.is_empty())
.map(str::to_owned)
.collect()
}
fn cap(at: &str, rights: &[&str]) -> Capability {
Capability {
path: path(at),
rights: rights.iter().map(|right| (*right).to_owned()).collect(),
by: String::new(),
since: 0,
}
}
#[test]
fn a_capability_covers_its_path_and_below() {
let servers = cap("servers/eu", &["read"]);
assert!(servers.covers(Right::Read, &path("servers/eu")));
assert!(servers.covers(Right::Read, &path("servers/eu/0/name")));
// By segment, not by text.
assert!(!servers.covers(Right::Read, &path("servers/europe")));
assert!(!servers.covers(Right::Read, &path("servers")));
assert!(!servers.covers(Right::Write, &path("servers/eu")));
}
#[test]
fn protocol_data_only_by_capabilities_within_it() {
let everything = cap("", &["read", "write"]);
assert!(everything.covers(Right::Write, &path("accounts/1")));
assert!(everything.covers(Right::Read, &[]));
assert!(!everything.covers(Right::Read, &path("_continuum")));
assert!(!everything.covers(Right::Write, &path("_continuum/users/alice")));
let operator = cap("_continuum", &["read", "write"]);
assert!(operator.covers(Right::Write, &path("_continuum/users/alice")));
assert!(!operator.covers(Right::Read, &path("accounts")));
let remakes = cap("_continuum/remakes", &["write"]);
assert!(remakes.covers(Right::Write, &path("_continuum/remakes/r1")));
assert!(!remakes.covers(Right::Write, &path("_continuum/users")));
}
#[test]
fn rights_from_later_versions_give_nothing() {
let later = cap("", &["grant"]);
assert!(!later.covers(Right::Read, &path("x")));
assert!(!later.covers(Right::Write, &path("x")));
}
#[test]
fn a_listing_shows_the_way_to_what_may_be_read() {
let caps = Caps::new(vec![cap("servers/eu", &["read"])]);
// The way there, as a directory passed through.
assert!(caps.reaches(Right::Read, &[]));
assert!(caps.reaches(Right::Read, &path("servers")));
// There and below.
assert!(caps.reaches(Right::Read, &path("servers/eu")));
assert!(caps.reaches(Right::Read, &path("servers/eu/0")));
// Elsewhere, nothing.
assert!(!caps.reaches(Right::Read, &path("servers/us")));
assert!(!caps.reaches(Right::Read, &path("accounts")));
assert!(!caps.allows(Right::Read, &path("servers")));
// A capability on everything doesn't lead into protocol data.
let everything = Caps::new(vec![cap("", &["read"])]);
assert!(everything.reaches(Right::Read, &[]));
assert!(!everything.reaches(Right::Read, &path("_continuum")));
let members = Caps::new(vec![cap("_continuum/members", &["read"])]);
assert!(members.reaches(Right::Read, &[]));
assert!(members.reaches(Right::Read, &path("_continuum")));
assert!(!members.reaches(Right::Read, &path("_continuum/users")));
}
}
|