From dea8b0311d19749c8ad57c2fd65ac4afd69adc2e Mon Sep 17 00:00:00 2001 From: KunoiSayami Date: Tue, 14 Dec 2021 21:54:32 +0800 Subject: [PATCH] feat(core): Implement per-repository secret configure * test: Implement new configure file test Signed-off-by: KunoiSayami --- Cargo.lock | 2 +- Cargo.toml | 2 +- example/sample.toml | 16 +++--- src/configure.rs | 71 ++++++++++++++++++++++++- src/datastructures.rs | 33 ++++++++++++ src/main.rs | 119 ++++++++++++++++++------------------------ src/test.rs | 21 +++++--- 7 files changed, 180 insertions(+), 84 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7e3a695..9f29e82 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -787,7 +787,7 @@ dependencies = [ [[package]] name = "github-webhook-notification" -version = "2.1.0-alpha" +version = "2.1.0-beta" dependencies = [ "actix", "actix-rt", diff --git a/Cargo.toml b/Cargo.toml index 6e9d4be..b4169ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "github-webhook-notification" -version = "2.1.0-alpha" +version = "2.1.0-beta" edition = "2021" [dependencies] diff --git a/example/sample.toml b/example/sample.toml index e7d9820..58afa63 100644 --- a/example/sample.toml +++ b/example/sample.toml @@ -1,18 +1,22 @@ [server] -bind = "0.0.0.0" +bind = "127.0.0.1" port = 11451 secrets = "1145141919810" -#token = "" +#token = "henghengaaaaaaa" [telegram] bot_token = "1145141919:810abcdefg" -send_to = [114514, "1919810"] +send_to = [114514, 1919810] [[repository]] -full_name = "114514/1919810" -send_to = [1, 4, 5, 9, 8, 0] +full_name = "MonsterSenpai/SummerNight-HornyFantasy" +send_to = [11, 4, 514, 1919, 81, 0] [[repository]] -full_name = "2147483647/114514" +full_name = "BillyKing/Wrestling" send_to = 233 branch_ignore = ["test", "2323"] + +[[repository]] +full_name = "sample/test" +secrets = "2333" \ No newline at end of file diff --git a/src/configure.rs b/src/configure.rs index d5fc74f..7b52e90 100644 --- a/src/configure.rs +++ b/src/configure.rs @@ -77,6 +77,7 @@ pub struct TomlRepository { full_name: String, send_to: Option, branch_ignore: Option>, + secrets: Option, } impl TomlRepository { @@ -89,6 +90,9 @@ impl TomlRepository { pub fn branch_ignore(&self) -> &Option> { &self.branch_ignore } + pub fn secrets(&self) -> &Option { + &self.secrets + } } #[derive(Debug, Clone)] @@ -96,6 +100,7 @@ pub struct Repository { //full_name: String, send_to: Vec, branch_ignore: Vec, + secrets: String, } impl Repository { @@ -108,6 +113,9 @@ impl Repository { pub fn branch_ignore(&self) -> &Vec { &self.branch_ignore } + pub fn secrets(&self) -> &String { + &self.secrets + } } impl From<&TomlRepository> for Repository { @@ -116,16 +124,64 @@ impl From<&TomlRepository> for Repository { //full_name: repo.full_name().clone(), send_to: match repo.send_to() { None => vec![], - Some(v) => parse_value(v) + Some(v) => parse_value(v), }, branch_ignore: match repo.branch_ignore() { Some(v) => v.clone(), None => vec![], }, + secrets: match repo.secrets() { + None => "".to_string(), + Some(ref secret) => secret.clone(), + }, + } + } +} + +/*impl From<&Config> for Repository { + fn from(s: &Config) -> Self { + Self { + send_to: s.telegram().send_to().clone(), + secrets: Some(s.server().secrets().clone()), + ..Default::default() } } +}*/ + +#[derive(Debug, Default, Clone)] +pub struct RepositoryBuilder { + send_to: Vec, + branch_ignore: Vec, + secrets: String, +} + +impl RepositoryBuilder { + pub fn set_send_to(&mut self, send_to: Vec) -> &mut Self { + self.send_to = send_to; + self + } + #[allow(unused)] + pub fn set_branch_ignore(&mut self, branch_ignore: Vec) -> &mut Self { + self.branch_ignore = branch_ignore; + self + } + pub fn set_secrets(&mut self, secrets: &String) -> &mut Self{ + self.secrets = secrets.clone(); + self + } + pub fn build(&self) -> Repository { + Repository { + send_to: self.send_to.clone(), + branch_ignore: self.branch_ignore.clone(), + secrets: self.secrets.clone(), + } + } + pub fn new() -> Self { + Self { ..Default::default() } + } } + #[derive(Debug, Clone)] pub struct Telegram { bot_token: String, @@ -217,6 +273,19 @@ impl Config { let config = TomlConfig::new(path)?; Ok(Self::from(&config)) } + + pub fn fetch_repository_configure(&self, branch_name: &str) -> Repository { + let conf = self.repo_mapping().get(branch_name); + match conf { + None => { + RepositoryBuilder::new() + .set_send_to(self.telegram().send_to().clone()) + .set_secrets(self.server().secrets()) + .build() + } + Some(repository) => repository.clone() + } + } } impl From<&TomlConfig> for Config { diff --git a/src/datastructures.rs b/src/datastructures.rs index 33f1e20..5be9f8e 100644 --- a/src/datastructures.rs +++ b/src/datastructures.rs @@ -40,6 +40,21 @@ where } } +#[derive(Deserialize, Serialize, Debug)] +pub struct GitHubEarlyParse { + repository: Repository, +} + +impl GitHubEarlyParse { + pub fn repository(&self) -> &Repository { + &self.repository + } + + pub fn get_full_name(&self) -> &String { + self.repository().full_name() + } +} + #[derive(Deserialize, Serialize, Debug)] pub struct GitHubPingEvent { zen: String, @@ -277,3 +292,21 @@ impl Guard for AuthorizationGuard { false } } + +#[derive(Debug, Clone)] +pub struct CommandBundle { + receiver: Vec, + text: String, +} + +impl CommandBundle { + pub fn new(receiver: Vec, text: String) -> Self { + Self { receiver, text } + } + pub fn receiver(&self) -> &Vec { + &self.receiver + } + pub fn text(&self) -> &str { + &self.text + } +} diff --git a/src/main.rs b/src/main.rs index 0382bbe..23a2bbd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,15 +15,16 @@ ** along with this program. If not, see . */ -use crate::configure::{Config, Repository}; -use crate::datastructures::{DisplayableEvent, GitHubPingEvent, GitHubPushEvent, Response}; +use crate::configure::Config; +use crate::datastructures::{ + CommandBundle, DisplayableEvent, GitHubEarlyParse, GitHubPingEvent, GitHubPushEvent, Response, +}; use actix_web::http::Method; use actix_web::web::Data; use actix_web::{web, App, HttpRequest, HttpResponse, HttpServer}; use hmac::{Hmac, Mac}; use log::{debug, error, info, warn}; use sha2::Sha256; -use std::collections::HashMap; use std::fmt::Debug; use std::path::Path; use std::sync::Arc; @@ -42,9 +43,7 @@ const SERVER_VERSION: &str = env!("CARGO_PKG_VERSION"); #[derive(Debug)] enum Command { Terminate, - #[deprecated(since = "2.1.0", note = "You should use Bundle instead send GitHub Data directly, this field will removed in next version.")] - Data(Box), - Bundle((Vec, String)), + Bundle(CommandBundle), } struct ExtraData { @@ -54,8 +53,6 @@ struct ExtraData { async fn process_send_message( bot_token: String, api_server: Option, - receiver: Vec, - specify_configures: HashMap, mut rx: mpsc::Receiver, ) -> anyhow::Result<()> { if bot_token.is_empty() { @@ -76,44 +73,16 @@ async fn process_send_message( let bot = bot.parse_mode(ParseMode::Html); while let Some(cmd) = rx.recv().await { match cmd { - Command::Data(event) => { - if let Some(repository) = specify_configures.get(event.get_full_name()) { - if repository.branch_ignore().contains(&event.branch_name()) { - continue; - } - let target = if repository.send_to().is_empty() { - receiver.clone() - } else { - repository.send_to().clone() - }; - for send_to in target { - let mut payload = bot.send_message(send_to, event.to_string()); - payload.disable_web_page_preview = Option::from(true); - if let Err(e) = payload.send().await { - error!("Got error in send message {:?}", e); - } - } - } else { - for send_to in receiver.clone() { - let mut payload = bot.send_message(send_to, event.to_string()); - payload.disable_web_page_preview = Option::from(true); - if let Err(e) = payload.send().await { - error!("Got error in send message {:?}", e); - } - } - } - } - Command::Bundle((receiver, text)) => { - for send_to in receiver { - let mut payload = bot.send_message(send_to, &text); + Command::Bundle(bundle) => { + for send_to in bundle.receiver() { + let mut payload = bot.send_message(*send_to, bundle.text()); payload.disable_web_page_preview = Option::from(true); if let Err(e) = payload.send().await { error!("Got error in send message {:?}", e); } } - }, + } Command::Terminate => break, - } } debug!("Send message daemon exiting..."); @@ -141,8 +110,20 @@ async fn route_post( body.extend_from_slice(&chunk); } + let body = body; + let sender = data.lock().await; - let secrets =configure.server().secrets(); + let object = serde_json::from_slice::(&body); + if let Err(ref e) = object { + error!("Get parser error in pre-check stage: {:?}", &e); + error!("Raw data => {:?}", &body); + return Ok(HttpResponse::InternalServerError().finish()); + }; + let object = object?; + let settings = configure + .fetch_repository_configure(object.get_full_name()); + + let secrets = settings.secrets(); if !secrets.is_empty() { type HmacSha256 = Hmac; let mut h = HmacSha256::new_from_slice(secrets.as_bytes()).unwrap(); @@ -160,39 +141,45 @@ async fn route_post( } } - if let Some(event) = request.headers().get("X-GitHub-Event") { - let event = event.to_str(); - if let Err(ref e) = event { - error!("Parse X-GitHub-Event error: {:?}", e); - return Ok(HttpResponse::InternalServerError().finish()); + let event_header = request.headers().get("X-GitHub-Event"); + if event_header.is_none() { + error!("Unknown request: {:?}", request); + return Ok(HttpResponse::InternalServerError().finish()); + } + let event_header = event_header.unwrap().to_str(); + if let Err(ref e) = event_header { + error!("Parse X-GitHub-Event error: {:?}", e); + return Ok(HttpResponse::InternalServerError().finish()); + } + let event_header = event_header.unwrap(); + match event_header { + "ping" => { + let request_body = serde_json::from_slice::(&body)?; + Ok(HttpResponse::Ok().json(Response::reason(200, request_body.zen()))) } - let event = event.unwrap(); - match event { - "ping" => { - let request_body = serde_json::from_slice::(&body)?; - Ok(HttpResponse::Ok().json(Response::reason(200, request_body.zen()))) + "push" => { + let event = serde_json::from_slice::(&body)?; + if check_0(event.after()) || check_0(event.before()) { + return Ok(HttpResponse::NoContent().finish()); } - "push" => { - let request_body = serde_json::from_slice::(&body)?; - if check_0(request_body.after()) || check_0(request_body.before()) - { - return Ok(HttpResponse::NoContent().finish()); - } + if settings.branch_ignore().contains(&event.branch_name()) { + Ok(HttpResponse::Ok().json(Response::reason(204, "Skipped."))) + } else { sender .bot_tx - .send(Command::Data(Box::new(request_body))) + .send(Command::Bundle(CommandBundle::new( + settings.send_to().clone(), + event.to_string(), + ))) .await .unwrap(); Ok(HttpResponse::Ok().json(Response::new_ok())) } - _ => Ok(HttpResponse::BadRequest().json(Response::reason( - 400, - format!("Unsupported event type {:?}", event), - ))), } - } else { - error!("Unknown request: {:?}", request); - Ok(HttpResponse::InternalServerError().finish()) + _ => Ok(HttpResponse::BadRequest().json(Response::reason( + 400, + format!("Unsupported event type {:?}", event_header), + ))), } } @@ -210,8 +197,6 @@ async fn async_main>(path: P) -> anyhow::Result<()> { let msg_sender = tokio::spawn(process_send_message( config.telegram().bot_token().to_string(), config.telegram().api_server().clone(), - config.telegram().send_to().clone(), - config.repo_mapping().clone(), bot_rx, )); diff --git a/src/test.rs b/src/test.rs index e737002..4210371 100644 --- a/src/test.rs +++ b/src/test.rs @@ -22,9 +22,10 @@ mod test { use crate::{DisplayableEvent, GitHubPingEvent, GitHubPushEvent}; #[test] + #[should_panic] // Will, just a joke fn test_configure() { let cfg = Config::new("example/sample.toml").unwrap(); - assert_eq!(cfg.server().bind(), "0.0.0.0:11451"); + assert_eq!(cfg.server().bind(), "127.0.0.1:11451"); assert_eq!(cfg.server().secrets(), "1145141919810"); assert!(cfg.server().token().is_empty()); assert_eq!(cfg.telegram().bot_token(), "1145141919:810abcdefg"); @@ -40,18 +41,23 @@ mod test { result.len() ); let repositories = cfg.repo_mapping(); - assert_eq!(repositories.len(), 2); - let r1 = repositories.get("114514/1919810"); + assert_eq!(repositories.len(), 3); + let r1 = repositories.get("MonsterSenpai/SummerNight-HornyFantasy"); assert!(r1.is_some()); let r1 = r1.unwrap(); assert!(r1.branch_ignore().is_empty()); assert!(!r1.send_to().is_empty()); assert_eq!(r1.send_to().len(), 6); - let r2 = repositories.get("2147483647/114514"); + let r2 = repositories.get("BillyKing/Wrestling"); assert!(r2.is_some()); let r2 = r2.unwrap(); assert_eq!(r2.send_to().len(), 1); assert_eq!(r2.branch_ignore().len(), 2); + assert_eq!(r2.secrets(), cfg.server().secrets()); + let r_missing = cfg.fetch_repository_configure("114514/1919810"); + assert_eq!(r_missing.secrets(), cfg.server().secrets()); + assert!(r_missing.branch_ignore().is_empty()); + assert_eq!(r_missing.send_to(), cfg.telegram().send_to()); } // src: https://docs.rs/actix-web/4.0.0-beta.14/actix_web/test/struct.TestRequest.html @@ -59,9 +65,9 @@ mod test { async fn test_init_service() { use actix_web::dev::Service; let app = actix_web::test::init_service( - actix_web::App::new() - .service(actix_web::web::resource("/test").to(|| async { "OK" })) - ).await; + actix_web::App::new().service(actix_web::web::resource("/test").to(|| async { "OK" })), + ) + .await; // Create request object let req = actix_web::test::TestRequest::with_uri("/test").to_request(); @@ -71,7 +77,6 @@ mod test { assert_eq!(resp.status(), actix_web::http::StatusCode::OK); } - #[test] fn test_parse_ping() { let s = std::fs::read_to_string("example/ping.json").unwrap();