mirror of
https://codeberg.org/Mo8it/git-webhook-client
synced 2024-10-18 07:22:39 +00:00
Progress!
This commit is contained in:
parent
5c35ca6a0e
commit
1c4b5c877c
3 changed files with 197 additions and 35 deletions
111
Cargo.lock
generated
111
Cargo.lock
generated
|
@ -69,6 +69,12 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async_once"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ce4f10ea3abcd6617873bae9f91d1c5332b4a778bd9ce34d0cd517474c1de82"
|
||||
|
||||
[[package]]
|
||||
name = "atomic"
|
||||
version = "0.5.1"
|
||||
|
@ -128,6 +134,43 @@ version = "1.2.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db"
|
||||
|
||||
[[package]]
|
||||
name = "cached"
|
||||
version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3e27085975166ffaacbd04527132e1cf5906fa612991f9b4fea08e787da2961"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"async_once",
|
||||
"cached_proc_macro",
|
||||
"cached_proc_macro_types",
|
||||
"futures",
|
||||
"hashbrown",
|
||||
"instant",
|
||||
"lazy_static",
|
||||
"once_cell",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cached_proc_macro"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "751f7f4e7a091545e7f6c65bacc404eaee7e87bfb1f9ece234a1caa173dc16f2"
|
||||
dependencies = [
|
||||
"cached_proc_macro_types",
|
||||
"darling",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cached_proc_macro_types"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a4f925191b4367301851c6d99b09890311d74b0d43f274c0b34c86d308a3663"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.73"
|
||||
|
@ -197,6 +240,41 @@ dependencies = [
|
|||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.13.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.13.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.13.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "devise"
|
||||
version = "0.3.1"
|
||||
|
@ -398,6 +476,7 @@ dependencies = [
|
|||
name = "git-webhook-client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cached",
|
||||
"hmac",
|
||||
"rocket",
|
||||
"serde",
|
||||
|
@ -521,6 +600,12 @@ dependencies = [
|
|||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.1"
|
||||
|
@ -1127,6 +1212,12 @@ dependencies = [
|
|||
"loom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.4.1"
|
||||
|
@ -1158,6 +1249,26 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.4"
|
||||
|
|
|
@ -7,6 +7,7 @@ readme = "README.adoc"
|
|||
license-file = "LICENSE"
|
||||
|
||||
[dependencies]
|
||||
cached = "0.39.0"
|
||||
hmac = "0.12.1"
|
||||
rocket = "0.5.0-rc.2"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
|
120
src/main.rs
120
src/main.rs
|
@ -1,37 +1,81 @@
|
|||
#[macro_use]
|
||||
extern crate rocket;
|
||||
|
||||
use rocket::data::{self, Data, FromData, Limits, Outcome, ToByteUnit};
|
||||
use rocket::data::{self, Data, FromData, Limits, Outcome};
|
||||
use rocket::http::Status;
|
||||
use rocket::outcome::Outcome::{Failure, Success};
|
||||
use rocket::request::{self, Request};
|
||||
|
||||
use hmac::{Hmac, Mac};
|
||||
use sha2::Sha256;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
use std::path::Path;
|
||||
|
||||
const CONFIG_FILENAME: &str = "config.json";
|
||||
use cached::proc_macro::once;
|
||||
|
||||
struct SignatureDataGuard;
|
||||
#[derive(Deserialize, Clone)]
|
||||
struct Hook {
|
||||
repo_url: String,
|
||||
commands: Vec<String>,
|
||||
}
|
||||
|
||||
fn is_valid_signature(received_signature: &str, payload: &Vec<u8>) -> bool {
|
||||
let config_file = File::open(CONFIG_FILENAME).unwrap();
|
||||
#[derive(Deserialize, Clone)]
|
||||
struct Config {
|
||||
secret: String,
|
||||
hooks: Vec<Hook>,
|
||||
}
|
||||
|
||||
struct Repo<'r> {
|
||||
name: &'r str,
|
||||
clone_url: &'r str,
|
||||
}
|
||||
|
||||
#[once]
|
||||
fn get_config() -> Config {
|
||||
let config_path = Path::new("config.json");
|
||||
let config_file = File::open(config_path).unwrap();
|
||||
let config_reader = BufReader::new(config_file);
|
||||
let config: Config = serde_json::from_reader(config_reader).unwrap();
|
||||
config
|
||||
}
|
||||
|
||||
#[once]
|
||||
fn get_secret() -> Vec<u8> {
|
||||
let config = get_config();
|
||||
config.secret.as_bytes().to_owned()
|
||||
}
|
||||
|
||||
#[once]
|
||||
fn get_hooks() -> Vec<Hook> {
|
||||
let config = get_config();
|
||||
config.hooks
|
||||
}
|
||||
|
||||
fn get_hook_commands(clone_url: &str) -> Option<Vec<String>> {
|
||||
let hooks = get_hooks();
|
||||
for hook in hooks {
|
||||
if hook.repo_url == clone_url {
|
||||
return Some(hook.commands);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn is_valid_signature(received_signature: &[u8], payload: &Vec<u8>) -> bool {
|
||||
type HmacSha256 = Hmac<Sha256>;
|
||||
let mut mac = HmacSha256::new_from_slice(config.secret.as_bytes()).unwrap();
|
||||
let mut mac = HmacSha256::new_from_slice(&get_secret()).unwrap();
|
||||
mac.update(payload);
|
||||
let expected_signature = mac.finalize().into_bytes();
|
||||
|
||||
received_signature.as_bytes()[..] == expected_signature[..]
|
||||
received_signature[..] == expected_signature[..]
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Error {
|
||||
enum RepoDataGuardError {
|
||||
PayloadTooLarge,
|
||||
MissingSignature,
|
||||
MoreThatOneSignature,
|
||||
|
@ -40,54 +84,60 @@ enum Error {
|
|||
}
|
||||
|
||||
#[rocket::async_trait]
|
||||
impl<'r> FromData<'r> for SignatureDataGuard {
|
||||
type Error = Error;
|
||||
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(256.bytes()).into_bytes().await {
|
||||
let payload = match data.open(Limits::JSON).into_bytes().await {
|
||||
Ok(payload) if payload.is_complete() => payload.into_inner(),
|
||||
Ok(_) => return Failure((Status::PayloadTooLarge, Error::PayloadTooLarge)),
|
||||
Err(e) => return Failure((Status::InternalServerError, Error::Io(e))),
|
||||
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) => signature,
|
||||
None => return Outcome::Failure((Status::BadRequest, Error::MissingSignature)),
|
||||
Some(signature) => signature.as_bytes(),
|
||||
None => return Outcome::Failure((Status::BadRequest, Self::Error::MissingSignature)),
|
||||
};
|
||||
|
||||
if received_signatures.next().is_some() {
|
||||
return Outcome::Failure((Status::BadRequest, Error::MoreThatOneSignature));
|
||||
return Outcome::Failure((Status::BadRequest, Self::Error::MoreThatOneSignature));
|
||||
}
|
||||
|
||||
if !is_valid_signature(received_signature, &payload) {
|
||||
return Outcome::Failure((Status::BadRequest, Error::InvalidSignature));
|
||||
return Outcome::Failure((Status::BadRequest, Self::Error::InvalidSignature));
|
||||
}
|
||||
|
||||
Outcome::Success(SignatureDataGuard)
|
||||
let json: Value = serde_json::from_slice(&payload).unwrap();
|
||||
let repo = json.get("repository").unwrap();
|
||||
let repo_name = repo.get("repo_name").unwrap().as_str().unwrap().to_string();
|
||||
let clone_url = repo.get("clone_url").unwrap().as_str().unwrap().to_string();
|
||||
|
||||
let repo_name = request::local_cache!(req, repo_name);
|
||||
let clone_url = request::local_cache!(req, clone_url);
|
||||
|
||||
Outcome::Success(Repo {
|
||||
name: repo_name,
|
||||
clone_url,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Hook {
|
||||
repo_url: String,
|
||||
commands: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Config {
|
||||
secret: String,
|
||||
hooks: Vec<Hook>,
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
fn index() -> &'static str {
|
||||
"Hello, world!"
|
||||
}
|
||||
|
||||
#[post("/trigger", data = "<payload>")]
|
||||
fn trigger(payload: SignatureDataGuard) -> &'static str {
|
||||
"Sensitive data."
|
||||
#[post("/trigger", format = "json", data = "<repo>")]
|
||||
fn trigger(repo: Repo) -> &'static str {
|
||||
println!("{:?}", repo.name);
|
||||
let commands = get_hook_commands(repo.clone_url).unwrap();
|
||||
for command in commands {
|
||||
println!("{:?}", command)
|
||||
}
|
||||
"Successful trigger!"
|
||||
}
|
||||
|
||||
#[launch]
|
||||
|
|
Loading…
Reference in a new issue