diff --git a/Cargo.lock b/Cargo.lock index ec9bfe0..b21d842 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -58,9 +58,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.15" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" dependencies = [ "memchr", ] @@ -71,12 +71,6 @@ version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b" -[[package]] -name = "arrayref" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" - [[package]] name = "arrayvec" version = "0.5.2" @@ -110,12 +104,6 @@ dependencies = [ "safemem", ] -[[package]] -name = "base64" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" - [[package]] name = "base64" version = "0.13.0" @@ -140,17 +128,6 @@ dependencies = [ "wyz", ] -[[package]] -name = "blake2b_simd" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" -dependencies = [ - "arrayref", - "arrayvec", - "constant_time_eq", -] - [[package]] name = "block-buffer" version = "0.9.0" @@ -168,9 +145,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "cc" -version = "1.0.67" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" +checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787" [[package]] name = "cfg-if" @@ -187,12 +164,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "constant_time_eq" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" - [[package]] name = "cookie" version = "0.11.4" @@ -210,10 +181,13 @@ dependencies = [ ] [[package]] -name = "cpuid-bool" -version = "0.1.2" +name = "cpufeatures" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" +checksum = "ed00c67cb5d0a7d64a44f6ad2668db7e7530311dd53ea79bcd4fb022c64911c8" +dependencies = [ + "libc", +] [[package]] name = "cpuid-bool" @@ -221,17 +195,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba" -[[package]] -name = "crossbeam-utils" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49" -dependencies = [ - "autocfg", - "cfg-if", - "lazy_static", -] - [[package]] name = "crypto-mac" version = "0.10.0" @@ -294,18 +257,18 @@ dependencies = [ [[package]] name = "dirs" -version = "3.0.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "142995ed02755914747cc6ca76fc7e4583cd18578746716d0508ea6ed558b9ff" +checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309" dependencies = [ "dirs-sys", ] [[package]] name = "dirs-sys" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a" +checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" dependencies = [ "libc", "redox_users", @@ -359,24 +322,13 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.1.16" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" dependencies = [ "cfg-if", "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.10.2+wasi-snapshot-preview1", + "wasi", ] [[package]] @@ -438,9 +390,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a1ce40d6fc9764887c2fdc7305c3dcc429ba11ff981c1509416afd5697e4437" +checksum = "f3a87b616e37e93c22fb19bcd386f02f3af5ea98a25670ad0fce773de23c5e68" [[package]] name = "humantime" @@ -542,9 +494,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41" +checksum = "789da6d93f1b866ffe175afc5322a4d76c038605a1c3319bb57b06967ca98a36" [[package]] name = "linked-hash-map" @@ -578,9 +530,9 @@ checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" [[package]] name = "memchr" -version = "2.3.4" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" +checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" [[package]] name = "mime" @@ -660,7 +612,7 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eebcc4aa140b9abd2bc40d9c3f7ccec842679cd79045ac3a7ac698c1a064b7cd" dependencies = [ - "cpuid-bool 0.2.0", + "cpuid-bool", "opaque-debug", "universal-hash", ] @@ -682,11 +634,11 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" +checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" dependencies = [ - "unicode-xid 0.2.1", + "unicode-xid 0.2.2", ] [[package]] @@ -704,7 +656,7 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" dependencies = [ - "proc-macro2 1.0.26", + "proc-macro2 1.0.27", ] [[package]] @@ -741,7 +693,7 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" dependencies = [ - "getrandom 0.2.2", + "getrandom", ] [[package]] @@ -755,26 +707,28 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.1.57" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" +checksum = "742739e41cd49414de871ea5e549afb7e2a3ac77b589bcbebe8c82fab37147fc" +dependencies = [ + "bitflags", +] [[package]] name = "redox_users" -version = "0.3.5" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" dependencies = [ - "getrandom 0.1.16", + "getrandom", "redox_syscall", - "rust-argon2", ] [[package]] name = "regex" -version = "1.4.5" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" dependencies = [ "aho-corasick", "memchr", @@ -783,9 +737,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.23" +version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" [[package]] name = "ring" @@ -801,12 +755,12 @@ dependencies = [ [[package]] name = "rocket" -version = "0.4.7" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7febfdfd4d43facfc7daba20349ebe2c310c6735bd6a2a9255ea8bc425b4cb13" +checksum = "4a7ab1dfdc75bb8bd2be381f37796b1b300c45a3c9145b34d86715e8dd90bf28" dependencies = [ "atty", - "base64 0.12.3", + "base64 0.13.0", "log 0.4.14", "memchr", "num_cpus", @@ -822,9 +776,9 @@ dependencies = [ [[package]] name = "rocket_codegen" -version = "0.4.7" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ceac2c55b2c8b1cdc53add64332defa5fc227f64263b86b4114d1386286d42a3" +checksum = "1729e687d6d2cf434d174da84fb948f7fef4fac22d20ce94ca61c28b72dbcf9f" dependencies = [ "devise", "glob", @@ -837,9 +791,9 @@ dependencies = [ [[package]] name = "rocket_http" -version = "0.4.7" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce364100ed7a1bf39257b69ebd014c1d5b4979b0d365d8c9ab0aa9c79645493d" +checksum = "6131e6e6d38a9817f4a494ff5da95971451c2eb56a53915579fc9c80f6ef0117" dependencies = [ "cookie", "hyper", @@ -863,18 +817,6 @@ dependencies = [ "fsio", ] -[[package]] -name = "rust-argon2" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb" -dependencies = [ - "base64 0.13.0", - "blake2b_simd", - "constant_time_eq", - "crossbeam-utils", -] - [[package]] name = "rustls" version = "0.14.0" @@ -913,22 +855,22 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.125" +version = "1.0.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" +checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.125" +version = "1.0.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" +checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" dependencies = [ - "proc-macro2 1.0.26", + "proc-macro2 1.0.27", "quote 1.0.9", - "syn 1.0.70", + "syn 1.0.72", ] [[package]] @@ -956,13 +898,13 @@ dependencies = [ [[package]] name = "sha2" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa827a14b29ab7f44778d14a88d3cb76e949c45083f7dbfa507d0cb699dc12de" +checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12" dependencies = [ "block-buffer", "cfg-if", - "cpuid-bool 0.1.2", + "cpufeatures", "digest", "opaque-debug", ] @@ -1004,13 +946,13 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.70" +version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9505f307c872bab8eb46f77ae357c8eba1fdacead58ee5a850116b1d7f82883" +checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82" dependencies = [ - "proc-macro2 1.0.26", + "proc-macro2 1.0.27", "quote 1.0.9", - "unicode-xid 0.2.1", + "unicode-xid 0.2.2", ] [[package]] @@ -1030,22 +972,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e" +checksum = "fa6f76457f59514c7eeb4e59d891395fab0b2fd1d40723ae737d64153392e9c6" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0" +checksum = "8a36768c0fbf1bb15eca10defa29526bda730a2376c2ab4393ccfa16fb1a318d" dependencies = [ - "proc-macro2 1.0.26", + "proc-macro2 1.0.27", "quote 1.0.9", - "syn 1.0.70", + "syn 1.0.72", ] [[package]] @@ -1135,9 +1077,9 @@ checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" [[package]] name = "unicode-xid" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" [[package]] name = "universal-hash" @@ -1188,12 +1130,6 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - [[package]] name = "wasi" version = "0.10.2+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index 37ae86b..0a021c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ thiserror = "1.0" run_script = "0.7" [package.metadata.deb] -extended-description = "Webhookey receives requests as for example sent by Gitea's webhooks. Those requests are filtered against configurable filters. When a filter matches values from the header and the body can be passed to scripts which are then executed." +extended-description = "Webhookey receives requests in form of a so called Webhook as for example sent by Gitea. Those requests are matched against configured filters, if a filter matches, values from the header and the body can be passed to scripts as parameters which are then executed subsequently." maintainer-scripts = "debian/" systemd-units = { enable = false } assets = [ diff --git a/README.md b/README.md index 25b32f2..50943c7 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Webhookey can either be run from the project directory with: ``` or you can copy the produced binary somewhere else from -`webhookey/target/{debug, release}/webhookey` depending on which one +`webhookey/target/{debug,release}/webhookey` depending on which one you built. ## Configuration diff --git a/src/main.rs b/src/main.rs index d773821..7e372f2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,7 +16,6 @@ use regex::Regex; use rocket::{ data::{self, FromDataSimple}, fairing::AdHoc, - get, http::{HeaderMap, Status}, post, routes, Data, Outcome::{Failure, Success}, @@ -41,6 +40,15 @@ enum AddrType { IpNet(IpNet), } +impl AddrType { + fn matches(&self, client_ip: &IpAddr) -> bool { + match self { + AddrType::IpAddr(addr) => addr == client_ip, + AddrType::IpNet(net) => net.contains(client_ip), + } + } +} + #[derive(Debug, Deserialize, Serialize)] #[serde(deny_unknown_fields, rename_all = "lowercase")] enum IpFilter { @@ -48,6 +56,15 @@ enum IpFilter { Deny(Vec), } +impl IpFilter { + fn validate(&self, client_ip: &IpAddr) -> bool { + match self { + IpFilter::Allow(list) => list.iter().any(|i| i.matches(client_ip)), + IpFilter::Deny(list) => !list.iter().any(|i| i.matches(client_ip)), + } + } +} + #[derive(Debug, Deserialize, Serialize)] #[serde(deny_unknown_fields)] struct Config { @@ -74,7 +91,7 @@ struct Filter { #[derive(Debug, Error)] enum WebhookeyError { #[error("Could not extract signature from header")] - InvalidHeader, + InvalidSignature, #[error("Unauthorized request from `{0}`")] Unauthorized(IpAddr), #[error("Unmatched hook from `{0}`")] @@ -86,58 +103,29 @@ enum WebhookeyError { } #[derive(Debug)] -struct Hooks(HashMap); +struct Hooks { + inner: HashMap, +} fn accept_ip(hook_name: &str, client_ip: &IpAddr, ip_filter: &Option) -> bool { match ip_filter { - Some(IpFilter::Allow(list)) => { - for i in list { - match i { - AddrType::IpAddr(addr) => { - if addr == client_ip { - info!("Allow hook `{}` from {}", &hook_name, &addr); - return true; - } - } - AddrType::IpNet(net) => { - if net.contains(client_ip) { - info!("Allow hook `{}` from {}", &hook_name, &net); - return true; - } - } - } + Some(filter) => { + if filter.validate(client_ip) { + info!("Allow hook `{}` from {}", &hook_name, &client_ip); + true + } else { + warn!("Deny hook `{}` from {}", &hook_name, &client_ip); + false } - - warn!("Deny hook `{}` from {}", &hook_name, &client_ip); - return false; } - Some(IpFilter::Deny(list)) => { - for i in list { - match i { - AddrType::IpAddr(addr) => { - if addr == client_ip { - warn!("Deny hook `{}` from {}", &hook_name, &addr); - return false; - } - } - AddrType::IpNet(net) => { - if net.contains(client_ip) { - warn!("Deny hook `{}` from {}", &hook_name, &net); - return false; - } - } - } - } - - info!("Allow hook `{}` from {}", &hook_name, &client_ip) + None => { + info!( + "Allow hook `{}` from {}, no IP filter was configured", + &hook_name, &client_ip + ); + true } - None => info!( - "Allow hook `{}` from {} as no IP filter was configured", - &hook_name, &client_ip - ), } - - true } fn validate_request(secret: &str, signature: &str, data: &[u8]) -> Result<()> { @@ -161,7 +149,31 @@ fn get_parameter(input: &str) -> Result> { Ok(result) } -fn replace_parameter(input: &str, headers: &HeaderMap, data: &serde_json::Value) -> Result { +fn get_header_field<'a>(headers: &'a HeaderMap, param: &[&str]) -> Result<&'a str> { + headers + .get_one( + param + .get(1) + .ok_or_else(|| anyhow!("Missing parameter for `header` expression"))?, + ) + .ok_or_else(|| anyhow!("Could not extract event parameter from header")) +} + +fn get_value_from_pointer<'a>(data: &'a serde_json::Value, pointer: &'a str) -> Result<&'a str> { + let value = data + .pointer(pointer) + .ok_or_else(|| anyhow!("Could not get field from pointer {}", pointer))?; + + value + .as_str() + .ok_or_else(|| anyhow!("Could not convert value `{}` to string", value)) +} + +fn replace_parameters( + input: &str, + headers: &HeaderMap, + data: &serde_json::Value, +) -> Result { let parse: IResult<&str, Vec<&str>> = many0(alt(( map_res( delimited(tag("{{"), take_until("}}"), tag("}}")), @@ -169,23 +181,8 @@ fn replace_parameter(input: &str, headers: &HeaderMap, data: &serde_json::Value) let expr = param.trim().split(' ').collect::>(); match expr.get(0) { - Some(&"header") => { - if let Some(field) = expr.get(1) { - match headers.get_one(field) { - Some(value) => Ok(value), - _ => bail!("Could not extract event parameter from header"), - } - } else { - bail!("Missing parameter for `header` expression"); - } - } - Some(pointer) => match data.pointer(pointer) { - Some(value) => match value.as_str() { - Some(value) => Ok(value), - _ => bail!("Could not convert value `{}` to string", value), - }, - _ => bail!("Could not convert field `{}` to string", param.trim()), - }, + Some(&"header") => get_header_field(headers, &expr), + Some(pointer) => get_value_from_pointer(data, &pointer), None => bail!("Missing expression in `{}`", input), } }, @@ -203,12 +200,13 @@ fn replace_parameter(input: &str, headers: &HeaderMap, data: &serde_json::Value) fn get_string(value: &serde_json::Value) -> Result { match &value { - serde_json::Value::Null => unimplemented!(), serde_json::Value::Bool(bool) => Ok(bool.to_string()), serde_json::Value::Number(number) => Ok(number.to_string()), serde_json::Value::String(string) => Ok(string.as_str().to_string()), - serde_json::Value::Array(_array) => unimplemented!(), - serde_json::Value::Object(_object) => unimplemented!(), + x => { + error!("Could not get string from: {:?}", x); + unimplemented!() + } } } @@ -228,15 +226,8 @@ fn filter_match( let parameter = parameter.trim(); if let Some(json_value) = data.pointer(parameter) { - *data.pointer_mut(parameter).unwrap() = match json_value { - serde_json::Value::Bool(bool) => serde_json::Value::String(bool.to_string()), - serde_json::Value::String(string) => serde_json::Value::String(string.to_string()), - serde_json::Value::Number(number) => serde_json::Value::String(number.to_string()), - x => { - error!("Could not get string from: {:?}", x); - unimplemented!() - } - } + *data.pointer_mut(parameter).unwrap() = + serde_json::Value::String(get_string(json_value)?); } } @@ -246,7 +237,7 @@ fn filter_match( if regex.is_match(&value) { debug!("Filter `{}` of hook `{}` matched", filter_name, hook_name); - return Ok(Some(replace_parameter( + return Ok(Some(replace_parameters( &hook.command, &request.headers(), data, @@ -272,47 +263,56 @@ fn execute_hooks(request: &Request, data: Data) -> Result let config = request.guard::>().unwrap(); // should never fail let mut valid = false; - let mut hooks = HashMap::new(); + let mut result = HashMap::new(); let client_ip = &request .client_ip() .unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)); - for (hook_name, hook) in &config.hooks { - if accept_ip(&hook_name, &client_ip, &hook.ip_filter) { - if let Some(signature) = request.headers().get_one(&hook.signature) { - for secret in &hook.secrets { - match validate_request(&secret, &signature, &buffer) { - Ok(()) => { - trace!("Valid signature found for hook `{}`", hook_name,); + let hooks = config + .hooks + .iter() + .filter(|(name, hook)| accept_ip(&name, &client_ip, &hook.ip_filter)); - valid = true; + for (hook_name, hook) in hooks { + let signature = request + .headers() + .get_one(&hook.signature) + .ok_or_else(|| WebhookeyError::InvalidSignature)?; - let mut data: serde_json::Value = - serde_json::from_slice(&buffer).map_err(WebhookeyError::Serde)?; + let secrets = hook + .secrets + .iter() + .map(|secret| validate_request(&secret, &signature, &buffer)); - for (filter_name, filter) in &hook.filters { - match filter_match( - &hook_name, - &hook, - &filter_name, - &filter, - &request, - &mut data, - ) { - Ok(Some(command)) => { - hooks.insert(hook_name.to_string(), command); - break; - } - Ok(None) => {} - Err(e) => error!("{}", e), - } + for secret in secrets { + match secret { + Ok(()) => { + trace!("Valid signature found for hook `{}`", hook_name); + + valid = true; + + let mut data: serde_json::Value = + serde_json::from_slice(&buffer).map_err(WebhookeyError::Serde)?; + + for (filter_name, filter) in &hook.filters { + match filter_match( + &hook_name, + &hook, + &filter_name, + &filter, + &request, + &mut data, + ) { + Ok(Some(command)) => { + result.insert(hook_name.to_string(), command); + break; } + Ok(None) => {} + Err(e) => error!("{}", e), } - Err(e) => trace!("Hook `{}` could not validate request: {}", &hook_name, e), } } - } else { - return Err(WebhookeyError::InvalidHeader); + Err(e) => trace!("Hook `{}` could not validate request: {}", &hook_name, e), } } } @@ -321,7 +321,7 @@ fn execute_hooks(request: &Request, data: Data) -> Result return Err(WebhookeyError::Unauthorized(*client_ip)); } - Ok(Hooks(hooks)) + Ok(Hooks { inner: result }) } impl FromDataSimple for Hooks { @@ -330,7 +330,7 @@ impl FromDataSimple for Hooks { fn from_data(request: &Request, data: Data) -> data::Outcome { match execute_hooks(&request, data) { Ok(hooks) => { - if hooks.0.is_empty() { + if hooks.inner.is_empty() { let client_ip = &request .client_ip() .unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)); @@ -353,26 +353,21 @@ impl FromDataSimple for Hooks { } } -#[get("/")] -fn index() -> &'static str { - "Hello, webhookey!" -} - #[post("/", format = "json", data = "")] fn receive_hook<'a>(address: SocketAddr, hooks: Hooks) -> Result> { info!("Post request received from: {}", address); - for hook in hooks.0 { - info!("Execute `{}` from hook `{}`", &hook.1, &hook.0); + for (name, command) in hooks.inner { + info!("Execute `{}` from hook `{}`", &command, &name); - match run_script::run(&hook.1, &vec![], &ScriptOptions::new()) { + match run_script::run(&command, &vec![], &ScriptOptions::new()) { Ok((status, stdout, stderr)) => { - info!("Command `{}` exited with return code: {}", &hook.1, status); - trace!("Output of command `{}` on stdout: {:?}", &hook.1, &stdout); - debug!("Output of command `{}` on stderr: {:?}", &hook.1, &stderr); + info!("Command `{}` exited with return code: {}", &command, status); + trace!("Output of command `{}` on stdout: {:?}", &command, &stdout); + debug!("Output of command `{}` on stderr: {:?}", &command, &stderr); } Err(e) => { - error!("Execution of `{}` failed: {}", &hook.1, e); + error!("Execution of `{}` failed: {}", &command, e); } } } @@ -381,12 +376,14 @@ fn receive_hook<'a>(address: SocketAddr, hooks: Hooks) -> Result> { } fn get_config() -> Result { + // Look for systemwide config.. if let Ok(config) = File::open("/etc/webhookey/config.yml") { info!("Loading configuration from `/etc/webhookey/config.yml`"); return Ok(config); } + // ..look for user path config.. if let Some(mut path) = dirs::config_dir() { path.push("webhookey/config.yml"); @@ -400,12 +397,14 @@ fn get_config() -> Result { } } + // ..look for config in CWD.. if let Ok(config) = File::open("config.yml") { info!("Loading configuration from `./config.yml`"); return Ok(config); } + // ..you had your chance. bail!("No configuration file found."); } @@ -417,7 +416,7 @@ fn main() -> Result<()> { trace!("Parsed configuration:\n{}", serde_yaml::to_string(&config)?); rocket::ignite() - .mount("/", routes![index, receive_hook]) + .mount("/", routes![receive_hook]) .attach(AdHoc::on_attach("webhookey config", move |rocket| { Ok(rocket.manage(config)) })) @@ -435,17 +434,6 @@ mod tests { }; use serde_json::json; - #[test] - fn index() { - let rocket = rocket::ignite().mount("/", routes![index]); - - let client = Client::new(rocket).unwrap(); - let mut response = client.get("/").dispatch(); - - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.body_string(), Some("Hello, webhookey!".into())); - } - #[test] fn secret() { let mut hooks = HashMap::new(); @@ -520,42 +508,42 @@ mod tests { map.add_raw("X-Gitea-Event", "something"); assert_eq!( - replace_parameter("command", &map, &serde_json::Value::Null).unwrap(), + replace_parameters("command", &map, &serde_json::Value::Null).unwrap(), "command" ); assert_eq!( - replace_parameter(" command", &map, &serde_json::Value::Null).unwrap(), + replace_parameters(" command", &map, &serde_json::Value::Null).unwrap(), " command" ); assert_eq!( - replace_parameter("command ", &map, &serde_json::Value::Null).unwrap(), + replace_parameters("command ", &map, &serde_json::Value::Null).unwrap(), "command " ); assert_eq!( - replace_parameter(" command ", &map, &serde_json::Value::Null).unwrap(), + replace_parameters(" command ", &map, &serde_json::Value::Null).unwrap(), " command " ); assert_eq!( - replace_parameter("command command ", &map, &serde_json::Value::Null).unwrap(), + replace_parameters("command command ", &map, &serde_json::Value::Null).unwrap(), "command command " ); assert_eq!( - replace_parameter("{{ /foo }} command", &map, &json!({ "foo": "bar" })).unwrap(), + replace_parameters("{{ /foo }} command", &map, &json!({ "foo": "bar" })).unwrap(), "bar command" ); assert_eq!( - replace_parameter(" command {{ /foo }} ", &map, &json!({ "foo": "bar" })).unwrap(), + replace_parameters(" command {{ /foo }} ", &map, &json!({ "foo": "bar" })).unwrap(), " command bar " ); assert_eq!( - replace_parameter( + replace_parameters( "{{ /foo }} command{{/field1/foo}}", &map, &json!({ "foo": "bar", "field1": { "foo": "baz" } }) @@ -565,12 +553,12 @@ mod tests { ); assert_eq!( - replace_parameter(" command {{ /foo }} ", &map, &json!({ "foo": "bar" })).unwrap(), + replace_parameters(" command {{ /foo }} ", &map, &json!({ "foo": "bar" })).unwrap(), " command bar " ); assert_eq!( - replace_parameter( + replace_parameters( " {{ /field1/foo }} command", &map, &json!({ "field1": { "foo": "bar" } }) @@ -580,7 +568,7 @@ mod tests { ); assert_eq!( - replace_parameter( + replace_parameters( " {{ header X-Gitea-Event }} command", &map, &json!({ "field1": { "foo": "bar" } })