From c270cd91db2cf37d75645b0b3d9f2efa3ae7981b Mon Sep 17 00:00:00 2001 From: Mo8it Date: Fri, 21 Oct 2022 04:54:57 +0200 Subject: [PATCH] Use template and replace expect with match --- .gitignore | 1 + Cargo.toml | 3 +- Rocket.toml | 6 +++ src/db.rs | 86 +++++++++++++++++++------------- src/guards.rs | 106 +++++++++++++++++++++++++++------------- src/main.rs | 3 ++ src/models.rs | 3 +- src/routes.rs | 85 ++++++++++++++------------------ src/states.rs | 7 +-- templates/hook_log.tera | 23 +++++++++ 10 files changed, 203 insertions(+), 120 deletions(-) create mode 100644 Rocket.toml create mode 100644 templates/hook_log.tera diff --git a/.gitignore b/.gitignore index ffe83ab..42b3623 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /Cargo.lock /config.json /db/ +/scripts/ /target/ diff --git a/Cargo.toml b/Cargo.toml index a9c2082..6d24a73 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ diesel = { version = "2.0.2", features = [ hex = "0.4.3" hmac = "0.12.1" rocket = "0.5.0-rc.2" +rocket_dyn_templates = { version = "0.1.0-rc.2", features = ["tera"] } serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0.85" +serde_json = "1.0.87" sha2 = "0.10.6" diff --git a/Rocket.toml b/Rocket.toml new file mode 100644 index 0000000..9ea876c --- /dev/null +++ b/Rocket.toml @@ -0,0 +1,6 @@ +[default] +template_dir = "templates" + +[release] +address = "0.0.0.0" +port = 80 diff --git a/src/db.rs b/src/db.rs index 2ae2044..08393e9 100644 --- a/src/db.rs +++ b/src/db.rs @@ -10,23 +10,30 @@ use crate::schema::hooklog; pub type DBPool = Pool>; -pub fn establish_connection_pool() -> DBPool { - let database_url = - env::var("DATABASE_URL").expect("Environment variable DATABASE_URL missing!"); +pub fn establish_connection_pool() -> Result { + let database_url = match env::var("DATABASE_URL") { + Ok(url) => url, + Err(_) => return Err("Environment variable DATABASE_URL missing!".to_string()), + }; let manager = ConnectionManager::::new(&database_url); - Pool::builder() - .build(manager) - .expect("Could not build database connection pool!") + match Pool::builder().build(manager) { + Ok(pool) => Ok(pool), + Err(_) => Err("Could not build database connection pool!".to_string()), + } } -fn get_conn(pool: &DBPool) -> PooledConnection> { - pool.get() - .expect("Can not get a connection from the database pool!") +fn get_conn( + pool: &DBPool, +) -> Result>, String> { + match pool.get() { + Ok(pool) => Ok(pool), + Err(_) => Err("Could not get database pool!".to_string()), + } } -pub fn add_hook_log(pool: &DBPool, hook: &Hook, output: &Output) -> i32 { - let conn = &mut get_conn(pool); +pub fn add_hook_log(pool: &DBPool, hook: &Hook, output: &Output) -> Result { + let conn = &mut get_conn(pool)?; let command_with_args = hook.command.to_owned() + " " + &hook.args.join(" "); @@ -35,32 +42,45 @@ pub fn add_hook_log(pool: &DBPool, hook: &Hook, output: &Output) -> i32 { repo_url: &hook.repo_url, command_with_args: &command_with_args, current_dir: &hook.current_dir, - stdout: std::str::from_utf8(&output.stdout).expect("Can not convert stdout to str!"), - stderr: std::str::from_utf8(&output.stderr).expect("Can not convert stderr to str!"), + stdout: match std::str::from_utf8(&output.stdout) { + Ok(s) => s, + Err(_) => return Err("Can not convert stdout to str!".to_string()), + }, + stderr: match std::str::from_utf8(&output.stderr) { + Ok(s) => s, + Err(_) => return Err("Can not convert stderr to str!".to_string()), + }, status_code: output.status.code(), }; - let result: HookLog = diesel::insert_into(hooklog::table) + let result = diesel::insert_into(hooklog::table) .values(&new_hook_log) - .get_result(conn) - .expect("Error saving hook log!"); + .get_result::(conn); - result.id -} - -pub fn get_hook_log(pool: &DBPool, id: i32) -> HookLog { - let conn = &mut get_conn(pool); - - if id >= 0 { - hooklog::dsl::hooklog - .find(id) - .first(conn) - .expect("No hook log exists for this id!") - } else { - hooklog::dsl::hooklog - .order(hooklog::dsl::id.desc()) - .offset((-id - 1).into()) - .first(conn) - .expect("No hook log exists for this negative id!") + match result { + Ok(hook_log) => Ok(hook_log.id), + Err(e) => Err(e.to_string()), + } +} + +pub fn get_hook_log(pool: &DBPool, id: i32) -> Result { + // id=0 not allowed! + + let conn = &mut get_conn(pool)?; + + let hl: Result; + + if id > 0 { + hl = hooklog::dsl::hooklog.find(id).first(conn); + } else { + hl = hooklog::dsl::hooklog + .order(hooklog::dsl::id.desc()) + .offset((-id - 1).into()) + .first(conn); + } + + match hl { + Ok(hl) => Ok(hl), + Err(e) => Err(e.to_string()), } } diff --git a/src/guards.rs b/src/guards.rs index 80170f2..ce23eee 100644 --- a/src/guards.rs +++ b/src/guards.rs @@ -11,60 +11,93 @@ pub struct Repo<'r> { pub clone_url: &'r str, } -#[derive(Debug)] -pub enum RepoDataGuardError { - PayloadTooLarge, - MissingSignature, - MoreThatOneSignature, - InvalidSignature, - Io(std::io::Error), -} - #[rocket::async_trait] impl<'r> FromData<'r> for Repo<'r> { - type Error = RepoDataGuardError; + type Error = String; async fn from_data(req: &'r Request<'_>, data: Data<'r>) -> Outcome<'r, Self> { let payload = match data.open(Limits::JSON).into_bytes().await { Ok(payload) if payload.is_complete() => payload.into_inner(), Ok(_) => { - return Outcome::Failure((Status::PayloadTooLarge, Self::Error::PayloadTooLarge)) + return Outcome::Failure((Status::PayloadTooLarge, "Payload too large".to_string())) } - Err(e) => return Outcome::Failure((Status::InternalServerError, Self::Error::Io(e))), + Err(e) => return Outcome::Failure((Status::InternalServerError, e.to_string())), }; let mut received_signatures = req.headers().get("X-GITEA-SIGNATURE"); let received_signature = match received_signatures.next() { - Some(signature) => { - hex::decode(signature).expect("Can not hex decode the received signature!") + Some(signature) => match hex::decode(signature) { + Ok(signature) => signature, + Err(_) => { + return Outcome::Failure(( + Status::BadRequest, + "Can not hex decode the received signature!".to_string(), + )) + } + }, + None => { + return Outcome::Failure((Status::BadRequest, "Missing signature!".to_string())) } - None => return Outcome::Failure((Status::BadRequest, Self::Error::MissingSignature)), }; if received_signatures.next().is_some() { - return Outcome::Failure((Status::BadRequest, Self::Error::MoreThatOneSignature)); + return Outcome::Failure(( + Status::BadRequest, + "Received more than one signature!".to_string(), + )); } - let config_state = req - .rocket() - .state::() - .expect("Can not get the config state!"); + let config_state = match req.rocket().state::() { + Some(state) => state, + None => { + return Outcome::Failure(( + Status::BadRequest, + "Can not get the config state!".to_string(), + )) + } + }; if !is_valid_signature(&config_state.secret, &received_signature, &payload) { - return Outcome::Failure((Status::BadRequest, Self::Error::InvalidSignature)); + return Outcome::Failure((Status::BadRequest, "Invalid signature!".to_string())); } - let json: Value = - serde_json::from_slice(&payload).expect("Can not parse payload into JSON!"); - let repo = json - .get("repository") - .expect("Can not get the repository value from the payload!"); - let clone_url = repo - .get("clone_url") - .expect("Can not get value clone_url from repository in the payload!") - .as_str() - .expect("The value of clone_url from repository in the payload is not a string!") - .to_string(); + let json: Value = match serde_json::from_slice(&payload) { + Ok(json) => json, + Err(_) => { + return Outcome::Failure(( + Status::BadRequest, + "Can not parse payload into JSON!".to_string(), + )) + } + }; + let repo = match json.get("repository") { + Some(repo) => repo, + None => { + return Outcome::Failure(( + Status::BadRequest, + "Can not get the repository value from the payload!".to_string(), + )) + } + }; + let clone_url = match repo.get("clone_url") { + Some(url) => url, + None => { + return Outcome::Failure(( + Status::BadRequest, + "Can not get value clone_url from repository in the payload!".to_string(), + )) + } + }; + let clone_url = match clone_url.as_str() { + Some(url) => url.to_string(), + None => { + return Outcome::Failure(( + Status::BadRequest, + "The value of clone_url from repository in the payload is not a string!" + .to_string(), + )) + } + }; let clone_url = request::local_cache!(req, clone_url); @@ -73,8 +106,13 @@ impl<'r> FromData<'r> for Repo<'r> { } fn is_valid_signature(secret: &[u8], received_signature: &[u8], payload: &[u8]) -> bool { - let mut mac = - Hmac::::new_from_slice(secret).expect("Can not generate a mac from the secret!"); + let mut mac = match Hmac::::new_from_slice(secret) { + Ok(mac) => mac, + Err(_) => { + println!("Can not generate a mac from the secret!"); + return false; + } + }; mac.update(payload); let expected_signature = mac.finalize().into_bytes(); diff --git a/src/main.rs b/src/main.rs index ccf488a..7f34d46 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,8 @@ mod routes; mod schema; mod states; +use rocket_dyn_templates::Template; + #[rocket::launch] fn rocket() -> _ { rocket::build() @@ -13,4 +15,5 @@ fn rocket() -> _ { .mount("/api", rocket::routes![routes::trigger]) .manage(states::DB::new()) .manage(states::Config::new()) + .attach(Template::fairing()) } diff --git a/src/models.rs b/src/models.rs index 37fb694..9dccd03 100644 --- a/src/models.rs +++ b/src/models.rs @@ -1,8 +1,9 @@ use diesel::prelude::{Insertable, Queryable}; +use serde::Serialize; use crate::schema::hooklog; -#[derive(Queryable)] +#[derive(Queryable, Serialize)] pub struct HookLog { pub id: i32, pub datetime: String, diff --git a/src/routes.rs b/src/routes.rs index 8d66e2d..558cad8 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -1,4 +1,6 @@ +use rocket::response::status::BadRequest; use rocket::{get, post, State}; +use rocket_dyn_templates::Template; use std::process::Command; use crate::db; @@ -6,51 +8,25 @@ use crate::guards; use crate::states; #[get("/?")] -pub fn index(db_state: &State, id: Option) -> String { +pub fn index( + db_state: &State, + id: Option, +) -> Result> { let id = match id { Some(id) => id, None => -1, }; - let hook_log = db::get_hook_log(&db_state.pool, id); + if id == 0 { + return Err(BadRequest(Some("id=0 not allowed!".to_string()))); + } - format!( - "Hook log id: -{} + let hook_log = match db::get_hook_log(&db_state.pool, id) { + Ok(hl) => hl, + Err(e) => return Err(BadRequest(Some(e))), + }; -Datetime: -{} - -Repository url: -{} - -Command with arguments: -{} - -Current directory of the command: -{} - -Standard output: -{} - -Standard error: -{} - -Status code: -{} -", - hook_log.id, - hook_log.datetime, - hook_log.repo_url, - hook_log.command_with_args, - hook_log.current_dir, - hook_log.stdout, - hook_log.stderr, - match hook_log.status_code { - Some(code) => code.to_string(), - None => String::from("None"), - } - ) + Ok(Template::render("hook_log", hook_log)) } #[post("/trigger", format = "json", data = "")] @@ -58,19 +34,32 @@ pub fn trigger( db_state: &State, config_state: &State, repo: guards::Repo, -) -> String { - let hook = config_state.get_hook(repo.clone_url).expect(&format!( - "No matching repository with url {} in the configuration file.", - repo.clone_url - )); +) -> Result> { + let hook = match config_state.get_hook(repo.clone_url) { + Some(hook) => hook, + None => { + return Err(BadRequest(Some(format!( + "No matching repository with url {} in the configuration file.", + repo.clone_url + )))) + } + }; - let output = Command::new(&hook.command) + let output = match Command::new(&hook.command) .args(&hook.args) .current_dir(&hook.current_dir) .output() - .expect("Can not run the hook command!"); + { + Ok(output) => output, + Err(_) => { + return Err(BadRequest(Some( + "Can not run the hook command!".to_string(), + ))) + } + }; - let new_hook_log_id = db::add_hook_log(&db_state.pool, hook, &output); - - format!("{}/?id={}", config_state.base_url, new_hook_log_id) + match db::add_hook_log(&db_state.pool, hook, &output) { + Ok(new_hook_log_id) => Ok(format!("{}/?id={}", config_state.base_url, new_hook_log_id)), + Err(e) => Err(BadRequest(Some(e))), + } } diff --git a/src/states.rs b/src/states.rs index 43b2994..972f8f2 100644 --- a/src/states.rs +++ b/src/states.rs @@ -7,9 +7,10 @@ pub struct DB { impl DB { pub fn new() -> Self { - let pool = db::establish_connection_pool(); - - Self { pool } + match db::establish_connection_pool() { + Ok(pool) => Self { pool }, + Err(e) => panic!("Could not establish database pool: {}", e), + } } } diff --git a/templates/hook_log.tera b/templates/hook_log.tera new file mode 100644 index 0000000..b84ff8b --- /dev/null +++ b/templates/hook_log.tera @@ -0,0 +1,23 @@ +Hook log id: +{{ id }} + +Datetime: +{{ datetime }} + +Repository url: +{{ repo_url }} + +Command with arguments: +{{ command_with_args }} + +Current directory of the command: +{{ current_dir }} + +Standard output: +{{ stdout }} + +Standard error: +{{ stderr }} + +Status code: +{{ status_code }}