finga
b4b46ebd58
Clean up some comments and refactor the code to improve readability and to get rid of "ugly" unwraps.
166 lines
4.8 KiB
Rust
166 lines
4.8 KiB
Rust
use anyhow::Result;
|
|
use ipnet::IpNet;
|
|
use log::{debug, error, trace};
|
|
use regex::Regex;
|
|
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),
|
|
#[error("Regex Error")]
|
|
Regex(regex::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<AddrType>),
|
|
Deny(Vec<AddrType>),
|
|
}
|
|
|
|
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)]
|
|
#[serde(deny_unknown_fields)]
|
|
pub struct JsonFilter {
|
|
pub pointer: String,
|
|
pub regex: String,
|
|
}
|
|
|
|
impl JsonFilter {
|
|
pub fn evaluate(&self, data: &serde_json::Value) -> Result<bool, WebhookeyError> {
|
|
trace!(
|
|
"Matching `{}` on `{}` from received json",
|
|
&self.regex,
|
|
&self.pointer,
|
|
);
|
|
|
|
let regex = Regex::new(&self.regex).map_err(WebhookeyError::Regex)?;
|
|
|
|
if let Some(value) = data.pointer(&self.pointer) {
|
|
if regex.is_match(&self.get_string(&value)?) {
|
|
debug!("Regex `{}` for `{}` matches", &self.regex, &self.pointer);
|
|
|
|
return Ok(true);
|
|
}
|
|
}
|
|
|
|
debug!(
|
|
"Regex `{}` for `{}` does not match",
|
|
&self.regex, &self.pointer
|
|
);
|
|
|
|
Ok(false)
|
|
}
|
|
|
|
fn get_string(&self, data: &serde_json::Value) -> Result<String, WebhookeyError> {
|
|
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!()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, Serialize)]
|
|
#[serde(deny_unknown_fields, rename_all = "lowercase")]
|
|
pub enum FilterType {
|
|
And(Vec<FilterType>),
|
|
Or(Vec<FilterType>),
|
|
// #[serde(rename = "header")]
|
|
// HeaderFilter(HeaderFilter),
|
|
#[serde(rename = "json")]
|
|
JsonFilter(JsonFilter),
|
|
}
|
|
|
|
impl FilterType {
|
|
pub fn evaluate(&self, data: &serde_json::Value) -> Result<bool, WebhookeyError> {
|
|
match self {
|
|
FilterType::And(filters) => {
|
|
let (mut results, mut errors) = (Vec::new(), Vec::new());
|
|
|
|
filters
|
|
.iter()
|
|
.map(|filter| filter.evaluate(data))
|
|
.for_each(|item| match item {
|
|
Ok(o) => results.push(o),
|
|
Err(e) => errors.push(e),
|
|
});
|
|
|
|
if errors.is_empty() {
|
|
Ok(results.iter().all(|r| *r))
|
|
} else {
|
|
errors
|
|
.iter()
|
|
.for_each(|e| error!("Could not evaluate Filter: {}", e));
|
|
|
|
Err(WebhookeyError::InvalidFilter)
|
|
}
|
|
}
|
|
FilterType::Or(filters) => {
|
|
let (mut results, mut errors) = (Vec::new(), Vec::new());
|
|
|
|
filters
|
|
.iter()
|
|
.map(|filter| filter.evaluate(data))
|
|
.for_each(|item| match item {
|
|
Ok(o) => results.push(o),
|
|
Err(e) => errors.push(e),
|
|
});
|
|
|
|
if errors.is_empty() {
|
|
Ok(results.iter().any(|r| *r))
|
|
} else {
|
|
errors
|
|
.iter()
|
|
.for_each(|e| error!("Could not evaluate Filter: {}", e));
|
|
|
|
Err(WebhookeyError::InvalidFilter)
|
|
}
|
|
}
|
|
// FilterType::HeaderFilter(filter) => todo!(),
|
|
FilterType::JsonFilter(filter) => filter.evaluate(data),
|
|
}
|
|
}
|
|
}
|