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:
parent
8314214e06
commit
7f143e0b08
4 changed files with 113 additions and 83 deletions
21
Cargo.lock
generated
21
Cargo.lock
generated
|
@ -1009,6 +1009,26 @@ dependencies = [
|
|||
"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]]
|
||||
name = "time"
|
||||
version = "0.1.43"
|
||||
|
@ -1169,6 +1189,7 @@ dependencies = [
|
|||
"serde_json",
|
||||
"serde_yaml",
|
||||
"sha2",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -25,3 +25,4 @@ hmac = "0.10"
|
|||
sha2 = "0.9"
|
||||
hex = "0.4"
|
||||
ipnet = { version = "2.3", features = ["serde"] }
|
||||
thiserror = "1.0"
|
||||
|
|
|
@ -7,7 +7,7 @@ actions.
|
|||
## Build
|
||||
|
||||
### 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:
|
||||
``` sh
|
||||
|
|
106
src/main.rs
106
src/main.rs
|
@ -24,6 +24,7 @@ use rocket::{
|
|||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::Sha256;
|
||||
use thiserror::Error;
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
|
@ -71,10 +72,24 @@ struct Filter {
|
|||
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)]
|
||||
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 {
|
||||
Some(IpFilter::Allow(list)) => {
|
||||
for i in list {
|
||||
|
@ -215,21 +230,13 @@ fn filter_match(
|
|||
Ok(None)
|
||||
}
|
||||
|
||||
impl FromDataSimple for Hooks {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn from_data(request: &Request, data: Data) -> data::Outcome<Self, Self::Error> {
|
||||
fn execute_hooks(request: &Request, data: Data) -> Result<Hooks, WebhookeyError> {
|
||||
let mut buffer = Vec::new();
|
||||
match data.open().read_to_end(&mut buffer) {
|
||||
Ok(size) => info!("Data of size {} received", size),
|
||||
Err(e) => {
|
||||
error!("Could not read to end of data: {}", &e);
|
||||
return Failure((
|
||||
Status::BadRequest,
|
||||
anyhow!("Could not read to end of data: {}", &e),
|
||||
));
|
||||
}
|
||||
}
|
||||
let size = data
|
||||
.open()
|
||||
.read_to_end(&mut buffer)
|
||||
.map_err(WebhookeyError::Io)?;
|
||||
info!("Data of size {} received", size);
|
||||
|
||||
let config = request.guard::<State<Config>>().unwrap(); // should never fail
|
||||
let mut valid = false;
|
||||
|
@ -239,7 +246,7 @@ impl FromDataSimple for Hooks {
|
|||
.unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED));
|
||||
|
||||
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) {
|
||||
for secret in &hook.secrets {
|
||||
match validate_request(&secret, &signature, &buffer) {
|
||||
|
@ -248,17 +255,8 @@ impl FromDataSimple for Hooks {
|
|||
|
||||
valid = true;
|
||||
|
||||
let data: serde_json::Value = match serde_json::from_slice(&buffer)
|
||||
{
|
||||
Ok(data) => data,
|
||||
Err(e) => {
|
||||
error!("Could not parse json: {}", e);
|
||||
return Failure((
|
||||
Status::BadRequest,
|
||||
anyhow!("Could not parse json: {}", e),
|
||||
));
|
||||
}
|
||||
};
|
||||
let data: serde_json::Value =
|
||||
serde_json::from_slice(&buffer).map_err(WebhookeyError::Serde)?;
|
||||
|
||||
for (filter_name, filter) in &hook.filters {
|
||||
match filter_match(
|
||||
|
@ -278,38 +276,48 @@ impl FromDataSimple for Hooks {
|
|||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Hook `{}` could not validate request: {}", &hook_name, e);
|
||||
}
|
||||
Err(e) => trace!("Hook `{}` could not validate request: {}", &hook_name, e),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error!("Could not extract signature from header");
|
||||
return Failure((
|
||||
Status::BadRequest,
|
||||
anyhow!("Could not extract signature from header"),
|
||||
));
|
||||
return Err(WebhookeyError::InvalidHeader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
return Failure((
|
||||
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),
|
||||
));
|
||||
}
|
||||
return Failure((Status::NotFound, WebhookeyError::UnmatchedHook(*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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue