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; #[derive(Debug, Error)] pub enum WebhookeyError { #[error("Could not extract signature from header")] InvalidSignature, #[error("Unauthorized request from `{0}`")] Unauthorized(IpAddr), #[error("Unmatched hook from `{0}`")] UnmatchedHook(IpAddr), #[error("Could not evaluate filter request")] InvalidFilter, #[error("IO Error")] Io(std::io::Error), #[error("Serde Error")] Serde(serde_json::Error), } #[derive(Debug, Deserialize, Serialize)] #[serde(deny_unknown_fields, untagged)] pub enum AddrType { IpAddr(IpAddr), IpNet(IpNet), } impl AddrType { pub 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")] pub enum IpFilter { Allow(Vec), Deny(Vec), } impl IpFilter { pub 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)] pub struct HeaderFilter { pub field: String, #[serde(with = "serde_regex")] pub regex: Regex, } impl HeaderFilter { pub fn evaluate(&self, headers: &HeaderMap) -> Result { trace!( "Matching `{}` on `{}` from received header", &self.regex, &self.field, ); if let Some(value) = headers.get_one(&self.field) { if self.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 { pub pointer: String, #[serde(with = "serde_regex")] pub regex: Regex, } impl JsonFilter { pub fn evaluate(&self, data: &serde_json::Value) -> Result { trace!( "Matching `{}` on `{}` from received json", &self.regex, &self.pointer, ); if let Some(value) = data.pointer(&self.pointer) { if self.regex.is_match(&get_string(value)?) { debug!("Regex `{}` for `{}` matches", &self.regex, &self.pointer); return Ok(true); } } debug!( "Regex `{}` for json field `{}` does not match", &self.regex, &self.pointer ); Ok(false) } } macro_rules! interrelate { ($request:expr, $data:expr, $filters:expr, $relation:ident) => {{ let (mut results, mut errors) = (Vec::new(), Vec::new()); $filters .iter() .map(|filter| filter.evaluate($request, $data)) .for_each(|item| match item { Ok(o) => results.push(o), Err(e) => errors.push(e), }); if errors.is_empty() { Ok(results.iter().$relation(|r| *r)) } else { errors .iter() .for_each(|e| error!("Could not evaluate Filter: {}", e)); Err(WebhookeyError::InvalidFilter) } }}; } #[derive(Debug, Deserialize, Serialize)] #[serde(deny_unknown_fields, rename_all = "lowercase")] 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, request: &Request, data: &serde_json::Value, ) -> Result { match self { FilterType::Not(filter) => Ok(!filter.evaluate(request, data)?), FilterType::And(filters) => interrelate!(request, data, filters, all), FilterType::Or(filters) => interrelate!(request, data, filters, any), FilterType::HeaderFilter(filter) => filter.evaluate(request.headers()), FilterType::JsonFilter(filter) => filter.evaluate(data), } } } pub fn get_string(data: &serde_json::Value) -> Result { match &data { 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()), x => { error!("Could not get string from: {:?}", x); unimplemented!() } } }