From f087705205ff9486cef81f4db233380160e0091b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Bardon?= Date: Sat, 25 May 2024 17:28:50 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20`=5Ferrors`=20admin=20route?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Cargo.lock | 146 +++++++++++++++++++++++++++++++ src/orangutan-server/Cargo.toml | 1 + src/orangutan-server/src/main.rs | 92 +++++++++++++------ 3 files changed, 211 insertions(+), 28 deletions(-) diff --git a/src/Cargo.lock b/src/Cargo.lock index 7287115..7a6f14a 100644 --- a/src/Cargo.lock +++ b/src/Cargo.lock @@ -26,6 +26,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anyhow" version = "1.0.81" @@ -206,6 +221,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + [[package]] name = "bytemuck" version = "1.15.0" @@ -230,6 +251,20 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets 0.52.4", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -247,6 +282,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + [[package]] name = "cpufeatures" version = "0.2.12" @@ -670,6 +711,29 @@ dependencies = [ "want", ] +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "indexmap" version = "2.2.5" @@ -722,6 +786,15 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -864,6 +937,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.16.0" @@ -936,6 +1018,7 @@ version = "0.4.1" dependencies = [ "base64 0.21.7", "biscuit-auth", + "chrono", "lazy_static", "orangutan-helpers", "rocket", @@ -1862,6 +1945,60 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.52", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + [[package]] name = "winapi" version = "0.3.9" @@ -1893,6 +2030,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.4", +] + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/src/orangutan-server/Cargo.toml b/src/orangutan-server/Cargo.toml index 1372bce..575928b 100644 --- a/src/orangutan-server/Cargo.toml +++ b/src/orangutan-server/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" [dependencies] base64 = "0.21" +chrono = "0.4" urlencoding = "2" biscuit-auth = "4" lazy_static = "1" diff --git a/src/orangutan-server/src/main.rs b/src/orangutan-server/src/main.rs index ec4eb61..208e278 100644 --- a/src/orangutan-server/src/main.rs +++ b/src/orangutan-server/src/main.rs @@ -4,12 +4,14 @@ use std::fmt; use std::ops::Deref; use std::path::Path; use std::process::exit; +use std::sync::{Arc, Mutex}; use std::time::SystemTime; use biscuit::builder::{Fact, Term}; use biscuit::macros::authorizer; use biscuit::Biscuit; use biscuit_auth as biscuit; +use chrono::{DateTime, Utc}; use lazy_static::lazy_static; use object_reader::{ObjectReader, ReadObjectResponse}; use orangutan_helpers::generate::{self, *}; @@ -38,11 +40,16 @@ lazy_static! { match keys_reader.get_root_biscuit_key() { Ok(public_key) => public_key, Err(err) => { - error!("Error generating root Biscuit key: {}", err); + error!("Error generating root Biscuit key: {err}"); exit(1); }, } }; + /// A list of runtime errors, used to show error logs in an admin page + /// without having to open the cloud hosting provider's logs. + /// + /// // NOTE: `Arc` prevents race conditions + static ref ERRORS: Arc, String)>>> = Arc::new(Mutex::new(Vec::new())); } #[rocket::launch] @@ -55,6 +62,7 @@ fn rocket() -> _ { get_user_info, update_content_github, update_content_other, + errors, ]) .register("/", catchers![unauthorized, not_found]) .manage(ObjectReader::new()) @@ -108,6 +116,21 @@ fn clear_cookies(cookies: &CookieJar<'_>) -> &'static str { "Success" } +#[get("/_errors")] +fn errors(token: Token) -> Result { + if token.profiles().contains(&"*".to_owned()) { + Ok(ERRORS + .lock() + .unwrap() + .iter() + .map(|(d, l)| format!("{d} | {l}")) + .collect::>() + .join("\n")) + } else { + Err(Status::Unauthorized) + } +} + #[get("/<_..>?")] fn handle_refresh_token( origin: &Origin, @@ -150,7 +173,7 @@ fn handle_refresh_token( let new_biscuit = match builder.build(&ROOT_KEY) { Ok(biscuit) => biscuit, Err(err) => { - error!("Error: Could not append block to biscuit: {err}"); + error(format!("Error: Could not append block to biscuit: {err}")); return Err(Status::InternalServerError); }, }; @@ -172,8 +195,8 @@ fn handle_refresh_token( debug!("Redirecting to <{redirect_to}> from <{origin}>…"); Ok(Redirect::found(redirect_to.path().to_string())) }, - Err(e) => { - error!("{e}"); + Err(err) => { + error(format!("{err}")); Err(Status::InternalServerError) }, } @@ -185,29 +208,13 @@ async fn handle_request( token: Option, object_reader: &State, ) -> Result, Error> { - let biscuit = token.map(|t| t.biscuit); - // FIXME: Handle error let path = urlencoding::decode(origin.path().as_str()) .unwrap() .into_owned(); trace!("GET {}", &path); - let user_profiles: Vec = biscuit - .as_ref() - .map(|b| { - b.authorizer() - .unwrap() - .query_all("data($name) <- profile($name)") - .unwrap() - .iter() - .map(|f: &Fact| match f.predicate.terms.get(0).unwrap() { - Term::Str(s) => s.clone(), - t => panic!("Term {t} should be of type String"), - }) - .collect() - }) - .unwrap_or_default(); + let user_profiles: Vec = token.as_ref().map(Token::profiles).unwrap_or_default(); debug!("User has profiles {user_profiles:?}"); let website_id = WebsiteId::from(&user_profiles); @@ -222,7 +229,10 @@ async fn handle_request( .first() .map(|o| o.to_owned()) else { - error!("No file matching '{}' found in stored objects", &path); + error(format!( + "No file matching '{}' found in stored objects", + &path + )); return Ok(None); }; @@ -248,6 +258,7 @@ async fn handle_request( .join(", ") ); let mut profile: Option = None; + let biscuit = token.map(|t| t.biscuit); for allowed_profile in allowed_profiles { trace!("Checking if profile '{allowed_profile}' exists in token…"); if allowed_profile == DEFAULT_PROFILE { @@ -300,7 +311,7 @@ fn update_content_github() -> Result<(), Error> { // Pre-generate default website as we will access it at some point anyway match generate_default_website().map_err(Error::WebsiteGenerationError) { Err(err) => { - error!("{err}"); + error(format!("{err}")); recover_trash(state).map_err(Error::CannotRecoverTrash) }, Ok(()) => empty_trash(state).map_err(Error::CannotEmptyTrash), @@ -317,17 +328,17 @@ async fn _not_found() -> Result { let website_dir = match generate_website_if_needed(&website_id) { Ok(dir) => dir, Err(err) => { - error!("Could not get default website directory: {}", err); + error(format!("Could not get default website directory: {}", err)); return Err("This page doesn't exist or you are not allowed to see it."); }, }; let file_path = website_dir.join(NOT_FOUND_FILE); NamedFile::open(file_path.clone()).await.map_err(|err| { - error!( + error(format!( "Could not read \"not found\" file at <{}>: {}", file_path.display(), err - ); + )); "This page doesn't exist or you are not allowed to see it." }) } @@ -368,6 +379,12 @@ struct Token { biscuit: Biscuit, } +impl Token { + fn profiles(&self) -> Vec { + profiles(&self.biscuit) + } +} + impl Deref for Token { type Target = Biscuit; @@ -472,6 +489,20 @@ impl<'r> FromRequest<'r> for Token { } } +fn profiles(biscuit: &Biscuit) -> Vec { + biscuit + .authorizer() + .unwrap() + .query_all("data($name) <- profile($name)") + .unwrap() + .iter() + .map(|f: &Fact| match f.predicate.terms.get(0).unwrap() { + Term::Str(s) => s.clone(), + t => panic!("Term {t} should be of type String"), + }) + .collect() +} + fn add_cookie( biscuit: &Biscuit, cookies: &CookieJar<'_>, @@ -488,7 +519,7 @@ fn add_cookie( ); }, Err(err) => { - error!("Error setting token cookie: {}", err); + error(format!("Error setting token cookie: {err}")); }, } } @@ -564,7 +595,7 @@ impl<'r> Responder<'r, 'static> for Error { self, _: &'r Request<'_>, ) -> response::Result<'static> { - error!("{self}"); + error(format!("{self}")); Err(Status::InternalServerError) } } @@ -597,6 +628,11 @@ impl fmt::Display for Error { impl std::error::Error for Error {} +fn error(err: String) { + ERRORS.lock().unwrap().push((Utc::now(), err.to_owned())); + error!(err); +} + #[cfg(test)] mod tests { use super::*;