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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
//! The Hierarchy model describes how Resources are structured in a tree-like shape.
//! It deals with authorization (read / write permissions, rights, grants)
//! See
use core::fmt;
use crate::{agents::ForAgent, errors::AtomicResult, storelike::Query, urls, Resource, Storelike};
#[derive(Debug)]
pub enum Right {
/// Full read access to the resource and its children.
/// [urls::READ]
Read,
/// Full edit, update, destroy access to the resource and its children.
/// [urls::WRITE]
Write,
/// Create new children (append to tree)
/// [urls::APPEND]
Append,
}
impl fmt::Display for Right {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
let str = match self {
Right::Read => urls::READ,
Right::Write => urls::WRITE,
Right::Append => urls::APPEND,
};
fmt.write_str(str)
}
}
/// Looks for children relations, adds to the resource. Performs a Query, might be expensive.
pub fn add_children(store: &impl Storelike, resource: &mut Resource) -> AtomicResult<Resource> {
let results = store.query(&Query::new_prop_val(urls::PARENT, resource.get_subject()))?;
let mut children = results.subjects;
children.sort();
resource.set(urls::CHILDREN.into(), children.into(), store)?;
Ok(resource.to_owned())
}
/// Throws if not allowed.
/// Returns string with explanation if allowed.
pub fn check_write(
store: &impl Storelike,
resource: &Resource,
for_agent: &ForAgent,
) -> AtomicResult<String> {
check_rights(store, resource, for_agent, Right::Write)
}
/// Does the Agent have the right to read / view the properties of the selected resource, or any of its parents?
/// Throws if not allowed.
/// Returns string with explanation if allowed.
pub fn check_read(
store: &impl Storelike,
resource: &Resource,
for_agent: &ForAgent,
) -> AtomicResult<String> {
check_rights(store, resource, for_agent, Right::Read)
}
/// Does the Agent have the right to _append_ to its parent?
/// This checks the `append` rights, and if that fails, checks the `write` right.
/// Throws if not allowed.
/// Returns string with explanation if allowed.
#[tracing::instrument(skip(store), level = "debug")]
pub fn check_append(
store: &impl Storelike,
resource: &Resource,
for_agent: &ForAgent,
) -> AtomicResult<String> {
match resource.get_parent(store) {
Ok(parent) => {
if let Ok(msg) = check_rights(store, &parent, for_agent, Right::Append) {
Ok(msg)
} else {
check_rights(store, resource, for_agent, Right::Write)
}
}
Err(e) => {
if resource
.get_classes(store)?
.iter()
.map(|c| c.subject.clone())
.collect::<String>()
.contains(urls::DRIVE)
{
Ok(String::from("Drive without a parent can be created"))
} else {
Err(e)
}
}
}
}
/// Recursively checks a Resource and its Parents for rights.
/// Throws if not allowed.
/// Returns string with explanation if allowed.
#[tracing::instrument(skip(store, resource))]
pub fn check_rights(
store: &impl Storelike,
resource: &Resource,
for_agent_enum: &ForAgent,
right: Right,
) -> AtomicResult<String> {
if for_agent_enum == &ForAgent::Sudo {
return Ok("Sudo has root access, and can edit anything.".into());
}
let for_agent = for_agent_enum.to_string();
if resource.get_subject() == &for_agent {
return Ok("Agents can always edit themselves or their children.".into());
}
if let Ok(server_agent) = store.get_default_agent() {
if server_agent.subject == for_agent {
return Ok("Server agent has root access, and can edit anything.".into());
}
}
// Handle Commits.
if let Ok(commit_subject) = resource.get(urls::SUBJECT) {
return match right {
Right::Read => {
// Commits can be read when their subject / target is readable.
let target = store.get_resource(&commit_subject.to_string())?;
check_rights(store, &target, for_agent_enum, right)
}
Right::Write => Err("Commits cannot be edited.".into()),
Right::Append => Err("Commits cannot have children, you cannot Append to them.".into()),
};
}
// Check if the resource's rights explicitly refers to the agent or the public agent
if let Ok(arr_val) = resource.get(&right.to_string()) {
for s in arr_val.to_subjects(None)? {
match s.as_str() {
urls::PUBLIC_AGENT => {
return Ok(format!(
"PublicAgent has been granted rights in {}",
resource.get_subject()
))
}
agent => {
if agent == for_agent {
return Ok(format!(
"Right has been explicitly set in {}",
resource.get_subject()
));
}
}
};
}
}
// Try the parents recursively
if let Ok(parent) = resource.get_parent(store) {
check_rights(store, &parent, for_agent_enum, right)
} else {
if for_agent_enum == &ForAgent::Public {
// resource has no parent and agent is not in rights array - check fails
let action = match right {
Right::Read => "readable",
Right::Write => "editable",
Right::Append => "appendable",
};
return Err(crate::errors::AtomicError::unauthorized(format!(
"This resource is not publicly {}. Try signing in",
action,
)));
}
// resource has no parent and agent is not in rights array - check fails
Err(crate::errors::AtomicError::unauthorized(format!(
"No {} right has been found for {} in this resource or its parents",
right, for_agent
)))
}
}
#[cfg(test)]
mod test {
// use super::*;
use crate::{datatype::DataType, Storelike, Value};
// TODO: Add tests for:
// - basic check_write (should be false for newly created agent)
// - Malicious Commit (which grants itself write rights)
#[test]
fn authorization() {
let store = crate::Store::init().unwrap();
store.populate().unwrap();
// let agent = store.create_agent(Some("test_actor")).unwrap();
let subject = "https://localhost/new_thing";
let mut commitbuilder_1 = crate::commit::CommitBuilder::new(subject.into());
let property = crate::urls::DESCRIPTION;
let value = Value::new("Some value", &DataType::Markdown).unwrap();
commitbuilder_1.set(property.into(), value);
// let mut commitbuilder_2 = commitbuilder_1.clone();
// let commit_1 = commitbuilder_1.sign(&agent, &store).unwrap();
// Should fail if there is no self_url set in the store, and no parent in the commit
// TODO: FINISH THIS
// commit_1.apply_opts(&store, true, true, true, true).unwrap_err();
// commitbuilder_2.set(crate::urls::PARENT.into(), Value::AtomicUrl(crate::urls::AGENT.into()));
// let commit_2 = commitbuilder_2.sign(&agent, &store).unwrap();
// let resource = store.get_resource(&subject).unwrap();
// assert!(resource.get(property).unwrap().to_string() == value.to_string());
}
#[test]
fn display_right() {
let read = super::Right::Read;
assert_eq!(read.to_string(), super::urls::READ);
let write = super::Right::Write;
assert_eq!(write.to_string(), super::urls::WRITE);
}
}