Skip to content

Commit 86c1875

Browse files
committed
Refactor ForAgent #623
1 parent 74a5dd5 commit 86c1875

35 files changed

+344
-240
lines changed

cli/src/path.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::{
22
print::{get_serialization, print_resource},
33
Context,
44
};
5-
use atomic_lib::{errors::AtomicResult, serialize, storelike, Atom, Storelike};
5+
use atomic_lib::{agents::ForAgent, errors::AtomicResult, serialize, storelike, Atom, Storelike};
66
use serialize::Format;
77

88
/// Resolves an Atomic Path query
@@ -18,10 +18,14 @@ pub fn get_path(context: &mut Context) -> AtomicResult<()> {
1818

1919
// Returns a URL or Value
2020
let store = &mut context.store;
21-
let path = store.get_path(&path_string, Some(&context.mapping.lock().unwrap()), None)?;
21+
let path = store.get_path(
22+
&path_string,
23+
Some(&context.mapping.lock().unwrap()),
24+
&ForAgent::Sudo,
25+
)?;
2226
let out = match path {
2327
storelike::PathReturn::Subject(subject) => {
24-
let resource = store.get_resource_extended(&subject, false, None)?;
28+
let resource = store.get_resource_extended(&subject, false, &ForAgent::Sudo)?;
2529
print_resource(context, &resource, subcommand_matches)?;
2630
return Ok(());
2731
}

lib/src/agents.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,43 @@ use base64::{engine::general_purpose, Engine};
66

77
use crate::{errors::AtomicResult, urls, Resource, Storelike, Value};
88

9+
/// None represents no right checks will be performed, effectively SUDO mode.
10+
#[derive(Clone, Debug, PartialEq)]
11+
pub enum ForAgent {
12+
/// The Subject URL agent that is performing the action.
13+
AgentSubject(String),
14+
/// Allows all checks to pass.
15+
/// See [urls::SUDO_AGENT]
16+
Sudo,
17+
/// Public Agent, most strict.
18+
/// See [urls::PUBLIC_AGENT]
19+
Public,
20+
}
21+
22+
impl std::fmt::Display for ForAgent {
23+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24+
match self {
25+
ForAgent::AgentSubject(subject) => write!(f, "{}", subject),
26+
ForAgent::Sudo => write!(f, "{}", urls::SUDO_AGENT),
27+
ForAgent::Public => write!(f, "{}", urls::PUBLIC_AGENT),
28+
}
29+
}
30+
}
31+
32+
// From all string-likes
33+
impl<T: Into<String>> From<T> for ForAgent {
34+
fn from(subject: T) -> Self {
35+
let subject = subject.into();
36+
if subject == urls::SUDO_AGENT {
37+
ForAgent::Sudo
38+
} else if subject == urls::PUBLIC_AGENT {
39+
ForAgent::Public
40+
} else {
41+
ForAgent::AgentSubject(subject)
42+
}
43+
}
44+
}
45+
946
#[derive(Clone, Debug)]
1047
pub struct Agent {
1148
/// Private key for signing commits

lib/src/authentication.rs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
//! Check signatures in authentication headers, find the correct agent. Authorization is done in Hierarchies
22
33
use crate::{
4-
agents::decode_base64, commit::check_timestamp, errors::AtomicResult, urls, Storelike,
4+
agents::{decode_base64, ForAgent},
5+
commit::check_timestamp,
6+
errors::AtomicResult,
7+
urls, Storelike,
58
};
69

710
/// Set of values extracted from the request.
@@ -51,8 +54,7 @@ pub fn check_auth_signature(subject: &str, auth_header: &AuthValues) -> AtomicRe
5154
pub fn get_agent_from_auth_values_and_check(
5255
auth_header_values: Option<AuthValues>,
5356
store: &impl Storelike,
54-
) -> AtomicResult<String> {
55-
let mut for_agent = crate::urls::PUBLIC_AGENT.to_string();
57+
) -> AtomicResult<ForAgent> {
5658
if let Some(auth_vals) = auth_header_values {
5759
// If there are auth headers, check 'em, make sure they are valid.
5860
check_auth_signature(&auth_vals.requested_subject, &auth_vals)
@@ -62,16 +64,17 @@ pub fn get_agent_from_auth_values_and_check(
6264
// check if the public key belongs to the agent
6365
let found_public_key = store.get_value(&auth_vals.agent_subject, urls::PUBLIC_KEY)?;
6466
if found_public_key.to_string() != auth_vals.public_key {
65-
return Err(
67+
Err(
6668
"The public key in the auth headers does not match the public key in the agent"
6769
.to_string()
6870
.into(),
69-
);
71+
)
7072
} else {
71-
for_agent = auth_vals.agent_subject;
73+
Ok(ForAgent::AgentSubject(auth_vals.agent_subject))
7274
}
73-
};
74-
Ok(for_agent)
75+
} else {
76+
Ok(ForAgent::Public)
77+
}
7578
}
7679

7780
// fn get_agent_from_value_index() {

lib/src/collections.rs

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Collections are dynamic resources that refer to multiple resources.
22
//! They are constructed using a [Query]
33
use crate::{
4+
agents::ForAgent,
45
errors::AtomicResult,
56
storelike::{Query, ResourceCollection},
67
urls, Resource, Storelike, Value,
@@ -108,7 +109,7 @@ impl CollectionBuilder {
108109
pub fn into_collection(
109110
self,
110111
store: &impl Storelike,
111-
for_agent: Option<&str>,
112+
for_agent: &ForAgent,
112113
) -> AtomicResult<Collection> {
113114
Collection::collect_members(store, self, for_agent)
114115
}
@@ -185,7 +186,7 @@ impl Collection {
185186
pub fn collect_members(
186187
store: &impl Storelike,
187188
collection_builder: crate::collections::CollectionBuilder,
188-
for_agent: Option<&str>,
189+
for_agent: &ForAgent,
189190
) -> AtomicResult<Collection> {
190191
if collection_builder.page_size < 1 {
191192
return Err("Page size must be greater than 0".into());
@@ -210,7 +211,7 @@ impl Collection {
210211
sort_desc: collection_builder.sort_desc,
211212
include_external: collection_builder.include_external,
212213
include_nested: collection_builder.include_nested,
213-
for_agent: for_agent.map(|a| a.to_string()),
214+
for_agent: for_agent.clone(),
214215
};
215216

216217
let query_result = store.query(&q)?;
@@ -325,7 +326,7 @@ pub fn construct_collection_from_params(
325326
store: &impl Storelike,
326327
query_params: url::form_urlencoded::Parse,
327328
resource: &mut Resource,
328-
for_agent: Option<&str>,
329+
for_agent: &ForAgent,
329330
) -> AtomicResult<Resource> {
330331
let mut sort_by = None;
331332
let mut sort_desc = false;
@@ -458,7 +459,8 @@ mod test {
458459
include_nested: false,
459460
include_external: false,
460461
};
461-
let collection = Collection::collect_members(&store, collection_builder, None).unwrap();
462+
let collection =
463+
Collection::collect_members(&store, collection_builder, &ForAgent::Sudo).unwrap();
462464
assert!(collection.members.contains(&urls::PROPERTY.into()));
463465
}
464466

@@ -478,7 +480,8 @@ mod test {
478480
include_nested: false,
479481
include_external: false,
480482
};
481-
let collection = Collection::collect_members(&store, collection_builder, None).unwrap();
483+
let collection =
484+
Collection::collect_members(&store, collection_builder, &ForAgent::Sudo).unwrap();
482485
assert!(collection.members.contains(&urls::PROPERTY.into()));
483486

484487
let resource_collection = &collection.to_resource(&store).unwrap();
@@ -504,7 +507,8 @@ mod test {
504507
include_nested: true,
505508
include_external: false,
506509
};
507-
let collection = Collection::collect_members(&store, collection_builder, None).unwrap();
510+
let collection =
511+
Collection::collect_members(&store, collection_builder, &ForAgent::Sudo).unwrap();
508512
let first_resource = &collection.members_nested.clone().unwrap()[0];
509513
assert!(first_resource.get_subject().contains("Agent"));
510514

@@ -531,7 +535,7 @@ mod test {
531535
.get_resource_extended(
532536
&format!("{}/collections", store.get_server_url()),
533537
false,
534-
None,
538+
&ForAgent::Public,
535539
)
536540
.unwrap();
537541
assert!(
@@ -559,7 +563,11 @@ mod test {
559563
store.populate().unwrap();
560564

561565
let collection_page_size = store
562-
.get_resource_extended("https://atomicdata.dev/classes?page_size=1", false, None)
566+
.get_resource_extended(
567+
"https://atomicdata.dev/classes?page_size=1",
568+
false,
569+
&ForAgent::Public,
570+
)
563571
.unwrap();
564572
assert!(
565573
collection_page_size
@@ -572,7 +580,7 @@ mod test {
572580
.get_resource_extended(
573581
"https://atomicdata.dev/classes?current_page=2&page_size=1",
574582
false,
575-
None,
583+
&ForAgent::Public,
576584
)
577585
.unwrap();
578586
assert!(

lib/src/commit.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ impl Commit {
169169
if opts.validate_rights {
170170
let validate_for = opts.validate_for_agent.as_ref().unwrap_or(&self.signer);
171171
if is_new {
172-
hierarchy::check_append(store, &resource_new, validate_for)?;
172+
hierarchy::check_append(store, &resource_new, &validate_for.into())?;
173173
} else {
174174
// Set a parent only if the rights checks are to be validated.
175175
// If there is no explicit parent set on the previous resource, use a default.
@@ -183,7 +183,7 @@ impl Commit {
183183
)?;
184184
}
185185
// This should use the _old_ resource, no the new one, as the new one might maliciously give itself write rights.
186-
hierarchy::check_write(store, &resource_old, validate_for)?;
186+
hierarchy::check_write(store, &resource_old, &validate_for.into())?;
187187
}
188188
};
189189
// Check if all required props are there

lib/src/db.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use std::{
1616
use tracing::{info, instrument};
1717

1818
use crate::{
19+
agents::ForAgent,
1920
atoms::IndexAtom,
2021
commit::CommitResponse,
2122
db::{query_index::NO_VALUE, val_prop_sub_index::find_in_val_prop_sub_index},
@@ -342,7 +343,7 @@ impl Storelike for Db {
342343
&self,
343344
subject: &str,
344345
skip_dynamic: bool,
345-
for_agent: Option<&str>,
346+
for_agent: &ForAgent,
346347
) -> AtomicResult<Resource> {
347348
let url_span = tracing::span!(tracing::Level::TRACE, "URL parse").entered();
348349
// This might add a trailing slash
@@ -390,9 +391,7 @@ impl Storelike for Db {
390391
let dynamic_span = tracing::span!(tracing::Level::TRACE, "Dynamic").entered();
391392
let mut resource = self.get_resource(&removed_query_params)?;
392393

393-
if let Some(agent) = for_agent {
394-
let _explanation = crate::hierarchy::check_read(self, &resource, agent)?;
395-
}
394+
let _explanation = crate::hierarchy::check_read(self, &resource, for_agent)?;
396395

397396
// Whether the resource has dynamic properties
398397
let mut has_dynamic = false;
@@ -534,7 +533,7 @@ impl Storelike for Db {
534533
&self,
535534
subject: &str,
536535
body: Vec<u8>,
537-
for_agent: Option<&str>,
536+
for_agent: &ForAgent,
538537
) -> AtomicResult<Resource> {
539538
let endpoints = self.endpoints.iter().filter(|e| e.handle_post.is_some());
540539
let subj_url = url::Url::try_from(subject)?;

lib/src/db/query_index.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
//! It relies on lexicographic ordering of keys, which Sled utilizes using `scan_prefix` queries.
33
44
use crate::{
5+
agents::ForAgent,
56
atoms::IndexAtom,
67
errors::AtomicResult,
78
storelike::{Query, QueryResult},
@@ -125,8 +126,8 @@ pub fn query_indexed(store: &Db, q: &Query) -> AtomicResult<QueryResult> {
125126
// When an agent is defined, we must perform authorization checks
126127
// WARNING: EXPENSIVE!
127128
// TODO: Make async
128-
if q.include_nested || q.for_agent.is_some() {
129-
match store.get_resource_extended(subject, true, q.for_agent.as_deref()) {
129+
if q.include_nested || q.for_agent != ForAgent::Sudo {
130+
match store.get_resource_extended(subject, true, &q.for_agent) {
130131
Ok(resource) => {
131132
resources.push(resource);
132133
subjects.push(subject.into())

0 commit comments

Comments
 (0)