From 8c9d9e63f20edd543c1db5ee75ae59e05e49afff Mon Sep 17 00:00:00 2001 From: finga Date: Wed, 17 Nov 2021 15:13:12 +0100 Subject: [PATCH] Add `HeaderFilter` for filter based on the header This extends filtering to filter also on the received http header. --- src/main.rs | 12 ++++++------ src/webhooks.rs | 52 +++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 52 insertions(+), 12 deletions(-) diff --git a/src/main.rs b/src/main.rs index fbb8d9a..4cd5658 100644 --- a/src/main.rs +++ b/src/main.rs @@ -199,7 +199,7 @@ impl Hooks { let mut data: serde_json::Value = serde_json::from_slice(&buffer).map_err(WebhookeyError::Serde)?; - match hook.filter.evaluate(&data) { + match hook.filter.evaluate(request, &data) { Ok(true) => match hook.get_command(hook_name, request, &mut data) { Ok(command) => { info!("Filter for `{}` matched", &hook_name); @@ -483,7 +483,7 @@ async fn main() -> Result<()> { #[cfg(test)] mod tests { use super::*; - use crate::webhooks::{AddrType, JsonFilter}; + use crate::webhooks::{AddrType, HeaderFilter, JsonFilter}; use rocket::{ http::{ContentType, Header}, local::asynchronous::Client, @@ -850,8 +850,8 @@ hooks: - json: pointer: /ref regex: refs/heads/master - - json: - pointer: /after + - header: + field: X-Gitea-Signature regex: f6e5fe4fe37df76629112d55cc210718b6a55e7e"#, ) .unwrap(); @@ -889,8 +889,8 @@ hooks: pointer: "/ref".to_string(), regex: "refs/heads/master".to_string(), }), - FilterType::JsonFilter(JsonFilter { - pointer: "/after".to_string(), + FilterType::HeaderFilter(HeaderFilter { + field: "X-Gitea-Signature".to_string(), regex: "f6e5fe4fe37df76629112d55cc210718b6a55e7e".to_string(), }), ]), diff --git a/src/webhooks.rs b/src/webhooks.rs index 0c4ee15..9bef844 100644 --- a/src/webhooks.rs +++ b/src/webhooks.rs @@ -2,6 +2,7 @@ use anyhow::Result; use ipnet::IpNet; use log::{debug, error, trace}; use regex::Regex; +use rocket::{http::HeaderMap, Request}; use serde::{Deserialize, Serialize}; use std::net::IpAddr; use thiserror::Error; @@ -56,6 +57,39 @@ impl IpFilter { } } +#[derive(Debug, Deserialize, Serialize)] +pub struct HeaderFilter { + pub field: String, + pub regex: String, +} + +impl HeaderFilter { + pub fn evaluate(&self, headers: &HeaderMap) -> Result { + trace!( + "Matching `{}` on `{}` from received header", + &self.regex, + &self.field, + ); + + let regex = Regex::new(&self.regex).map_err(WebhookeyError::Regex)?; + + if let Some(value) = headers.get_one(&self.field) { + if regex.is_match(value) { + debug!("Regex `{}` for `{}` matches", &self.regex, &self.field); + + return Ok(true); + } + } + + debug!( + "Regex `{}` for header field `{}` does not match", + &self.regex, &self.field + ); + + Ok(false) + } +} + #[derive(Debug, Deserialize, Serialize)] #[serde(deny_unknown_fields)] pub struct JsonFilter { @@ -82,7 +116,7 @@ impl JsonFilter { } debug!( - "Regex `{}` for `{}` does not match", + "Regex `{}` for json field `{}` does not match", &self.regex, &self.pointer ); @@ -96,20 +130,26 @@ pub enum FilterType { Not(Box), And(Vec), Or(Vec), + #[serde(rename = "header")] + HeaderFilter(HeaderFilter), #[serde(rename = "json")] JsonFilter(JsonFilter), } impl FilterType { - pub fn evaluate(&self, data: &serde_json::Value) -> Result { + pub fn evaluate( + &self, + request: &Request, + data: &serde_json::Value, + ) -> Result { match self { - FilterType::Not(filter) => Ok(!filter.evaluate(data)?), + FilterType::Not(filter) => Ok(!filter.evaluate(request, data)?), FilterType::And(filters) => { let (mut results, mut errors) = (Vec::new(), Vec::new()); filters .iter() - .map(|filter| filter.evaluate(data)) + .map(|filter| filter.evaluate(request, data)) .for_each(|item| match item { Ok(o) => results.push(o), Err(e) => errors.push(e), @@ -130,7 +170,7 @@ impl FilterType { filters .iter() - .map(|filter| filter.evaluate(data)) + .map(|filter| filter.evaluate(request, data)) .for_each(|item| match item { Ok(o) => results.push(o), Err(e) => errors.push(e), @@ -146,7 +186,7 @@ impl FilterType { Err(WebhookeyError::InvalidFilter) } } - // FilterType::HeaderFilter(filter) => todo!(), + FilterType::HeaderFilter(filter) => filter.evaluate(request.headers()), FilterType::JsonFilter(filter) => filter.evaluate(data), } }