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",
|
"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]]
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
@ -148,6 +148,6 @@ Each filter must have following fields:
|
||||||
## Configure rocket via config.yml
|
## Configure rocket via config.yml
|
||||||
## Security
|
## Security
|
||||||
### https support
|
### https support
|
||||||
basically supported, but related to "Configure rocket via config.yml".
|
basically supported, but related to "Configure rocket via config.yml".
|
||||||
### Authentication features
|
### Authentication features
|
||||||
## Use proptest or quickcheck for tests of parsers
|
## Use proptest or quickcheck for tests of parsers
|
||||||
|
|
170
src/main.rs
170
src/main.rs
|
@ -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,101 +230,94 @@ 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;
|
let mut buffer = Vec::new();
|
||||||
|
let size = data
|
||||||
|
.open()
|
||||||
|
.read_to_end(&mut buffer)
|
||||||
|
.map_err(WebhookeyError::Io)?;
|
||||||
|
info!("Data of size {} received", size);
|
||||||
|
|
||||||
fn from_data(request: &Request, data: Data) -> data::Outcome<Self, Self::Error> {
|
let config = request.guard::<State<Config>>().unwrap(); // should never fail
|
||||||
let mut buffer = Vec::new();
|
let mut valid = false;
|
||||||
match data.open().read_to_end(&mut buffer) {
|
let mut hooks = HashMap::new();
|
||||||
Ok(size) => info!("Data of size {} received", size),
|
let client_ip = &request
|
||||||
Err(e) => {
|
.client_ip()
|
||||||
error!("Could not read to end of data: {}", &e);
|
.unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED));
|
||||||
return Failure((
|
|
||||||
Status::BadRequest,
|
|
||||||
anyhow!("Could not read to end of data: {}", &e),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let config = request.guard::<State<Config>>().unwrap(); // should never fail
|
for (hook_name, hook) in &config.hooks {
|
||||||
let mut valid = false;
|
if accept_ip(&hook_name, &client_ip, &hook.ip_filter) {
|
||||||
let mut hooks = HashMap::new();
|
if let Some(signature) = request.headers().get_one(&hook.signature) {
|
||||||
let client_ip = &request
|
for secret in &hook.secrets {
|
||||||
.client_ip()
|
match validate_request(&secret, &signature, &buffer) {
|
||||||
.unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED));
|
Ok(()) => {
|
||||||
|
trace!("Valid signature found for hook `{}`", hook_name,);
|
||||||
|
|
||||||
for (hook_name, hook) in &config.hooks {
|
valid = true;
|
||||||
if accepted_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) {
|
|
||||||
Ok(()) => {
|
|
||||||
trace!("Valid signature found for hook `{}`", hook_name,);
|
|
||||||
|
|
||||||
valid = true;
|
let data: serde_json::Value =
|
||||||
|
serde_json::from_slice(&buffer).map_err(WebhookeyError::Serde)?;
|
||||||
|
|
||||||
let data: serde_json::Value = match serde_json::from_slice(&buffer)
|
for (filter_name, filter) in &hook.filters {
|
||||||
{
|
match filter_match(
|
||||||
Ok(data) => data,
|
&hook_name,
|
||||||
Err(e) => {
|
&hook,
|
||||||
error!("Could not parse json: {}", e);
|
&filter_name,
|
||||||
return Failure((
|
&filter,
|
||||||
Status::BadRequest,
|
&request,
|
||||||
anyhow!("Could not parse json: {}", e),
|
&data,
|
||||||
));
|
) {
|
||||||
}
|
Ok(Some(command)) => {
|
||||||
};
|
hooks.insert(hook_name.to_string(), command);
|
||||||
|
break;
|
||||||
for (filter_name, filter) in &hook.filters {
|
|
||||||
match filter_match(
|
|
||||||
&hook_name,
|
|
||||||
&hook,
|
|
||||||
&filter_name,
|
|
||||||
&filter,
|
|
||||||
&request,
|
|
||||||
&data,
|
|
||||||
) {
|
|
||||||
Ok(Some(command)) => {
|
|
||||||
hooks.insert(hook_name.to_string(), command);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Ok(None) => {}
|
|
||||||
Err(e) => error!("{}", e),
|
|
||||||
}
|
}
|
||||||
|
Ok(None) => {}
|
||||||
|
Err(e) => error!("{}", e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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"),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if hooks.is_empty() {
|
|
||||||
if valid {
|
|
||||||
warn!("Unmatched hook from {}", &client_ip);
|
|
||||||
return Failure((
|
|
||||||
Status::NotFound,
|
|
||||||
anyhow!("Unmatched hook from {}", &client_ip),
|
|
||||||
));
|
|
||||||
} else {
|
} else {
|
||||||
error!("Unauthorized request from {}", &client_ip);
|
return Err(WebhookeyError::InvalidHeader);
|
||||||
return Failure((
|
|
||||||
Status::Unauthorized,
|
|
||||||
anyhow!("Unauthorized request from {}", &client_ip),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Success(Hooks(hooks))
|
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, WebhookeyError::UnmatchedHook(*client_ip)));
|
||||||
|
}
|
||||||
|
|
||||||
|
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