1
0
Fork 0
mirror of https://codeberg.org/Mo8it/git-webhook-client synced 2024-10-18 07:22:39 +00:00

Split code into more modules

This commit is contained in:
Mo 2022-10-12 13:19:54 +02:00
parent bcd55f33c0
commit 322fa93c65
6 changed files with 137 additions and 130 deletions

0
migrations/.keep Normal file
View file

View file

@ -1,8 +1,9 @@
use diesel::prelude::*;
use std::process::Output;
use crate::config::Hook; use crate::config::Hook;
use crate::models::{HookLog, NewHookLog}; use crate::models::{HookLog, NewHookLog};
use crate::schema::hooklog; use crate::schema::hooklog;
use diesel::prelude::*;
use std::process::Output;
fn establish_connection() -> SqliteConnection { fn establish_connection() -> SqliteConnection {
let database_url = "db/db.sqlite"; let database_url = "db/db.sqlite";

66
src/guards.rs Normal file
View file

@ -0,0 +1,66 @@
use hmac::{Hmac, Mac};
use rocket::data::{Data, FromData, Limits, Outcome};
use rocket::http::Status;
use rocket::request::{self, Request};
use serde_json::Value;
use sha2::Sha256;
use crate::config;
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;
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))
}
Err(e) => return Outcome::Failure((Status::InternalServerError, Self::Error::Io(e))),
};
let mut received_signatures = req.headers().get("X-GITEA-SIGNATURE");
let received_signature = match received_signatures.next() {
Some(signature) => hex::decode(signature).unwrap(),
None => return Outcome::Failure((Status::BadRequest, Self::Error::MissingSignature)),
};
if received_signatures.next().is_some() {
return Outcome::Failure((Status::BadRequest, Self::Error::MoreThatOneSignature));
}
if !is_valid_signature(&received_signature, &payload) {
return Outcome::Failure((Status::BadRequest, Self::Error::InvalidSignature));
}
let json: Value = serde_json::from_slice(&payload).unwrap();
let repo = json.get("repository").unwrap();
let clone_url = repo.get("clone_url").unwrap().as_str().unwrap().to_string();
let clone_url = request::local_cache!(req, clone_url);
Outcome::Success(Repo { clone_url })
}
}
fn is_valid_signature(received_signature: &[u8], payload: &[u8]) -> bool {
let mut mac = Hmac::<Sha256>::new_from_slice(&config::get_secret()).unwrap();
mac.update(payload);
let expected_signature = mac.finalize().into_bytes();
received_signature[..] == expected_signature[..]
}

View file

@ -1,135 +1,13 @@
#[macro_use]
extern crate rocket;
mod config; mod config;
mod db; mod db;
mod guards;
mod models; mod models;
mod routes;
mod schema; mod schema;
use hmac::{Hmac, Mac}; #[rocket::launch]
use rocket::data::{self, Data, FromData, Limits, Outcome};
use rocket::http::Status;
use rocket::request::{self, Request};
use serde_json::Value;
use sha2::Sha256;
use std::process::Command;
struct Repo<'r> {
clone_url: &'r str,
}
fn is_valid_signature(received_signature: &[u8], payload: &[u8]) -> bool {
let mut mac = Hmac::<Sha256>::new_from_slice(&config::get_secret()).unwrap();
mac.update(payload);
let expected_signature = mac.finalize().into_bytes();
received_signature[..] == expected_signature[..]
}
#[derive(Debug)]
enum RepoDataGuardError {
PayloadTooLarge,
MissingSignature,
MoreThatOneSignature,
InvalidSignature,
Io(std::io::Error),
}
#[rocket::async_trait]
impl<'r> FromData<'r> for Repo<'r> {
type Error = RepoDataGuardError;
async fn from_data(req: &'r Request<'_>, data: Data<'r>) -> data::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))
}
Err(e) => return Outcome::Failure((Status::InternalServerError, Self::Error::Io(e))),
};
let mut received_signatures = req.headers().get("X-GITEA-SIGNATURE");
let received_signature = match received_signatures.next() {
Some(signature) => hex::decode(signature).unwrap(),
None => return Outcome::Failure((Status::BadRequest, Self::Error::MissingSignature)),
};
if received_signatures.next().is_some() {
return Outcome::Failure((Status::BadRequest, Self::Error::MoreThatOneSignature));
}
if !is_valid_signature(&received_signature, &payload) {
return Outcome::Failure((Status::BadRequest, Self::Error::InvalidSignature));
}
let json: Value = serde_json::from_slice(&payload).unwrap();
let repo = json.get("repository").unwrap();
let clone_url = repo.get("clone_url").unwrap().as_str().unwrap().to_string();
let clone_url = request::local_cache!(req, clone_url);
Outcome::Success(Repo { clone_url })
}
}
#[get("/<id>")]
fn index(id: i32) -> String {
let hook_log = db::get_hook_log(id);
format!(
"Hook log id:
{}
Repository url:
{}
Command with arguments:
{}
Current directory of the command:
{}
Standard output:
{}
Standard error:
{}
Status code:
{}
",
hook_log.id,
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"),
}
)
}
#[post("/trigger", format = "json", data = "<repo>")]
fn trigger(repo: Repo) -> String {
let hook = config::get_hook(repo.clone_url).unwrap();
let output = Command::new(&hook.command)
.args(&hook.args)
.current_dir(&hook.current_dir)
.output()
.unwrap();
let new_hook_log_id = db::add_hook_log(&hook, &output);
let base_url = config::get_base_url();
format!("https://{base_url}/{new_hook_log_id}")
}
#[launch]
fn rocket() -> _ { fn rocket() -> _ {
rocket::build() rocket::build()
.mount("/", routes![index]) .mount("/", rocket::routes![routes::index])
.mount("/api", routes![trigger]) .mount("/api", rocket::routes![routes::trigger])
} }

View file

@ -1,6 +1,7 @@
use crate::schema::hooklog;
use diesel::prelude::{Insertable, Queryable}; use diesel::prelude::{Insertable, Queryable};
use crate::schema::hooklog;
#[derive(Queryable)] #[derive(Queryable)]
pub struct HookLog { pub struct HookLog {
pub id: i32, pub id: i32,

61
src/routes.rs Normal file
View file

@ -0,0 +1,61 @@
use rocket::{get, post};
use std::process::Command;
use crate::config;
use crate::db;
use crate::guards;
#[get("/<id>")]
pub fn index(id: i32) -> String {
let hook_log = db::get_hook_log(id);
format!(
"Hook log id:
{}
Repository url:
{}
Command with arguments:
{}
Current directory of the command:
{}
Standard output:
{}
Standard error:
{}
Status code:
{}
",
hook_log.id,
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"),
}
)
}
#[post("/trigger", format = "json", data = "<repo>")]
pub fn trigger(repo: guards::Repo) -> String {
let hook = config::get_hook(repo.clone_url).unwrap();
let output = Command::new(&hook.command)
.args(&hook.args)
.current_dir(&hook.current_dir)
.output()
.unwrap();
let new_hook_log_id = db::add_hook_log(&hook, &output);
let base_url = config::get_base_url();
format!("https://{base_url}/{new_hook_log_id}")
}