Skip to content

Commit

Permalink
Refactor ForAgent #623
Browse files Browse the repository at this point in the history
  • Loading branch information
joepio committed Mar 22, 2023
1 parent 74a5dd5 commit 86c1875
Show file tree
Hide file tree
Showing 35 changed files with 344 additions and 240 deletions.
10 changes: 7 additions & 3 deletions cli/src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::{
print::{get_serialization, print_resource},
Context,
};
use atomic_lib::{errors::AtomicResult, serialize, storelike, Atom, Storelike};
use atomic_lib::{agents::ForAgent, errors::AtomicResult, serialize, storelike, Atom, Storelike};
use serialize::Format;

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

// Returns a URL or Value
let store = &mut context.store;
let path = store.get_path(&path_string, Some(&context.mapping.lock().unwrap()), None)?;
let path = store.get_path(
&path_string,
Some(&context.mapping.lock().unwrap()),
&ForAgent::Sudo,
)?;
let out = match path {
storelike::PathReturn::Subject(subject) => {
let resource = store.get_resource_extended(&subject, false, None)?;
let resource = store.get_resource_extended(&subject, false, &ForAgent::Sudo)?;
print_resource(context, &resource, subcommand_matches)?;
return Ok(());
}
Expand Down
37 changes: 37 additions & 0 deletions lib/src/agents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,43 @@ use base64::{engine::general_purpose, Engine};

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

/// None represents no right checks will be performed, effectively SUDO mode.
#[derive(Clone, Debug, PartialEq)]
pub enum ForAgent {
/// The Subject URL agent that is performing the action.
AgentSubject(String),
/// Allows all checks to pass.
/// See [urls::SUDO_AGENT]
Sudo,
/// Public Agent, most strict.
/// See [urls::PUBLIC_AGENT]
Public,
}

impl std::fmt::Display for ForAgent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ForAgent::AgentSubject(subject) => write!(f, "{}", subject),
ForAgent::Sudo => write!(f, "{}", urls::SUDO_AGENT),
ForAgent::Public => write!(f, "{}", urls::PUBLIC_AGENT),
}
}
}

// From all string-likes
impl<T: Into<String>> From<T> for ForAgent {
fn from(subject: T) -> Self {
let subject = subject.into();
if subject == urls::SUDO_AGENT {
ForAgent::Sudo
} else if subject == urls::PUBLIC_AGENT {
ForAgent::Public
} else {
ForAgent::AgentSubject(subject)
}
}
}

#[derive(Clone, Debug)]
pub struct Agent {
/// Private key for signing commits
Expand Down
19 changes: 11 additions & 8 deletions lib/src/authentication.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
//! Check signatures in authentication headers, find the correct agent. Authorization is done in Hierarchies

use crate::{
agents::decode_base64, commit::check_timestamp, errors::AtomicResult, urls, Storelike,
agents::{decode_base64, ForAgent},
commit::check_timestamp,
errors::AtomicResult,
urls, Storelike,
};

/// Set of values extracted from the request.
Expand Down Expand Up @@ -51,8 +54,7 @@ pub fn check_auth_signature(subject: &str, auth_header: &AuthValues) -> AtomicRe
pub fn get_agent_from_auth_values_and_check(
auth_header_values: Option<AuthValues>,
store: &impl Storelike,
) -> AtomicResult<String> {
let mut for_agent = crate::urls::PUBLIC_AGENT.to_string();
) -> AtomicResult<ForAgent> {
if let Some(auth_vals) = auth_header_values {
// If there are auth headers, check 'em, make sure they are valid.
check_auth_signature(&auth_vals.requested_subject, &auth_vals)
Expand All @@ -62,16 +64,17 @@ pub fn get_agent_from_auth_values_and_check(
// check if the public key belongs to the agent
let found_public_key = store.get_value(&auth_vals.agent_subject, urls::PUBLIC_KEY)?;
if found_public_key.to_string() != auth_vals.public_key {
return Err(
Err(
"The public key in the auth headers does not match the public key in the agent"
.to_string()
.into(),
);
)
} else {
for_agent = auth_vals.agent_subject;
Ok(ForAgent::AgentSubject(auth_vals.agent_subject))
}
};
Ok(for_agent)
} else {
Ok(ForAgent::Public)
}
}

// fn get_agent_from_value_index() {
Expand Down
28 changes: 18 additions & 10 deletions lib/src/collections.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Collections are dynamic resources that refer to multiple resources.
//! They are constructed using a [Query]
use crate::{
agents::ForAgent,
errors::AtomicResult,
storelike::{Query, ResourceCollection},
urls, Resource, Storelike, Value,
Expand Down Expand Up @@ -108,7 +109,7 @@ impl CollectionBuilder {
pub fn into_collection(
self,
store: &impl Storelike,
for_agent: Option<&str>,
for_agent: &ForAgent,
) -> AtomicResult<Collection> {
Collection::collect_members(store, self, for_agent)
}
Expand Down Expand Up @@ -185,7 +186,7 @@ impl Collection {
pub fn collect_members(
store: &impl Storelike,
collection_builder: crate::collections::CollectionBuilder,
for_agent: Option<&str>,
for_agent: &ForAgent,
) -> AtomicResult<Collection> {
if collection_builder.page_size < 1 {
return Err("Page size must be greater than 0".into());
Expand All @@ -210,7 +211,7 @@ impl Collection {
sort_desc: collection_builder.sort_desc,
include_external: collection_builder.include_external,
include_nested: collection_builder.include_nested,
for_agent: for_agent.map(|a| a.to_string()),
for_agent: for_agent.clone(),
};

let query_result = store.query(&q)?;
Expand Down Expand Up @@ -325,7 +326,7 @@ pub fn construct_collection_from_params(
store: &impl Storelike,
query_params: url::form_urlencoded::Parse,
resource: &mut Resource,
for_agent: Option<&str>,
for_agent: &ForAgent,
) -> AtomicResult<Resource> {
let mut sort_by = None;
let mut sort_desc = false;
Expand Down Expand Up @@ -458,7 +459,8 @@ mod test {
include_nested: false,
include_external: false,
};
let collection = Collection::collect_members(&store, collection_builder, None).unwrap();
let collection =
Collection::collect_members(&store, collection_builder, &ForAgent::Sudo).unwrap();
assert!(collection.members.contains(&urls::PROPERTY.into()));
}

Expand All @@ -478,7 +480,8 @@ mod test {
include_nested: false,
include_external: false,
};
let collection = Collection::collect_members(&store, collection_builder, None).unwrap();
let collection =
Collection::collect_members(&store, collection_builder, &ForAgent::Sudo).unwrap();
assert!(collection.members.contains(&urls::PROPERTY.into()));

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

Expand All @@ -531,7 +535,7 @@ mod test {
.get_resource_extended(
&format!("{}/collections", store.get_server_url()),
false,
None,
&ForAgent::Public,
)
.unwrap();
assert!(
Expand Down Expand Up @@ -559,7 +563,11 @@ mod test {
store.populate().unwrap();

let collection_page_size = store
.get_resource_extended("https://atomicdata.dev/classes?page_size=1", false, None)
.get_resource_extended(
"https://atomicdata.dev/classes?page_size=1",
false,
&ForAgent::Public,
)
.unwrap();
assert!(
collection_page_size
Expand All @@ -572,7 +580,7 @@ mod test {
.get_resource_extended(
"https://atomicdata.dev/classes?current_page=2&page_size=1",
false,
None,
&ForAgent::Public,
)
.unwrap();
assert!(
Expand Down
4 changes: 2 additions & 2 deletions lib/src/commit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ impl Commit {
if opts.validate_rights {
let validate_for = opts.validate_for_agent.as_ref().unwrap_or(&self.signer);
if is_new {
hierarchy::check_append(store, &resource_new, validate_for)?;
hierarchy::check_append(store, &resource_new, &validate_for.into())?;
} else {
// Set a parent only if the rights checks are to be validated.
// If there is no explicit parent set on the previous resource, use a default.
Expand All @@ -183,7 +183,7 @@ impl Commit {
)?;
}
// This should use the _old_ resource, no the new one, as the new one might maliciously give itself write rights.
hierarchy::check_write(store, &resource_old, validate_for)?;
hierarchy::check_write(store, &resource_old, &validate_for.into())?;
}
};
// Check if all required props are there
Expand Down
9 changes: 4 additions & 5 deletions lib/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use std::{
use tracing::{info, instrument};

use crate::{
agents::ForAgent,
atoms::IndexAtom,
commit::CommitResponse,
db::{query_index::NO_VALUE, val_prop_sub_index::find_in_val_prop_sub_index},
Expand Down Expand Up @@ -342,7 +343,7 @@ impl Storelike for Db {
&self,
subject: &str,
skip_dynamic: bool,
for_agent: Option<&str>,
for_agent: &ForAgent,
) -> AtomicResult<Resource> {
let url_span = tracing::span!(tracing::Level::TRACE, "URL parse").entered();
// This might add a trailing slash
Expand Down Expand Up @@ -390,9 +391,7 @@ impl Storelike for Db {
let dynamic_span = tracing::span!(tracing::Level::TRACE, "Dynamic").entered();
let mut resource = self.get_resource(&removed_query_params)?;

if let Some(agent) = for_agent {
let _explanation = crate::hierarchy::check_read(self, &resource, agent)?;
}
let _explanation = crate::hierarchy::check_read(self, &resource, for_agent)?;

// Whether the resource has dynamic properties
let mut has_dynamic = false;
Expand Down Expand Up @@ -534,7 +533,7 @@ impl Storelike for Db {
&self,
subject: &str,
body: Vec<u8>,
for_agent: Option<&str>,
for_agent: &ForAgent,
) -> AtomicResult<Resource> {
let endpoints = self.endpoints.iter().filter(|e| e.handle_post.is_some());
let subj_url = url::Url::try_from(subject)?;
Expand Down
5 changes: 3 additions & 2 deletions lib/src/db/query_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
//! It relies on lexicographic ordering of keys, which Sled utilizes using `scan_prefix` queries.

use crate::{
agents::ForAgent,
atoms::IndexAtom,
errors::AtomicResult,
storelike::{Query, QueryResult},
Expand Down Expand Up @@ -125,8 +126,8 @@ pub fn query_indexed(store: &Db, q: &Query) -> AtomicResult<QueryResult> {
// When an agent is defined, we must perform authorization checks
// WARNING: EXPENSIVE!
// TODO: Make async
if q.include_nested || q.for_agent.is_some() {
match store.get_resource_extended(subject, true, q.for_agent.as_deref()) {
if q.include_nested || q.for_agent != ForAgent::Sudo {
match store.get_resource_extended(subject, true, &q.for_agent) {
Ok(resource) => {
resources.push(resource);
subjects.push(subject.into())
Expand Down
Loading

0 comments on commit 86c1875

Please sign in to comment.