use std::collections::hash_map::HashMap;
use std::fs::File;
use std::io;
use std::path::PathBuf;
use chrono::Utc;
use hex::FromHex;
use http::HeaderMap;
use log::{debug, info};
use rand::Rng;
use ring::hmac;
use serde::Deserialize;
use serde_json::{json, Value};
use thiserror::Error;
#[derive(Deserialize, Debug, Clone)]
struct Filter {
kind: String,
#[serde(default)]
use_header_value: bool,
#[serde(default)]
have_keys: Vec<String>,
#[serde(default)]
items: HashMap<String, Value>,
header_value: Option<String>,
}
impl Filter {
fn is_match(&self, header: &Option<&[u8]>, object: &Value) -> bool {
if let (&Some(actual), Some(expected)) = (header, &self.header_value) {
return actual == expected.as_bytes();
}
for key in &self.have_keys {
if object.pointer(key).is_none() {
return false;
}
}
for (pointer, expected) in &self.items {
let matches = object.pointer(pointer).map(|value| value == expected);
if !matches.unwrap_or(false) {
return false;
}
}
true
}
}
#[derive(Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
pub enum Algorithm {
#[serde(rename = "sha1")]
Sha1,
#[serde(rename = "sha256")]
Sha256,
}
impl Algorithm {
fn mac_with(self, key: &[u8]) -> hmac::Key {
let algo = match self {
Algorithm::Sha1 => hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY,
Algorithm::Sha256 => hmac::HMAC_SHA256,
};
hmac::Key::new(algo, key)
}
}
#[derive(Deserialize, Debug, Clone)]
#[serde(tag = "type")]
pub enum Comparison {
#[serde(rename = "hmac")]
Hmac {
algorithm: Algorithm,
prefix: String,
},
#[serde(rename = "token")]
Token,
}
impl Comparison {
fn verify(&self, given: &[u8], secret: &str, data: &[u8]) -> bool {
match *self {
Comparison::Token => given == secret.as_bytes(),
Comparison::Hmac {
ref algorithm,
ref prefix,
} => {
if given.starts_with(prefix.as_bytes()) {
if let Ok(digest) = Vec::<u8>::from_hex(&given[prefix.len()..]) {
let mac = algorithm.mac_with(secret.as_ref());
hmac::verify(&mac, data, &digest)
.map(|()| true)
.unwrap_or(false)
} else {
false
}
} else {
false
}
},
}
}
}
#[derive(Deserialize, Debug, Clone)]
pub struct Verification {
secret_key_lookup: String,
verification_header: String,
compare: Comparison,
}
impl Verification {
fn verify(&self, headers: &HeaderMap, secret: &str, data: &[u8]) -> bool {
headers
.get(&self.verification_header)
.map_or(false, |value| {
self.compare.verify(value.as_bytes(), secret, data)
})
}
}
#[derive(Debug, Error)]
pub enum HandlerError {
#[error("create file error: {}", source)]
Create {
filepath: PathBuf,
#[source]
source: io::Error,
},
#[error("create file error: {}", source)]
Write {
#[source]
source: serde_json::Error,
},
}
#[derive(Deserialize, Debug, Clone)]
pub struct Handler {
path: String,
filters: Vec<Filter>,
header_name: Option<String>,
verification: Option<Verification>,
}
impl Handler {
pub fn lookup_secret<'a>(&self, secrets: &'a Value, object: &Value) -> Option<&'a str> {
self.verification.as_ref().and_then(|verification| {
let secret_key = if verification.secret_key_lookup.starts_with('/') {
object.pointer(&verification.secret_key_lookup).map(|key| {
key.as_str().map(Into::into).unwrap_or_else(|| {
serde_json::to_string(key).expect("JSON values should be serializable")
})
})
} else {
Some(verification.secret_key_lookup.clone())
};
secret_key
.and_then(|key| secrets.get(key))
.and_then(Value::as_str)
})
}
pub fn verify(&self, headers: &HeaderMap, secret: Option<&str>, data: &[u8]) -> bool {
if let Some(ref verification) = self.verification {
secret.map_or(false, |secret_value| {
verification.verify(headers, secret_value, data)
})
} else {
true
}
}
pub fn kind(&self, headers: &HeaderMap, object: &Value) -> Option<String> {
let header = self
.header_name
.as_ref()
.and_then(|name| headers.get(name).map(|value| value.as_bytes()));
for filter in &self.filters {
if filter.is_match(&header, object) {
info!("matched an event of kind {}", filter.kind);
if filter.use_header_value {
if let Some(value) = header {
let value_str = String::from_utf8_lossy(value);
return Some(filter.kind.replace("HEADER", &value_str));
}
}
return Some(filter.kind.clone());
}
}
None
}
pub fn write_object(&self, kind: &str, object: Value) -> Result<PathBuf, HandlerError> {
let rndpart = rand::thread_rng()
.sample_iter(&rand::distributions::Alphanumeric)
.map(char::from)
.take(12)
.collect::<String>();
let filename = format!("{}-{}.json", Utc::now().to_rfc3339(), rndpart);
let mut filepath = PathBuf::from(&self.path);
filepath.push(filename);
debug!(
"writing an event of kind {} to {}",
kind,
filepath.display(),
);
let mut fout = File::create(&filepath).map_err(|source| {
HandlerError::Create {
filepath: filepath.clone(),
source,
}
})?;
let output = json!({
"kind": kind,
"data": object,
});
serde_json::to_writer(&mut fout, &output).map_err(|source| {
HandlerError::Write {
source,
}
})?;
Ok(filepath)
}
}
#[cfg(test)]
mod test {
use std::collections::hash_map::HashMap;
use std::fs;
use std::path::Path;
use http::{HeaderMap, HeaderValue};
use serde_json::{json, Value};
use tempfile::TempDir;
use crate::handler::{Algorithm, Comparison, Filter, Handler, HandlerError, Verification};
use crate::test_utils;
const WEBHOOK_HEADER_NAME: &str = "X-Webhook-Listen-Kind";
const WEBHOOK_HEADER_NAME_LOWER: &str = "x-webhook-listen-kind";
const VERIFICATION_HEADER: &str = "X-Webhook-Listen-Verification";
const VERIFICATION_HEADER_LOWER: &str = "x-webhook-listen-verification";
fn create_handler<'a, I>(filters: I) -> Handler
where
I: IntoIterator<Item = &'a Filter>,
{
Handler {
path: String::new(),
filters: filters.into_iter().cloned().collect(),
header_name: Some(WEBHOOK_HEADER_NAME.into()),
verification: None,
}
}
fn static_value(value: &'static str) -> HeaderValue {
HeaderValue::from_static(value)
}
#[test]
fn test_empty_filter() {
let handler = create_handler(&[Filter {
kind: "empty".into(),
use_header_value: false,
have_keys: Vec::new(),
items: HashMap::new(),
header_value: None,
}]);
let empty = json!({});
let array = json!({
"array": [0],
});
let array_non_array = json!({
"array": 0,
});
let with_slash = json!({
"with/slash": 0,
});
let with_tilde = json!({
"with~tilde": 0,
});
let string_keys = json!({
"dict": {
"0": 0,
},
});
let string_keys_missing = json!({
"dict": {
"1": 0,
},
});
let many_keys = json!({
"first_key": 0,
"second_key": 0,
});
let many_keys_missing = json!({
"first_key": 0,
});
let all = json!({
"array": [0],
"with/slash": 0,
"with~tilde": 0,
"dict": {
"0": 0,
},
"first_key": 0,
"second_key": 0,
});
let headers = HeaderMap::new();
assert_eq!(handler.kind(&headers, &empty), Some("empty".into()));
assert_eq!(handler.kind(&headers, &array), Some("empty".into()));
assert_eq!(
handler.kind(&headers, &array_non_array),
Some("empty".into()),
);
assert_eq!(handler.kind(&headers, &with_slash), Some("empty".into()));
assert_eq!(handler.kind(&headers, &with_tilde), Some("empty".into()));
assert_eq!(handler.kind(&headers, &string_keys), Some("empty".into()));
assert_eq!(
handler.kind(&headers, &string_keys_missing),
Some("empty".into()),
);
assert_eq!(handler.kind(&headers, &many_keys), Some("empty".into()));
assert_eq!(
handler.kind(&headers, &many_keys_missing),
Some("empty".into()),
);
assert_eq!(handler.kind(&headers, &all), Some("empty".into()));
}
#[test]
fn test_have_keys() {
let handler = create_handler(&[
Filter {
kind: "array".into(),
use_header_value: false,
have_keys: vec!["/array/0".into()],
items: HashMap::new(),
header_value: None,
},
Filter {
kind: "with_slash".into(),
use_header_value: false,
have_keys: vec!["/with~1slash".into()],
items: HashMap::new(),
header_value: None,
},
Filter {
kind: "with_tilde".into(),
use_header_value: false,
have_keys: vec!["/with~0tilde".into()],
items: HashMap::new(),
header_value: None,
},
Filter {
kind: "string_keys".into(),
use_header_value: false,
have_keys: vec!["/dict/0".into()],
items: HashMap::new(),
header_value: None,
},
Filter {
kind: "many_keys".into(),
use_header_value: false,
have_keys: vec!["/first_key".into(), "/second_key".into()],
items: HashMap::new(),
header_value: None,
},
]);
let empty = json!({});
let array = json!({
"array": [0],
});
let array_non_array = json!({
"array": 0,
});
let with_slash = json!({
"with/slash": 0,
});
let with_tilde = json!({
"with~tilde": 0,
});
let string_keys = json!({
"dict": {
"0": 0,
},
});
let string_keys_missing = json!({
"dict": {
"1": 0,
},
});
let many_keys = json!({
"first_key": 0,
"second_key": 0,
});
let many_keys_missing = json!({
"first_key": 0,
});
let all = json!({
"array": [0],
"with/slash": 0,
"with~tilde": 0,
"dict": {
"0": 0,
},
"first_key": 0,
"second_key": 0,
});
let headers = HeaderMap::new();
assert_eq!(handler.kind(&headers, &empty), None);
assert_eq!(handler.kind(&headers, &array), Some("array".into()));
assert_eq!(handler.kind(&headers, &array_non_array), None);
assert_eq!(
handler.kind(&headers, &with_slash),
Some("with_slash".into()),
);
assert_eq!(
handler.kind(&headers, &with_tilde),
Some("with_tilde".into()),
);
assert_eq!(
handler.kind(&headers, &string_keys),
Some("string_keys".into()),
);
assert_eq!(handler.kind(&headers, &string_keys_missing), None);
assert_eq!(handler.kind(&headers, &many_keys), Some("many_keys".into()));
assert_eq!(handler.kind(&headers, &many_keys_missing), None);
assert_eq!(handler.kind(&headers, &all), Some("array".into()));
}
#[test]
fn test_items() {
let handler = create_handler(&[
Filter {
kind: "multi".into(),
use_header_value: false,
have_keys: Vec::new(),
items: [("/number".into(), json!(1)), ("/null".into(), Value::Null)]
.iter()
.cloned()
.collect(),
header_value: None,
},
Filter {
kind: "null".into(),
use_header_value: false,
have_keys: Vec::new(),
items: [("/null".into(), Value::Null)].iter().cloned().collect(),
header_value: None,
},
Filter {
kind: "number".into(),
use_header_value: false,
have_keys: Vec::new(),
items: [("/number".into(), json!(0))].iter().cloned().collect(),
header_value: None,
},
Filter {
kind: "string".into(),
use_header_value: false,
have_keys: Vec::new(),
items: [("/string".into(), json!("string"))]
.iter()
.cloned()
.collect(),
header_value: None,
},
Filter {
kind: "array".into(),
use_header_value: false,
have_keys: Vec::new(),
items: [("/array".into(), json!([0]))].iter().cloned().collect(),
header_value: None,
},
Filter {
kind: "dict".into(),
use_header_value: false,
have_keys: Vec::new(),
items: [("/dict".into(), json!({"0": 0}))]
.iter()
.cloned()
.collect(),
header_value: None,
},
]);
let empty = json!({});
let null = json!({
"null": Value::Null,
});
let null_mismatch = json!({
"null": 0,
});
let number = json!({
"number": 0,
});
let number_mismatch = json!({
"number": 1,
});
let string = json!({
"string": "string",
});
let string_mismatch = json!({
"string": "mismatch",
});
let array = json!({
"array": [0],
});
let array_mismatch = json!({
"array": 0,
});
let dict = json!({
"dict": {
"0": 0,
},
});
let dict_mismatch = json!({
"dict": {
"1": 0,
},
});
let multi = json!({
"null": Value::Null,
"number": 1,
});
let multi_missing = json!({
"number": 1,
});
let headers = HeaderMap::new();
assert_eq!(handler.kind(&headers, &empty), None);
assert_eq!(handler.kind(&headers, &null), Some("null".into()));
assert_eq!(handler.kind(&headers, &null_mismatch), None);
assert_eq!(handler.kind(&headers, &number), Some("number".into()));
assert_eq!(handler.kind(&headers, &number_mismatch), None);
assert_eq!(handler.kind(&headers, &string), Some("string".into()));
assert_eq!(handler.kind(&headers, &string_mismatch), None);
assert_eq!(handler.kind(&headers, &array), Some("array".into()));
assert_eq!(handler.kind(&headers, &array_mismatch), None);
assert_eq!(handler.kind(&headers, &dict), Some("dict".into()));
assert_eq!(handler.kind(&headers, &dict_mismatch), None);
assert_eq!(handler.kind(&headers, &multi), Some("multi".into()));
assert_eq!(handler.kind(&headers, &multi_missing), None);
}
#[test]
fn test_have_keys_and_items() {
let handler = create_handler(&[Filter {
kind: "match".into(),
use_header_value: false,
have_keys: vec!["/array".into()],
items: [("/null".into(), Value::Null)].iter().cloned().collect(),
header_value: None,
}]);
let empty = json!({});
let has_keys = json!({
"array": 0,
});
let has_item = json!({
"null": Value::Null,
});
let has_everything = json!({
"array": 0,
"null": Value::Null,
});
let has_mismatch = json!({
"array": 0,
"null": 0,
});
let headers = HeaderMap::new();
assert_eq!(handler.kind(&headers, &empty), None);
assert_eq!(handler.kind(&headers, &has_keys), None);
assert_eq!(handler.kind(&headers, &has_item), None);
assert_eq!(
handler.kind(&headers, &has_everything),
Some("match".into()),
);
assert_eq!(handler.kind(&headers, &has_mismatch), None);
}
#[test]
fn test_headers() {
let handler = create_handler(&[
Filter {
kind: "also_keys".into(),
use_header_value: false,
have_keys: vec!["/array".into()],
items: HashMap::new(),
header_value: Some("also_keys".into()),
},
Filter {
kind: "multi".into(),
use_header_value: false,
have_keys: Vec::new(),
items: HashMap::new(),
header_value: Some("multi".into()),
},
]);
let empty = json!({});
let array = json!({
"array": [0],
});
let mut headers = HeaderMap::new();
assert_eq!(handler.kind(&headers, &empty), Some("multi".into()));
assert_eq!(handler.kind(&headers, &array), Some("also_keys".into()));
headers.insert(WEBHOOK_HEADER_NAME, static_value("multi"));
assert_eq!(handler.kind(&headers, &empty), Some("multi".into()));
assert_eq!(handler.kind(&headers, &array), Some("multi".into()));
headers.insert(WEBHOOK_HEADER_NAME, static_value("also_keys"));
assert_eq!(handler.kind(&headers, &empty), Some("also_keys".into()));
assert_eq!(handler.kind(&headers, &array), Some("also_keys".into()));
headers.insert(WEBHOOK_HEADER_NAME_LOWER, static_value("also_keys"));
assert_eq!(handler.kind(&headers, &empty), Some("also_keys".into()));
assert_eq!(handler.kind(&headers, &array), Some("also_keys".into()));
headers.insert(WEBHOOK_HEADER_NAME, static_value("unmatched"));
assert_eq!(handler.kind(&headers, &empty), None);
assert_eq!(handler.kind(&headers, &array), None);
}
#[test]
fn test_headers_replaced() {
let handler = create_handler(&[
Filter {
kind: "ignore".into(),
use_header_value: true,
have_keys: vec!["/have_keys".into()],
items: HashMap::new(),
header_value: Some("ignore".into()),
},
Filter {
kind: "unreplaced".into(),
use_header_value: true,
have_keys: vec!["/have_keys".into()],
items: HashMap::new(),
header_value: Some("unreplaced".into()),
},
Filter {
kind: "unused:HEADER".into(),
use_header_value: false,
have_keys: vec!["/have_keys".into()],
items: HashMap::new(),
header_value: Some("unused".into()),
},
Filter {
kind: "catchall:HEADER".into(),
use_header_value: true,
have_keys: Vec::new(),
items: HashMap::new(),
header_value: None,
},
]);
let empty = json!({});
let data = json!({
"have_keys": false,
});
let mut headers = HeaderMap::new();
assert_eq!(
handler.kind(&headers, &empty),
Some("catchall:HEADER".into()),
);
assert_eq!(handler.kind(&headers, &data), Some("ignore".into()));
headers.insert(WEBHOOK_HEADER_NAME, static_value("ignore"));
assert_eq!(handler.kind(&headers, &data), Some("ignore".into()));
headers.insert(WEBHOOK_HEADER_NAME, static_value("unreplaced"));
assert_eq!(handler.kind(&headers, &data), Some("unreplaced".into()));
headers.insert(WEBHOOK_HEADER_NAME, static_value("unused"));
assert_eq!(handler.kind(&headers, &data), Some("unused:HEADER".into()));
headers.insert(WEBHOOK_HEADER_NAME, static_value("other"));
assert_eq!(handler.kind(&headers, &data), Some("catchall:other".into()));
}
#[test]
fn test_verification_lookup_secret() {
let mut handler = create_handler(&[]);
let no_secrets = json!({});
let with_secret = json!({
"key": "secret",
"123": "secret with int",
"literal": "direct lookup",
});
let data = json!({
"secret_key": "key",
});
let data_non_str = json!({
"secret_key": 123,
});
assert_eq!(handler.lookup_secret(&no_secrets, &data), None);
assert_eq!(handler.lookup_secret(&no_secrets, &data_non_str), None);
assert_eq!(handler.lookup_secret(&with_secret, &data), None);
assert_eq!(handler.lookup_secret(&with_secret, &data_non_str), None);
handler.verification = Some(Verification {
secret_key_lookup: "/secret_key".into(),
verification_header: VERIFICATION_HEADER.into(),
compare: Comparison::Token,
});
assert_eq!(handler.lookup_secret(&no_secrets, &data), None);
assert_eq!(handler.lookup_secret(&no_secrets, &data_non_str), None);
assert_eq!(handler.lookup_secret(&with_secret, &data), Some("secret"));
assert_eq!(
handler.lookup_secret(&with_secret, &data_non_str),
Some("secret with int"),
);
handler.verification = Some(Verification {
secret_key_lookup: "literal".into(),
verification_header: VERIFICATION_HEADER.into(),
compare: Comparison::Token,
});
assert_eq!(handler.lookup_secret(&no_secrets, &data), None);
assert_eq!(handler.lookup_secret(&no_secrets, &data_non_str), None);
assert_eq!(
handler.lookup_secret(&with_secret, &data),
Some("direct lookup"),
);
assert_eq!(
handler.lookup_secret(&with_secret, &data_non_str),
Some("direct lookup"),
);
}
#[test]
fn test_verification_no_header() {
let mut handler = create_handler(&[]);
handler.verification = Some(Verification {
secret_key_lookup: "/secret".into(),
verification_header: VERIFICATION_HEADER.into(),
compare: Comparison::Token,
});
let data = Vec::new();
let headers = HeaderMap::new();
assert!(!handler.verify(&headers, None, &data));
assert!(!handler.verify(&headers, Some("secret"), &data));
}
#[test]
fn test_no_verification() {
let handler = create_handler(&[]);
let data = Vec::new();
let mut headers = HeaderMap::new();
headers.insert(VERIFICATION_HEADER, static_value("secret"));
assert!(handler.verify(&headers, None, &data));
assert!(handler.verify(&headers, Some("secret"), &data));
assert!(handler.verify(&headers, Some("wrong_secret"), &data));
}
#[test]
fn test_verification_token() {
let mut handler = create_handler(&[]);
handler.verification = Some(Verification {
secret_key_lookup: "/secret".into(),
verification_header: VERIFICATION_HEADER.into(),
compare: Comparison::Token,
});
let data = Vec::new();
let mut headers = HeaderMap::new();
headers.insert(VERIFICATION_HEADER, static_value("secret"));
assert!(!handler.verify(&headers, None, &data));
assert!(handler.verify(&headers, Some("secret"), &data));
}
#[test]
fn test_verification_token_icase() {
let mut handler = create_handler(&[]);
handler.verification = Some(Verification {
secret_key_lookup: "/secret".into(),
verification_header: VERIFICATION_HEADER.into(),
compare: Comparison::Token,
});
let data = Vec::new();
let mut headers = HeaderMap::new();
headers.insert(VERIFICATION_HEADER_LOWER, static_value("secret"));
assert!(!handler.verify(&headers, None, &data));
assert!(handler.verify(&headers, Some("secret"), &data));
}
#[test]
fn test_verification_hmac_sha1() {
let mut handler = create_handler(&[]);
handler.verification = Some(Verification {
secret_key_lookup: "/secret".into(),
verification_header: VERIFICATION_HEADER.into(),
compare: Comparison::Hmac {
algorithm: Algorithm::Sha1,
prefix: String::new(),
},
});
let data = Vec::new();
let mut headers = HeaderMap::new();
headers.insert(
VERIFICATION_HEADER,
static_value("25af6174a0fcecc4d346680a72b7ce644b9a88e8"),
);
assert!(!handler.verify(&headers, None, &data));
assert!(handler.verify(&headers, Some("secret"), &data));
assert!(!handler.verify(&headers, Some("wrong_secret"), &data));
}
#[test]
fn test_verification_hmac_sha256() {
let mut handler = create_handler(&[]);
handler.verification = Some(Verification {
secret_key_lookup: "/secret".into(),
verification_header: VERIFICATION_HEADER.into(),
compare: Comparison::Hmac {
algorithm: Algorithm::Sha256,
prefix: String::new(),
},
});
let data = Vec::new();
let mut headers = HeaderMap::new();
headers.insert(
VERIFICATION_HEADER,
static_value("f9e66e179b6747ae54108f82f8ade8b3c25d76fd30afde6c395822c530196169"),
);
assert!(!handler.verify(&headers, None, &data));
assert!(handler.verify(&headers, Some("secret"), &data));
assert!(!handler.verify(&headers, Some("wrong_secret"), &data));
}
#[test]
fn test_verification_hmac_prefix() {
let mut handler = create_handler(&[]);
handler.verification = Some(Verification {
secret_key_lookup: "/secret".into(),
verification_header: VERIFICATION_HEADER.into(),
compare: Comparison::Hmac {
algorithm: Algorithm::Sha1,
prefix: "sha1=".into(),
},
});
let data = Vec::new();
let mut headers = HeaderMap::new();
headers.insert(
VERIFICATION_HEADER,
static_value("sha1=25af6174a0fcecc4d346680a72b7ce644b9a88e8"),
);
assert!(!handler.verify(&headers, None, &data));
assert!(handler.verify(&headers, Some("secret"), &data));
assert!(!handler.verify(&headers, Some("wrong_secret"), &data));
}
#[test]
fn test_verification_hmac_not_hex() {
let mut handler = create_handler(&[]);
handler.verification = Some(Verification {
secret_key_lookup: "/secret".into(),
verification_header: VERIFICATION_HEADER.into(),
compare: Comparison::Hmac {
algorithm: Algorithm::Sha1,
prefix: "sha1=".into(),
},
});
let data = Vec::new();
let mut headers = HeaderMap::new();
headers.insert(
VERIFICATION_HEADER,
static_value("sha1=notvalidhexadecimaldigitsequenceofdigits"),
);
assert!(!handler.verify(&headers, None, &data));
assert!(!handler.verify(&headers, Some("secret"), &data));
assert!(!handler.verify(&headers, Some("wrong_secret"), &data));
}
#[test]
fn test_verification_hmac_wrong_prefix() {
let mut handler = create_handler(&[]);
handler.verification = Some(Verification {
secret_key_lookup: "/secret".into(),
verification_header: VERIFICATION_HEADER.into(),
compare: Comparison::Hmac {
algorithm: Algorithm::Sha1,
prefix: "sha12nope=".into(),
},
});
let data = Vec::new();
let mut headers = HeaderMap::new();
headers.insert(
VERIFICATION_HEADER,
static_value("sha1=25af6174a0fcecc4d346680a72b7ce644b9a88e8"),
);
assert!(!handler.verify(&headers, None, &data));
assert!(!handler.verify(&headers, Some("secret"), &data));
assert!(!handler.verify(&headers, Some("wrong_secret"), &data));
}
fn create_handler_with_dir() -> (Handler, TempDir) {
let tempdir = test_utils::create_tempdir();
let handler = Handler {
path: tempdir
.path()
.to_str()
.expect("test path to be valid UTF-8")
.into(),
filters: Vec::new(),
header_name: Some(WEBHOOK_HEADER_NAME.into()),
verification: None,
};
(handler, tempdir)
}
fn check_written_object(path: &Path, object: &Value) {
let contents = fs::read_to_string(path).unwrap();
let actual: Value = serde_json::from_str(&contents).unwrap();
assert_eq!(&actual, object);
}
#[test]
fn test_write_object_to_dir() {
let (handler, tempdir) = create_handler_with_dir();
let kind = "kind";
let value = json!({
"blah": null,
});
let output = handler.write_object(kind, value.clone()).unwrap();
let expected = json!({
"kind": kind,
"data": value,
});
check_written_object(&output, &expected);
let _ = tempdir;
}
#[test]
fn test_write_object_to_dir_err() {
let (handler, tempdir_path) = {
let (handler, tempdir) = create_handler_with_dir();
let tempdir_path = tempdir.path().to_path_buf();
(handler, tempdir_path)
};
let kind = "kind";
let value = json!({
"blah": null,
});
let err = handler.write_object(kind, value).unwrap_err();
if let HandlerError::Create {
filepath, ..
} = err
{
assert!(filepath.starts_with(tempdir_path))
} else {
panic!("unexpected error: {:?}", err);
}
}
}