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:
parent
ee32424f8c
commit
822ddafacb
1 changed files with 91 additions and 88 deletions
179
src/main.rs
179
src/main.rs
|
@ -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]
|
||||||
|
|
Loading…
Reference in a new issue