1#[macro_use]
48extern crate serde_derive;
49extern crate futures;
50extern crate hex;
51extern crate hyper;
52extern crate openssl;
53extern crate reqwest;
54extern crate serde_json;
55#[cfg(tests)]
56extern crate tokio;
57use futures::future::Either;
58use futures::{Future, Stream};
59
60#[derive(Debug, Serialize, Deserialize)]
61pub enum HookContent {
62 Discussion {
63 repository_owner: String,
64 repository_name: String,
65 discussion_number: u32,
66 title: String,
67 author: String,
68 },
69 NewPatches {
70 repository_owner: String,
71 repository_name: String,
72 pusher: String,
73 patches: Vec<Patch>,
74 },
75 PatchesApplied {
76 repository_owner: String,
77 repository_name: String,
78 merged_by: String,
79 title: String,
80 author: String,
81 patches: Vec<Patch>,
82 },
83}
84
85#[derive(Debug, Serialize, Deserialize)]
86pub struct Patch {
87 pub hash: String,
88 pub authors: Vec<String>,
89 pub url: String,
90 pub name: String,
91}
92
93#[derive(Debug)]
94pub struct Hook {
95 pub url: reqwest::Url,
96 pub secret: String,
97}
98
99impl Hook {
100 pub fn run(
103 self,
104 contents: &HookContent,
105 ) -> impl Future<Item = reqwest::async::Response, Error = reqwest::Error> {
106 use hex::encode;
107 let body = serde_json::to_string(contents).unwrap();
108 let host = if let (Some(host), Some(port)) = (self.url.host(), self.url.port()) {
109 Some(format!("{}:{}", host, port))
110 } else {
111 None
112 };
113 let client = reqwest::async::Client::new().post(self.url);
114 let client = if let Some(ref host) = host {
115 client.header(reqwest::header::HOST, host.as_str())
116 } else {
117 client
118 };
119 let signature = {
120 use openssl::hash::MessageDigest;
121 use openssl::pkey::PKey;
122 use openssl::sign::Signer;
123 let pkey = PKey::hmac(self.secret.as_bytes()).unwrap();
124 let mut signer = Signer::new(MessageDigest::sha256(), &pkey).unwrap();
125 signer.update(body.as_bytes()).unwrap();
126 signer.sign_to_vec().unwrap()
127 };
128 println!("sig: {:?}", signature);
129 let s = "sha256=".to_string() + &encode(&signature);
130 let client = client.header("X-Nest-Event-Signature", s.as_str());
131
132 client.body(body).send()
133 }
134}
135
136pub fn parse_request(
140 req: hyper::Request<hyper::Body>,
141 secret: &str,
142) -> impl Future<Item = Option<HookContent>, Error = hyper::Error> {
143 use hex::decode;
144 use openssl::hash::MessageDigest;
145 use openssl::pkey::PKey;
146 use openssl::sign::Signer;
147 let pkey = PKey::hmac(secret.as_bytes()).unwrap();
148 let (digest, sig) = if let Some(sig) = req.headers().get("X-Nest-Event-Signature") {
149 if let Ok(sig) = std::str::from_utf8(sig.as_bytes()) {
150 let mut sp = sig.split("=");
151 match (sp.next(), sp.next().and_then(|sig| decode(sig).ok())) {
152 (Some("sha256"), Some(sig)) => (MessageDigest::sha256(), sig),
153 _ => return Either::B(futures::finished(None)),
154 }
155 } else {
156 return Either::B(futures::finished(None));
157 }
158 } else {
159 return Either::B(futures::finished(None));
160 };
161 println!("received: {:?}", sig);
162 Either::A(req.into_body().concat2().map(move |body| {
163 let mut signer = Signer::new(digest, &pkey).unwrap();
164 signer.update(body.as_ref()).unwrap();
165 if let Ok(hmac) = signer.sign_to_vec() {
166 if openssl::memcmp::eq(&hmac, &sig) {
167 if let Ok(s) = std::str::from_utf8(&body) {
168 return serde_json::from_str(s).ok();
169 }
170 }
171 }
172 None
173 }))
174}