Break up code into multiple files
In order to increase readability, maintainability and maybe a future independence regarding web frameworks move code to new files.
This commit is contained in:
parent
b2205ea5f4
commit
d92e8029f2
3 changed files with 191 additions and 175 deletions
159
src/webhooks.rs
Normal file
159
src/webhooks.rs
Normal file
|
@ -0,0 +1,159 @@
|
|||
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)?;
|
||||
// let value = self.get_string(data)?;
|
||||
|
||||
// if let Some(value) = self.get_string() {data.pointer(&self.pointer) {
|
||||
// let value = get_string();
|
||||
|
||||
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 (results, errors): (Vec<_>, Vec<_>) = filters
|
||||
.iter()
|
||||
.map(|filter| filter.evaluate(data))
|
||||
.partition(Result::is_ok);
|
||||
|
||||
if errors.is_empty() {
|
||||
Ok(results.iter().all(|r| *r.as_ref().unwrap())) // should never fail
|
||||
} else {
|
||||
errors.iter().for_each(|e| {
|
||||
error!("Could not evaluate Filter: {}", e.as_ref().unwrap_err())
|
||||
});
|
||||
|
||||
Err(WebhookeyError::InvalidFilter)
|
||||
}
|
||||
}
|
||||
FilterType::Or(filters) => {
|
||||
let (results, errors): (Vec<_>, Vec<_>) = filters
|
||||
.iter()
|
||||
.map(|filter| filter.evaluate(data))
|
||||
.partition(Result::is_ok);
|
||||
|
||||
if errors.is_empty() {
|
||||
Ok(results.iter().any(|r| *r.as_ref().unwrap())) // should never fail
|
||||
} else {
|
||||
errors.iter().for_each(|e| {
|
||||
error!("Could not evaluate Filter: {}", e.as_ref().unwrap_err())
|
||||
});
|
||||
|
||||
Err(WebhookeyError::InvalidFilter)
|
||||
}
|
||||
}
|
||||
// FilterType::HeaderFilter(filter) => todo!(),
|
||||
FilterType::JsonFilter(filter) => filter.evaluate(data),
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue