#[macro_use]
extern crate serde_derive;
extern crate futures;
extern crate hex;
extern crate hyper;
extern crate openssl;
extern crate reqwest;
extern crate serde_json;
#[cfg(tests)]
extern crate tokio;
use futures::future::Either;
use futures::{Future, Stream};
#[derive(Debug, Serialize, Deserialize)]
pub enum HookContent {
Discussion {
repository_owner: String,
repository_name: String,
discussion_number: u32,
title: String,
author: String,
},
NewPatches {
repository_owner: String,
repository_name: String,
pusher: String,
patches: Vec<Patch>,
},
PatchesApplied {
repository_owner: String,
repository_name: String,
merged_by: String,
title: String,
author: String,
patches: Vec<Patch>,
},
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Patch {
pub hash: String,
pub authors: Vec<String>,
pub url: String,
pub name: String,
}
#[derive(Debug)]
pub struct Hook {
pub url: reqwest::Url,
pub secret: String,
}
impl Hook {
pub fn run(
self,
contents: &HookContent,
) -> impl Future<Item = reqwest::async::Response, Error = reqwest::Error> {
use hex::encode;
let body = serde_json::to_string(contents).unwrap();
let host = if let (Some(host), Some(port)) = (self.url.host(), self.url.port()) {
Some(format!("{}:{}", host, port))
} else {
None
};
let client = reqwest::async::Client::new().post(self.url);
let client = if let Some(ref host) = host {
client.header(reqwest::header::HOST, host.as_str())
} else {
client
};
let signature = {
use openssl::hash::MessageDigest;
use openssl::pkey::PKey;
use openssl::sign::Signer;
let pkey = PKey::hmac(self.secret.as_bytes()).unwrap();
let mut signer = Signer::new(MessageDigest::sha256(), &pkey).unwrap();
signer.update(body.as_bytes()).unwrap();
signer.sign_to_vec().unwrap()
};
println!("sig: {:?}", signature);
let s = "sha256=".to_string() + &encode(&signature);
let client = client.header("X-Nest-Event-Signature", s.as_str());
client.body(body).send()
}
}
pub fn parse_request(
req: hyper::Request<hyper::Body>,
secret: &str,
) -> impl Future<Item = Option<HookContent>, Error = hyper::Error> {
use hex::decode;
use openssl::hash::MessageDigest;
use openssl::pkey::PKey;
use openssl::sign::Signer;
let pkey = PKey::hmac(secret.as_bytes()).unwrap();
let (digest, sig) = if let Some(sig) = req.headers().get("X-Nest-Event-Signature") {
if let Ok(sig) = std::str::from_utf8(sig.as_bytes()) {
let mut sp = sig.split("=");
match (sp.next(), sp.next().and_then(|sig| decode(sig).ok())) {
(Some("sha256"), Some(sig)) => (MessageDigest::sha256(), sig),
_ => return Either::B(futures::finished(None)),
}
} else {
return Either::B(futures::finished(None));
}
} else {
return Either::B(futures::finished(None));
};
println!("received: {:?}", sig);
Either::A(req.into_body().concat2().map(move |body| {
let mut signer = Signer::new(digest, &pkey).unwrap();
signer.update(body.as_ref()).unwrap();
if let Ok(hmac) = signer.sign_to_vec() {
if openssl::memcmp::eq(&hmac, &sig) {
if let Ok(s) = std::str::from_utf8(&body) {
return serde_json::from_str(s).ok();
}
}
}
None
}))
}