Split from_data() up in smaller pieces

To still be able to handle errors correctly, also regarding the http
status code, `thiserror::Error` is used.
This commit is contained in:
finga 2021-04-03 01:10:50 +02:00
parent 8314214e06
commit 7f143e0b08
4 changed files with 113 additions and 83 deletions

21
Cargo.lock generated
View file

@ -1009,6 +1009,26 @@ dependencies = [
"winapi-util", "winapi-util",
] ]
[[package]]
name = "thiserror"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0"
dependencies = [
"proc-macro2 1.0.26",
"quote 1.0.9",
"syn 1.0.68",
]
[[package]] [[package]]
name = "time" name = "time"
version = "0.1.43" version = "0.1.43"
@ -1169,6 +1189,7 @@ dependencies = [
"serde_json", "serde_json",
"serde_yaml", "serde_yaml",
"sha2", "sha2",
"thiserror",
] ]
[[package]] [[package]]

View file

@ -25,3 +25,4 @@ hmac = "0.10"
sha2 = "0.9" sha2 = "0.9"
hex = "0.4" hex = "0.4"
ipnet = { version = "2.3", features = ["serde"] } ipnet = { version = "2.3", features = ["serde"] }
thiserror = "1.0"

View file

@ -7,7 +7,7 @@ actions.
## Build ## Build
### Install Rust ### Install Rust
Install the Rust toolchain from [rustup.rs](https://rustup.rs) Install the Rust toolchain from [rustup.rs](https://rustup.rs).
Further, for Rocket we need to have the nightly toolchain installed: Further, for Rocket we need to have the nightly toolchain installed:
``` sh ``` sh

View file

@ -24,6 +24,7 @@ use rocket::{
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sha2::Sha256; use sha2::Sha256;
use thiserror::Error;
use std::{ use std::{
collections::HashMap, collections::HashMap,
@ -71,10 +72,24 @@ struct Filter {
regex: String, regex: String,
} }
#[derive(Debug, Error)]
enum WebhookeyError {
#[error("Could not extract signature from header")]
InvalidHeader,
#[error("Unauthorized request from `{0}`")]
Unauthorized(IpAddr),
#[error("Unmatched hook from `{0}`")]
UnmatchedHook(IpAddr),
#[error("IO Error")]
Io(std::io::Error),
#[error("Serde Error")]
Serde(serde_json::Error),
}
#[derive(Debug)] #[derive(Debug)]
struct Hooks(HashMap<String, String>); struct Hooks(HashMap<String, String>);
fn accepted_ip(hook_name: &str, client_ip: &IpAddr, ip_filter: &Option<IpFilter>) -> bool { fn accept_ip(hook_name: &str, client_ip: &IpAddr, ip_filter: &Option<IpFilter>) -> bool {
match ip_filter { match ip_filter {
Some(IpFilter::Allow(list)) => { Some(IpFilter::Allow(list)) => {
for i in list { for i in list {
@ -215,21 +230,13 @@ fn filter_match(
Ok(None) Ok(None)
} }
impl FromDataSimple for Hooks { fn execute_hooks(request: &Request, data: Data) -> Result<Hooks, WebhookeyError> {
type Error = anyhow::Error;
fn from_data(request: &Request, data: Data) -> data::Outcome<Self, Self::Error> {
let mut buffer = Vec::new(); let mut buffer = Vec::new();
match data.open().read_to_end(&mut buffer) { let size = data
Ok(size) => info!("Data of size {} received", size), .open()
Err(e) => { .read_to_end(&mut buffer)
error!("Could not read to end of data: {}", &e); .map_err(WebhookeyError::Io)?;
return Failure(( info!("Data of size {} received", size);
Status::BadRequest,
anyhow!("Could not read to end of data: {}", &e),
));
}
}
let config = request.guard::<State<Config>>().unwrap(); // should never fail let config = request.guard::<State<Config>>().unwrap(); // should never fail
let mut valid = false; let mut valid = false;
@ -239,7 +246,7 @@ impl FromDataSimple for Hooks {
.unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)); .unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED));
for (hook_name, hook) in &config.hooks { for (hook_name, hook) in &config.hooks {
if accepted_ip(&hook_name, &client_ip, &hook.ip_filter) { if accept_ip(&hook_name, &client_ip, &hook.ip_filter) {
if let Some(signature) = request.headers().get_one(&hook.signature) { if let Some(signature) = request.headers().get_one(&hook.signature) {
for secret in &hook.secrets { for secret in &hook.secrets {
match validate_request(&secret, &signature, &buffer) { match validate_request(&secret, &signature, &buffer) {
@ -248,17 +255,8 @@ impl FromDataSimple for Hooks {
valid = true; valid = true;
let data: serde_json::Value = match serde_json::from_slice(&buffer) let data: serde_json::Value =
{ serde_json::from_slice(&buffer).map_err(WebhookeyError::Serde)?;
Ok(data) => data,
Err(e) => {
error!("Could not parse json: {}", e);
return Failure((
Status::BadRequest,
anyhow!("Could not parse json: {}", e),
));
}
};
for (filter_name, filter) in &hook.filters { for (filter_name, filter) in &hook.filters {
match filter_match( match filter_match(
@ -278,38 +276,48 @@ impl FromDataSimple for Hooks {
} }
} }
} }
Err(e) => { Err(e) => trace!("Hook `{}` could not validate request: {}", &hook_name, e),
warn!("Hook `{}` could not validate request: {}", &hook_name, e);
}
} }
} }
} else { } else {
error!("Could not extract signature from header"); return Err(WebhookeyError::InvalidHeader);
return Failure((
Status::BadRequest,
anyhow!("Could not extract signature from header"),
));
} }
} }
} }
if hooks.is_empty() { if !valid {
if valid { return Err(WebhookeyError::Unauthorized(*client_ip));
}
Ok(Hooks(hooks))
}
impl FromDataSimple for Hooks {
type Error = WebhookeyError;
fn from_data(request: &Request, data: Data) -> data::Outcome<Self, Self::Error> {
match execute_hooks(&request, data) {
Ok(hooks) => {
if hooks.0.is_empty() {
let client_ip = &request
.client_ip()
.unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED));
warn!("Unmatched hook from {}", &client_ip); warn!("Unmatched hook from {}", &client_ip);
return Failure(( return Failure((Status::NotFound, WebhookeyError::UnmatchedHook(*client_ip)));
Status::NotFound,
anyhow!("Unmatched hook from {}", &client_ip),
));
} else {
error!("Unauthorized request from {}", &client_ip);
return Failure((
Status::Unauthorized,
anyhow!("Unauthorized request from {}", &client_ip),
));
}
} }
Success(Hooks(hooks)) Success(hooks)
}
Err(WebhookeyError::Unauthorized(e)) => {
error!("{}", WebhookeyError::Unauthorized(e));
Failure((Status::Unauthorized, WebhookeyError::Unauthorized(e)))
}
Err(e) => {
error!("{}", e);
Failure((Status::BadRequest, e))
}
}
} }
} }