Skip to content

Commit e5cca39

Browse files
committed
#11 refactor error model
1 parent c97cde6 commit e5cca39

19 files changed

+336
-110
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
List of changes for this repo, including `atomic-cli`, `atomic-server` and `atomic-lib`.
44
By far most changes relate to `atomic-server`, so if not specified, assume the changes are relevant only for the server.
55

6+
## v0.29.0
7+
8+
- Add authentication to restrict read access. Works by signing requests with Private Keys. #13
9+
- Refactor internal error model, Use correct HTTP status codes #11
10+
611
## v0.28.2
712

813
- Full-text search endpoint, powered by Tantify #40

lib/src/authentication.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ pub struct AuthValues {
1414
pub agent_subject: String,
1515
}
1616

17-
pub const PUBLIC_AGENT: &str = "https://atomicdata.dev/agents/publicAgent";
18-
1917
/// Checks if the signature is valid for this timestamp.
2018
/// Does not check if the agent has rights to access the subject.
2119
pub fn check_auth_signature(subject: &str, auth_header: &AuthValues) -> AtomicResult<()> {
@@ -42,7 +40,7 @@ pub fn get_agent_from_headers_and_check(
4240
auth_header_values: Option<AuthValues>,
4341
store: &impl Storelike,
4442
) -> AtomicResult<String> {
45-
let mut for_agent = PUBLIC_AGENT.to_string();
43+
let mut for_agent = crate::urls::PUBLIC_AGENT.to_string();
4644
if let Some(auth_vals) = auth_header_values {
4745
// If there are auth headers, check 'em, make sure they are valid.
4846
check_auth_signature(&auth_vals.requested_subject, &auth_vals)?;

lib/src/db.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::{
88

99
use crate::{
1010
datatype::DataType,
11-
errors::AtomicResult,
11+
errors::{AtomicError, AtomicResult},
1212
resources::PropVals,
1313
storelike::{ResourceCollection, Storelike},
1414
Atom, Resource, Value,
@@ -314,9 +314,10 @@ impl Storelike for Db {
314314

315315
if let Some(agent) = for_agent {
316316
if !crate::hierarchy::check_read(self, &resource, agent.to_string())? {
317-
return Err(
318-
format!("Agent '{}' is not allowed to read '{}'.", agent, subject).into(),
319-
);
317+
return Err(AtomicError::unauthorized(format!(
318+
"Agent '{}' is not authorized to read '{}'. There should be a `read` right in this resource or one of its parents.",
319+
agent, subject
320+
)));
320321
}
321322
}
322323

lib/src/errors.rs

Lines changed: 211 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,221 @@
11
//! The Error type that you can expect when using this library
22
3-
use std::error::Error;
4-
use std::fmt;
3+
use std::{
4+
convert::Infallible,
5+
num::{ParseFloatError, ParseIntError},
6+
str::ParseBoolError,
7+
};
8+
9+
use base64::DecodeError;
510

611
/// The default Error type for all Atomic Lib Errors.
7-
// TODO: specify & limit error types
8-
// https://github.com/joepio/atomic/issues/11
9-
pub type AtomicResult<T> = std::result::Result<T, Box<dyn std::error::Error>>;
12+
pub type AtomicResult<T> = std::result::Result<T, AtomicError>;
13+
14+
#[derive(Debug)]
15+
pub struct AtomicError {
16+
pub message: String,
17+
pub error_type: AtomicErrorType,
18+
}
1019

1120
#[derive(Debug)]
12-
struct AtomicError(String);
21+
pub enum AtomicErrorType {
22+
NotFoundError,
23+
UnauthorizedError,
24+
OtherError,
25+
}
26+
27+
impl std::error::Error for AtomicError {
28+
// fn description(&self) -> &str {
29+
// // Both underlying errors already impl `Error`, so we defer to their
30+
// // implementations.
31+
// match *self {
32+
// CliError::Io(ref err) => err.description(),
33+
// // Normally we can just write `err.description()`, but the error
34+
// // type has a concrete method called `description`, which conflicts
35+
// // with the trait method. For now, we must explicitly call
36+
// // `description` through the `Error` trait.
37+
// CliError::Parse(ref err) => error::Error::description(err),
38+
// }
39+
// }
40+
41+
// fn cause(&self) -> Option<&dyn std::error::Error> {
42+
// match *self {
43+
// // N.B. Both of these implicitly cast `err` from their concrete
44+
// // types (either `&io::Error` or `&num::ParseIntError`)
45+
// // to a trait object `&Error`. This works because both error types
46+
// // implement `Error`.
47+
// CliError::Io(ref err) => Some(err),
48+
// CliError::Parse(ref err) => Some(err),
49+
// }
50+
// }
51+
}
52+
53+
impl AtomicError {
54+
#[allow(dead_code)]
55+
pub fn not_found(message: String) -> AtomicError {
56+
AtomicError {
57+
message: format!("Resource not found. {}", message),
58+
error_type: AtomicErrorType::NotFoundError,
59+
}
60+
}
61+
62+
pub fn unauthorized(message: String) -> AtomicError {
63+
AtomicError {
64+
message: format!("Unauthorized. {}", message),
65+
error_type: AtomicErrorType::UnauthorizedError,
66+
}
67+
}
68+
69+
pub fn other_error(message: String) -> AtomicError {
70+
AtomicError {
71+
message,
72+
error_type: AtomicErrorType::OtherError,
73+
}
74+
}
75+
}
76+
77+
impl std::fmt::Display for AtomicError {
78+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79+
write!(f, "{}", &self.message)
80+
}
81+
}
82+
83+
// Error conversions
84+
impl From<&str> for AtomicError {
85+
fn from(message: &str) -> Self {
86+
AtomicError {
87+
message: message.into(),
88+
error_type: AtomicErrorType::OtherError,
89+
}
90+
}
91+
}
92+
93+
impl From<String> for AtomicError {
94+
fn from(message: String) -> Self {
95+
AtomicError {
96+
message,
97+
error_type: AtomicErrorType::OtherError,
98+
}
99+
}
100+
}
101+
102+
// The following feel very redundant. Can this be simplified?
103+
104+
impl From<std::boxed::Box<dyn std::error::Error>> for AtomicError {
105+
fn from(error: std::boxed::Box<dyn std::error::Error>) -> Self {
106+
AtomicError {
107+
message: error.to_string(),
108+
error_type: AtomicErrorType::OtherError,
109+
}
110+
}
111+
}
112+
113+
impl<T> From<std::sync::PoisonError<T>> for AtomicError {
114+
fn from(error: std::sync::PoisonError<T>) -> Self {
115+
AtomicError {
116+
message: error.to_string(),
117+
error_type: AtomicErrorType::OtherError,
118+
}
119+
}
120+
}
121+
122+
impl From<std::io::Error> for AtomicError {
123+
fn from(error: std::io::Error) -> Self {
124+
AtomicError {
125+
message: error.to_string(),
126+
error_type: AtomicErrorType::OtherError,
127+
}
128+
}
129+
}
130+
131+
impl From<url::ParseError> for AtomicError {
132+
fn from(error: url::ParseError) -> Self {
133+
AtomicError {
134+
message: error.to_string(),
135+
error_type: AtomicErrorType::OtherError,
136+
}
137+
}
138+
}
139+
140+
impl From<serde_json::Error> for AtomicError {
141+
fn from(error: serde_json::Error) -> Self {
142+
AtomicError {
143+
message: error.to_string(),
144+
error_type: AtomicErrorType::OtherError,
145+
}
146+
}
147+
}
148+
149+
impl From<std::string::FromUtf8Error> for AtomicError {
150+
fn from(error: std::string::FromUtf8Error) -> Self {
151+
AtomicError {
152+
message: error.to_string(),
153+
error_type: AtomicErrorType::OtherError,
154+
}
155+
}
156+
}
157+
158+
impl From<ParseFloatError> for AtomicError {
159+
fn from(error: ParseFloatError) -> Self {
160+
AtomicError {
161+
message: error.to_string(),
162+
error_type: AtomicErrorType::OtherError,
163+
}
164+
}
165+
}
166+
167+
impl From<ParseIntError> for AtomicError {
168+
fn from(error: ParseIntError) -> Self {
169+
AtomicError {
170+
message: error.to_string(),
171+
error_type: AtomicErrorType::OtherError,
172+
}
173+
}
174+
}
13175

14-
impl fmt::Display for AtomicError {
15-
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
16-
write!(f, "There is an error: {}", self.0)
176+
impl From<DecodeError> for AtomicError {
177+
fn from(error: DecodeError) -> Self {
178+
AtomicError {
179+
message: error.to_string(),
180+
error_type: AtomicErrorType::OtherError,
181+
}
17182
}
18183
}
19184

20-
impl Error for AtomicError {}
185+
impl From<ParseBoolError> for AtomicError {
186+
fn from(error: ParseBoolError) -> Self {
187+
AtomicError {
188+
message: error.to_string(),
189+
error_type: AtomicErrorType::OtherError,
190+
}
191+
}
192+
}
193+
194+
impl From<Infallible> for AtomicError {
195+
fn from(error: Infallible) -> Self {
196+
AtomicError {
197+
message: error.to_string(),
198+
error_type: AtomicErrorType::OtherError,
199+
}
200+
}
201+
}
202+
203+
#[cfg(feature = "db")]
204+
impl From<sled::Error> for AtomicError {
205+
fn from(error: sled::Error) -> Self {
206+
AtomicError {
207+
message: error.to_string(),
208+
error_type: AtomicErrorType::OtherError,
209+
}
210+
}
211+
}
212+
213+
#[cfg(feature = "db")]
214+
impl From<Box<bincode::ErrorKind>> for AtomicError {
215+
fn from(error: Box<bincode::ErrorKind>) -> Self {
216+
AtomicError {
217+
message: error.to_string(),
218+
error_type: AtomicErrorType::OtherError,
219+
}
220+
}
221+
}

lib/src/storelike.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
use crate::{
44
agents::Agent,
5+
errors::AtomicError,
56
hierarchy,
67
schema::{Class, Property},
78
};
@@ -173,16 +174,12 @@ pub trait Storelike: Sized {
173174
if hierarchy::check_read(self, &resource, agent)? {
174175
return Ok(resource);
175176
}
176-
return Err("No rights".into());
177+
return Err(AtomicError::unauthorized("No rights".into()));
177178
}
178179
Ok(resource)
179180
}
180181

181-
fn handle_not_found(
182-
&self,
183-
subject: &str,
184-
error: Box<dyn std::error::Error>,
185-
) -> AtomicResult<Resource> {
182+
fn handle_not_found(&self, subject: &str, error: AtomicError) -> AtomicResult<Resource> {
186183
if let Some(self_url) = self.get_self_url() {
187184
if subject.starts_with(&self_url) {
188185
return Err(format!("Failed to retrieve locally: '{}'. {}", subject, error).into());

lib/src/urls.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,6 @@ pub const TIMESTAMP: &str = "https://atomicdata.dev/datatypes/timestamp";
9696
// Methods
9797
pub const INSERT: &str = "https://atomicdata.dev/methods/insert";
9898
pub const DELETE: &str = "https://atomicdata.dev/methods/delete";
99+
100+
// Instances
101+
pub const PUBLIC_AGENT: &str = "https://atomicdata.dev/agents/publicAgent";

server/src/appstate.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! App state, which is accessible from handlers
22
use crate::{
3-
commit_monitor::CommitMonitor, config::Config, errors::BetterResult, search::SearchState,
3+
commit_monitor::CommitMonitor, config::Config, errors::AtomicServerResult, search::SearchState,
44
};
55
use atomic_lib::{
66
agents::{generate_public_key, Agent},
@@ -24,7 +24,7 @@ pub struct AppState {
2424
/// Creates the server context.
2525
/// Initializes a store on disk.
2626
/// Creates a new agent, if neccessary.
27-
pub fn init(config: Config) -> BetterResult<AppState> {
27+
pub fn init(config: Config) -> AtomicServerResult<AppState> {
2828
// Enable logging, but hide most tantivy logs
2929
std::env::set_var("RUST_LOG", "info,tantivy=warn");
3030
env_logger::init();
@@ -84,7 +84,7 @@ pub fn init(config: Config) -> BetterResult<AppState> {
8484
}
8585

8686
/// Create a new agent if it does not yet exist.
87-
fn set_default_agent(config: &Config, store: &impl Storelike) -> BetterResult<()> {
87+
fn set_default_agent(config: &Config, store: &impl Storelike) -> AtomicServerResult<()> {
8888
let ag_cfg: atomic_lib::config::Config = match atomic_lib::config::read_config(
8989
&config.config_file_path,
9090
) {
@@ -142,7 +142,7 @@ fn set_default_agent(config: &Config, store: &impl Storelike) -> BetterResult<()
142142
}
143143

144144
/// Creates the first Invitation that is opened by the user on the Home page.
145-
fn set_up_initial_invite(store: &impl Storelike) -> BetterResult<()> {
145+
fn set_up_initial_invite(store: &impl Storelike) -> AtomicServerResult<()> {
146146
let subject = format!("{}/setup", store.get_base_url());
147147
log::info!("Creating initial Invite at {}", subject);
148148
let mut invite = atomic_lib::Resource::new_instance(atomic_lib::urls::INVITE, store)?;

server/src/config.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Parse CLI options, setup on boot, read .env values
22
3-
use crate::errors::BetterResult;
3+
use crate::errors::AtomicServerResult;
44
use clap::Parser;
55
use dotenv::dotenv;
66
use std::env;
@@ -127,7 +127,7 @@ pub struct Config {
127127
}
128128

129129
/// Creates the server config, reads .env values and sets defaults
130-
pub fn init() -> BetterResult<Config> {
130+
pub fn init() -> AtomicServerResult<Config> {
131131
// Parse .env file (do this before parsing the CLI opts)
132132
dotenv().ok();
133133

0 commit comments

Comments
 (0)