Breakup from_data() into smaller bits

To improve readability and reduce indention levels some code was moved
into their own functions.

And a test case was added to the `secret()` test.
This commit is contained in:
finga 2021-03-29 02:19:30 +02:00
parent ee32424f8c
commit 822ddafacb

View file

@ -54,6 +54,14 @@ struct Filter {
#[derive(Debug)] #[derive(Debug)]
struct Hooks(HashMap<String, Vec<String>>); struct Hooks(HashMap<String, Vec<String>>);
fn validate_request(secret: &str, signature: &str, data: &[u8]) -> Result<()> {
let mut mac = Hmac::<Sha256>::new_varkey(&secret.as_bytes())
.map_err(|e| anyhow!("Could not create hasher with secret: {}", e))?;
mac.update(&data);
let raw_signature = hex::decode(signature.as_bytes())?;
mac.verify(&raw_signature).map_err(|e| anyhow!("{}", e))
}
fn replace_parameter(input: &str, headers: &HeaderMap, data: &serde_json::Value) -> Result<String> { fn replace_parameter(input: &str, headers: &HeaderMap, data: &serde_json::Value) -> Result<String> {
let parse: IResult<&str, Vec<&str>> = many0(alt(( let parse: IResult<&str, Vec<&str>> = many0(alt((
map_res( map_res(
@ -86,6 +94,47 @@ fn replace_parameter(input: &str, headers: &HeaderMap, data: &serde_json::Value)
Ok(result.join("")) Ok(result.join(""))
} }
fn filter_match(
hook_name: &str,
hook: &Hook,
filter_name: &str,
filter: &Filter,
request: &Request,
data: &serde_json::Value,
) -> Result<Option<String>> {
trace!("Matching filter `{}` of hook `{}`", filter_name, hook_name);
let regex = Regex::new(&filter.regex)?;
if let Some(value) = data.pointer(&filter.pointer) {
if let Some(value) = value.as_str() {
if regex.is_match(value) {
debug!("Filter `{}` of hook `{}` matched", filter_name, hook_name);
return Ok(Some(replace_parameter(
&hook.command.to_string(),
&request.headers(),
data,
)?));
}
} else {
anyhow!(
"Could not parse pointer in hook `{}` from filter `{}`",
hook_name,
filter_name
);
}
}
trace!(
"Filter `{}` of hook `{}` did not match",
filter_name,
hook_name
);
Ok(None)
}
impl FromDataSimple for Hooks { impl FromDataSimple for Hooks {
type Error = anyhow::Error; type Error = anyhow::Error;
@ -117,102 +166,47 @@ impl FromDataSimple for Hooks {
let mut commands = Vec::new(); let mut commands = Vec::new();
for secret in &hook.secrets { for secret in &hook.secrets {
let mut mac = match Hmac::<Sha256>::new_varkey(&secret.as_bytes()) { match validate_request(&secret, &signature, &buffer) {
Ok(mac) => mac, Ok(()) => {
Err(e) => { trace!(
error!("Could not instantiate hasher: {}", e); "Valid signature found for hook `{}`: {}",
return Failure(( hook_name,
Status::InternalServerError, signature
anyhow!("Could not instantiate hasher: {}", e), );
));
}
};
mac.update(&buffer); valid = true;
match &hex::decode(&signature.as_bytes()) { let data: serde_json::Value = match serde_json::from_slice(&buffer) {
Ok(raw_signature) => { Ok(data) => data,
if mac.verify(&raw_signature) == Ok(()) { Err(e) => {
trace!( error!("Could not parse json: {}", e);
"Valid signature found for hook `{}`: {}", return Failure((
hook_name, Status::BadRequest,
signature anyhow!("Could not parse json: {}", e),
); ));
}
};
valid = true; for (filter_name, filter) in &hook.filters {
match filter_match(
let data: serde_json::Value = match serde_json::from_slice(&buffer) &hook_name,
{ &hook,
Ok(data) => data, &filter_name,
Err(e) => { &filter,
error!("Could not parse json: {}", e); &request,
return Failure(( &data,
Status::BadRequest, ) {
anyhow!("Could not parse json: {}", e), Ok(Some(command)) => commands.push(command),
)); Ok(None) => {}
} Err(e) => error!("{}", e),
};
for (filter_name, filter) in &hook.filters {
trace!(
"Matching filter `{}` of hook `{}`",
filter_name,
hook_name
);
let regex = match Regex::new(&filter.regex) {
Ok(regex) => regex,
Err(e) => {
error!(
"Could not compile regex `{}`: {}",
&filter.regex, e
);
continue;
}
};
if let Some(value) = data.pointer(&filter.pointer) {
if let Some(value) = value.as_str() {
if regex.is_match(value) {
debug!(
"Filter `{}` of hook `{}` matched",
filter_name, hook_name
);
match replace_parameter(
&hook.command.to_string(),
&request.headers(),
&data,
) {
Ok(command) => commands.push(command),
Err(e) => error!(
"Could not replace all parameter in hook `{}`: {}",
hook_name, e
),
}
}
} else {
anyhow!(
"Could not parse pointer in hook `{}` from filter `{}`",
hook_name,
filter_name
);
}
}
trace!(
"Filter `{}` of hook `{}` did not match",
filter_name,
hook_name
);
} }
} }
} }
Err(e) => { Err(e) => {
error!("Invalid configuration: {}", e); error!("Could not validate request: {}", e);
return Failure(( return Failure((
Status::InternalServerError, Status::Unauthorized,
anyhow!("Invalid configuration: {}", e), anyhow!("Could not validate request: {}", e),
)); ));
} }
} }
@ -427,6 +421,15 @@ mod tests {
.dispatch(); .dispatch();
assert_eq!(response.status(), Status::BadRequest); assert_eq!(response.status(), Status::BadRequest);
let response = client
.post("/")
.header(Header::new("X-Gitea-Signature", "foobar"))
.header(ContentType::JSON)
.remote("127.0.0.1:8000".parse().unwrap())
.dispatch();
assert_eq!(response.status(), Status::Unauthorized);
} }
#[test] #[test]