Add HeaderFilter for filter based on the header

This extends filtering to filter also on the received http header.
This commit is contained in:
finga 2021-11-17 15:13:12 +01:00
parent 0d9c5f650f
commit 8c9d9e63f2
2 changed files with 52 additions and 12 deletions

View file

@ -199,7 +199,7 @@ impl Hooks {
let mut data: serde_json::Value = let mut data: serde_json::Value =
serde_json::from_slice(&buffer).map_err(WebhookeyError::Serde)?; 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(true) => match hook.get_command(hook_name, request, &mut data) {
Ok(command) => { Ok(command) => {
info!("Filter for `{}` matched", &hook_name); info!("Filter for `{}` matched", &hook_name);
@ -483,7 +483,7 @@ async fn main() -> Result<()> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::webhooks::{AddrType, JsonFilter}; use crate::webhooks::{AddrType, HeaderFilter, JsonFilter};
use rocket::{ use rocket::{
http::{ContentType, Header}, http::{ContentType, Header},
local::asynchronous::Client, local::asynchronous::Client,
@ -850,8 +850,8 @@ hooks:
- json: - json:
pointer: /ref pointer: /ref
regex: refs/heads/master regex: refs/heads/master
- json: - header:
pointer: /after field: X-Gitea-Signature
regex: f6e5fe4fe37df76629112d55cc210718b6a55e7e"#, regex: f6e5fe4fe37df76629112d55cc210718b6a55e7e"#,
) )
.unwrap(); .unwrap();
@ -889,8 +889,8 @@ hooks:
pointer: "/ref".to_string(), pointer: "/ref".to_string(),
regex: "refs/heads/master".to_string(), regex: "refs/heads/master".to_string(),
}), }),
FilterType::JsonFilter(JsonFilter { FilterType::HeaderFilter(HeaderFilter {
pointer: "/after".to_string(), field: "X-Gitea-Signature".to_string(),
regex: "f6e5fe4fe37df76629112d55cc210718b6a55e7e".to_string(), regex: "f6e5fe4fe37df76629112d55cc210718b6a55e7e".to_string(),
}), }),
]), ]),

View file

@ -2,6 +2,7 @@ use anyhow::Result;
use ipnet::IpNet; use ipnet::IpNet;
use log::{debug, error, trace}; use log::{debug, error, trace};
use regex::Regex; use regex::Regex;
use rocket::{http::HeaderMap, Request};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::net::IpAddr; use std::net::IpAddr;
use thiserror::Error; 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<bool, WebhookeyError> {
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)] #[derive(Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct JsonFilter { pub struct JsonFilter {
@ -82,7 +116,7 @@ impl JsonFilter {
} }
debug!( debug!(
"Regex `{}` for `{}` does not match", "Regex `{}` for json field `{}` does not match",
&self.regex, &self.pointer &self.regex, &self.pointer
); );
@ -96,20 +130,26 @@ pub enum FilterType {
Not(Box<FilterType>), Not(Box<FilterType>),
And(Vec<FilterType>), And(Vec<FilterType>),
Or(Vec<FilterType>), Or(Vec<FilterType>),
#[serde(rename = "header")]
HeaderFilter(HeaderFilter),
#[serde(rename = "json")] #[serde(rename = "json")]
JsonFilter(JsonFilter), JsonFilter(JsonFilter),
} }
impl FilterType { impl FilterType {
pub fn evaluate(&self, data: &serde_json::Value) -> Result<bool, WebhookeyError> { pub fn evaluate(
&self,
request: &Request,
data: &serde_json::Value,
) -> Result<bool, WebhookeyError> {
match self { match self {
FilterType::Not(filter) => Ok(!filter.evaluate(data)?), FilterType::Not(filter) => Ok(!filter.evaluate(request, data)?),
FilterType::And(filters) => { FilterType::And(filters) => {
let (mut results, mut errors) = (Vec::new(), Vec::new()); let (mut results, mut errors) = (Vec::new(), Vec::new());
filters filters
.iter() .iter()
.map(|filter| filter.evaluate(data)) .map(|filter| filter.evaluate(request, data))
.for_each(|item| match item { .for_each(|item| match item {
Ok(o) => results.push(o), Ok(o) => results.push(o),
Err(e) => errors.push(e), Err(e) => errors.push(e),
@ -130,7 +170,7 @@ impl FilterType {
filters filters
.iter() .iter()
.map(|filter| filter.evaluate(data)) .map(|filter| filter.evaluate(request, data))
.for_each(|item| match item { .for_each(|item| match item {
Ok(o) => results.push(o), Ok(o) => results.push(o),
Err(e) => errors.push(e), Err(e) => errors.push(e),
@ -146,7 +186,7 @@ impl FilterType {
Err(WebhookeyError::InvalidFilter) Err(WebhookeyError::InvalidFilter)
} }
} }
// FilterType::HeaderFilter(filter) => todo!(), FilterType::HeaderFilter(filter) => filter.evaluate(request.headers()),
FilterType::JsonFilter(filter) => filter.evaluate(data), FilterType::JsonFilter(filter) => filter.evaluate(data),
} }
} }