diff --git a/src/main.rs b/src/main.rs index 0834874..c985766 100644 --- a/src/main.rs +++ b/src/main.rs @@ -54,6 +54,14 @@ struct Filter { #[derive(Debug)] struct Hooks(HashMap>); +fn validate_request(secret: &str, signature: &str, data: &[u8]) -> Result<()> { + let mut mac = Hmac::::new_varkey(&secret.as_bytes()) + .map_err(|e| anyhow!("Could not create hasher with secret: {}", e))?; + mac.update(&data); + let raw_signature = hex::decode(signature.as_bytes())?; + mac.verify(&raw_signature).map_err(|e| anyhow!("{}", e)) +} + fn replace_parameter(input: &str, headers: &HeaderMap, data: &serde_json::Value) -> Result { let parse: IResult<&str, Vec<&str>> = many0(alt(( map_res( @@ -86,6 +94,47 @@ fn replace_parameter(input: &str, headers: &HeaderMap, data: &serde_json::Value) Ok(result.join("")) } +fn filter_match( + hook_name: &str, + hook: &Hook, + filter_name: &str, + filter: &Filter, + request: &Request, + data: &serde_json::Value, +) -> Result> { + trace!("Matching filter `{}` of hook `{}`", filter_name, hook_name); + + let regex = Regex::new(&filter.regex)?; + + if let Some(value) = data.pointer(&filter.pointer) { + if let Some(value) = value.as_str() { + if regex.is_match(value) { + debug!("Filter `{}` of hook `{}` matched", filter_name, hook_name); + + return Ok(Some(replace_parameter( + &hook.command.to_string(), + &request.headers(), + data, + )?)); + } + } else { + anyhow!( + "Could not parse pointer in hook `{}` from filter `{}`", + hook_name, + filter_name + ); + } + } + + trace!( + "Filter `{}` of hook `{}` did not match", + filter_name, + hook_name + ); + + Ok(None) +} + impl FromDataSimple for Hooks { type Error = anyhow::Error; @@ -117,102 +166,47 @@ impl FromDataSimple for Hooks { let mut commands = Vec::new(); for secret in &hook.secrets { - let mut mac = match Hmac::::new_varkey(&secret.as_bytes()) { - Ok(mac) => mac, - Err(e) => { - error!("Could not instantiate hasher: {}", e); - return Failure(( - Status::InternalServerError, - anyhow!("Could not instantiate hasher: {}", e), - )); - } - }; + match validate_request(&secret, &signature, &buffer) { + Ok(()) => { + trace!( + "Valid signature found for hook `{}`: {}", + hook_name, + signature + ); - mac.update(&buffer); + valid = true; - match &hex::decode(&signature.as_bytes()) { - Ok(raw_signature) => { - if mac.verify(&raw_signature) == Ok(()) { - trace!( - "Valid signature found for hook `{}`: {}", - hook_name, - signature - ); + let data: serde_json::Value = match serde_json::from_slice(&buffer) { + Ok(data) => data, + Err(e) => { + error!("Could not parse json: {}", e); + return Failure(( + Status::BadRequest, + anyhow!("Could not parse json: {}", e), + )); + } + }; - valid = true; - - let data: serde_json::Value = match serde_json::from_slice(&buffer) - { - Ok(data) => data, - Err(e) => { - error!("Could not parse json: {}", e); - return Failure(( - Status::BadRequest, - anyhow!("Could not parse json: {}", e), - )); - } - }; - - for (filter_name, filter) in &hook.filters { - trace!( - "Matching filter `{}` of hook `{}`", - filter_name, - hook_name - ); - - let regex = match Regex::new(&filter.regex) { - Ok(regex) => regex, - Err(e) => { - error!( - "Could not compile regex `{}`: {}", - &filter.regex, e - ); - continue; - } - }; - - if let Some(value) = data.pointer(&filter.pointer) { - if let Some(value) = value.as_str() { - if regex.is_match(value) { - debug!( - "Filter `{}` of hook `{}` matched", - filter_name, hook_name - ); - - match replace_parameter( - &hook.command.to_string(), - &request.headers(), - &data, - ) { - Ok(command) => commands.push(command), - Err(e) => error!( - "Could not replace all parameter in hook `{}`: {}", - hook_name, e - ), - } - } - } else { - anyhow!( - "Could not parse pointer in hook `{}` from filter `{}`", - hook_name, - filter_name - ); - } - } - - trace!( - "Filter `{}` of hook `{}` did not match", - filter_name, - hook_name - ); + for (filter_name, filter) in &hook.filters { + match filter_match( + &hook_name, + &hook, + &filter_name, + &filter, + &request, + &data, + ) { + Ok(Some(command)) => commands.push(command), + Ok(None) => {} + Err(e) => error!("{}", e), } } } Err(e) => { - error!("Invalid configuration: {}", e); + error!("Could not validate request: {}", e); return Failure(( - Status::InternalServerError, - anyhow!("Invalid configuration: {}", e), + Status::Unauthorized, + anyhow!("Could not validate request: {}", e), )); } } @@ -427,6 +421,15 @@ mod tests { .dispatch(); assert_eq!(response.status(), Status::BadRequest); + + let response = client + .post("/") + .header(Header::new("X-Gitea-Signature", "foobar")) + .header(ContentType::JSON) + .remote("127.0.0.1:8000".parse().unwrap()) + .dispatch(); + + assert_eq!(response.status(), Status::Unauthorized); } #[test]