feat: test webhooks
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
parent
0c6199494b
commit
d919bad570
3 changed files with 206 additions and 98 deletions
|
@ -136,8 +136,10 @@ pub fn services(cfg: &mut web::ServiceConfig) {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use actix_web::{http::StatusCode, test};
|
||||
use actix_web::{error::ResponseError, http::StatusCode, test};
|
||||
use hmac::Mac;
|
||||
|
||||
use crate::ctx::api::v1::gitea::{HmacSha256, WebhookPayload};
|
||||
use crate::db::GiteaWebhook;
|
||||
use crate::tests;
|
||||
use crate::*;
|
||||
|
@ -153,6 +155,7 @@ mod tests {
|
|||
let (_dir, ctx) = tests::get_ctx().await;
|
||||
let _ = ctx.delete_user(NAME, PASSWORD).await;
|
||||
let (_, signin_resp) = ctx.register_and_signin(NAME, EMAIL, PASSWORD).await;
|
||||
let page = ctx.add_test_site(NAME.into()).await;
|
||||
let cookies = get_cookie!(signin_resp);
|
||||
let app = get_app!(ctx).await;
|
||||
|
||||
|
@ -185,6 +188,95 @@ mod tests {
|
|||
check_status!(list_all_webhooks_resp, StatusCode::OK);
|
||||
let hooks: Vec<GiteaWebhook> =
|
||||
actix_web::test::read_body_json(list_all_webhooks_resp).await;
|
||||
assert_eq!(vec![hook], hooks);
|
||||
assert_eq!(vec![hook.clone()], hooks);
|
||||
|
||||
let webhook_url = format!("{}?auth={}", V1_API_ROUTES.gitea.webhook, hook.auth_token);
|
||||
|
||||
// test webhook
|
||||
let mut webhook_payload = WebhookPayload::default();
|
||||
webhook_payload.reference = format!("refs/origin/{}", page.branch);
|
||||
webhook_payload.repository.html_url = page.repo;
|
||||
|
||||
let body = serde_json::to_string(&webhook_payload).unwrap();
|
||||
let body = body.as_bytes();
|
||||
let mut mac = HmacSha256::new_from_slice(hook.gitea_webhook_secret.as_bytes())
|
||||
.expect("HMAC can take key of any size");
|
||||
mac.update(&body);
|
||||
let res = mac.finalize();
|
||||
let sig = res.into_bytes();
|
||||
let sig = hex::encode(&sig[..]);
|
||||
|
||||
let post_to_webhook_resp = test::call_service(
|
||||
&app,
|
||||
post_request!(&webhook_payload, &webhook_url)
|
||||
.insert_header(("X-Gitea-Delivery", "foobar213randomuuid"))
|
||||
.insert_header(("X-Gitea-Signature", sig.clone()))
|
||||
.insert_header(("X-Gitea-Event", "push"))
|
||||
.cookie(cookies.clone())
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
check_status!(post_to_webhook_resp, StatusCode::OK);
|
||||
|
||||
// no webhook
|
||||
let fake_webhook_url = format!(
|
||||
"{}?auth={}",
|
||||
V1_API_ROUTES.gitea.webhook, hook.gitea_webhook_secret
|
||||
);
|
||||
let body = serde_json::to_string(&webhook_payload).unwrap();
|
||||
let body = body.as_bytes();
|
||||
let mut mac =
|
||||
HmacSha256::new_from_slice(b"nosecret").expect("HMAC can take key of any size");
|
||||
mac.update(&body);
|
||||
let res = mac.finalize();
|
||||
let fake_sig = res.into_bytes();
|
||||
let fake_sig = hex::encode(&fake_sig[..]);
|
||||
|
||||
let post_to_no_exist_webhook_resp = test::call_service(
|
||||
&app,
|
||||
post_request!(&webhook_payload, &fake_webhook_url)
|
||||
.insert_header(("X-Gitea-Delivery", "foobar213randomuuid"))
|
||||
.insert_header(("X-Gitea-Signature", fake_sig))
|
||||
.insert_header(("X-Gitea-Event", "push"))
|
||||
.cookie(cookies.clone())
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
let err = ServiceError::WebhookNotFound;
|
||||
assert_eq!(post_to_no_exist_webhook_resp.status(), err.status_code());
|
||||
let resp_err: ErrorToResponse =
|
||||
actix_web::test::read_body_json(post_to_no_exist_webhook_resp).await;
|
||||
assert_eq!(resp_err.error, err.to_string());
|
||||
|
||||
// no website
|
||||
let mut webhook_payload = WebhookPayload::default();
|
||||
webhook_payload.reference = format!("refs/origin/{}", page.branch);
|
||||
webhook_payload.repository.html_url = "foo".into();
|
||||
|
||||
let body = serde_json::to_string(&webhook_payload).unwrap();
|
||||
let body = body.as_bytes();
|
||||
let mut mac = HmacSha256::new_from_slice(hook.gitea_webhook_secret.as_bytes())
|
||||
.expect("HMAC can take key of any size");
|
||||
mac.update(&body);
|
||||
let res = mac.finalize();
|
||||
let sig = res.into_bytes();
|
||||
let sig = hex::encode(&sig[..]);
|
||||
|
||||
let post_to_no_website_webhook_resp = test::call_service(
|
||||
&app,
|
||||
post_request!(&webhook_payload, &webhook_url)
|
||||
.insert_header(("X-Gitea-Delivery", "foobar213randomuuid"))
|
||||
.insert_header(("X-Gitea-Signature", sig.clone()))
|
||||
.insert_header(("X-Gitea-Event", "push"))
|
||||
.cookie(cookies.clone())
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
|
||||
let err = ServiceError::WebsiteNotFound;
|
||||
assert_eq!(post_to_no_website_webhook_resp.status(), err.status_code());
|
||||
let resp_err: ErrorToResponse =
|
||||
actix_web::test::read_body_json(post_to_no_website_webhook_resp).await;
|
||||
assert_eq!(resp_err.error, err.to_string());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,127 +22,128 @@ use sha2::Sha256;
|
|||
use tracing::{info, warn};
|
||||
|
||||
use crate::ctx::Ctx;
|
||||
|
||||
use crate::errors::ServiceError;
|
||||
use crate::errors::ServiceResult;
|
||||
|
||||
type HmacSha256 = Hmac<Sha256>;
|
||||
pub type HmacSha256 = Hmac<Sha256>;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone, Eq, PartialEq)]
|
||||
pub struct CommitPerson {
|
||||
name: String,
|
||||
email: String,
|
||||
username: String,
|
||||
pub name: String,
|
||||
pub email: String,
|
||||
pub username: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, Debug, Clone, Eq, PartialEq)]
|
||||
pub struct Commit {
|
||||
id: String,
|
||||
message: String,
|
||||
url: String,
|
||||
author: CommitPerson,
|
||||
committer: CommitPerson,
|
||||
verification: serde_json::Value,
|
||||
timestamp: String,
|
||||
added: serde_json::Value,
|
||||
removed: serde_json::Value,
|
||||
modified: serde_json::Value,
|
||||
pub id: String,
|
||||
pub message: String,
|
||||
pub url: String,
|
||||
pub author: CommitPerson,
|
||||
pub committer: CommitPerson,
|
||||
pub verification: serde_json::Value,
|
||||
pub timestamp: String,
|
||||
pub added: serde_json::Value,
|
||||
pub removed: serde_json::Value,
|
||||
pub modified: serde_json::Value,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone, Eq, PartialEq)]
|
||||
pub struct Person {
|
||||
id: usize,
|
||||
login: String,
|
||||
full_name: String,
|
||||
email: String,
|
||||
avatar_url: String,
|
||||
language: String,
|
||||
is_admin: bool,
|
||||
last_login: String,
|
||||
created: String,
|
||||
restricted: bool,
|
||||
active: bool,
|
||||
prohibit_login: bool,
|
||||
location: String,
|
||||
website: String,
|
||||
description: String,
|
||||
visibility: String,
|
||||
followers_count: usize,
|
||||
following_count: usize,
|
||||
starred_repos_count: usize,
|
||||
username: String,
|
||||
pub id: usize,
|
||||
pub login: String,
|
||||
pub full_name: String,
|
||||
pub email: String,
|
||||
pub avatar_url: String,
|
||||
pub language: String,
|
||||
pub is_admin: bool,
|
||||
pub last_login: String,
|
||||
pub created: String,
|
||||
pub restricted: bool,
|
||||
pub active: bool,
|
||||
pub prohibit_login: bool,
|
||||
pub location: String,
|
||||
pub website: String,
|
||||
pub description: String,
|
||||
pub visibility: String,
|
||||
pub followers_count: usize,
|
||||
pub following_count: usize,
|
||||
pub starred_repos_count: usize,
|
||||
pub username: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone, Eq, PartialEq)]
|
||||
pub struct Permissions {
|
||||
admin: bool,
|
||||
push: bool,
|
||||
pull: bool,
|
||||
pub admin: bool,
|
||||
pub push: bool,
|
||||
pub pull: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone, Eq, PartialEq)]
|
||||
pub struct InternalTracker {
|
||||
enable_time_tracker: bool,
|
||||
allow_only_contributors_to_track_time: bool,
|
||||
enable_issue_dependencies: bool,
|
||||
pub enable_time_tracker: bool,
|
||||
pub allow_only_contributors_to_track_time: bool,
|
||||
pub enable_issue_dependencies: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone, Eq, PartialEq)]
|
||||
pub struct Repository {
|
||||
id: usize,
|
||||
owner: Person,
|
||||
name: String,
|
||||
full_name: String,
|
||||
description: String,
|
||||
empty: bool,
|
||||
private: bool,
|
||||
fork: bool,
|
||||
template: bool,
|
||||
parent: Option<serde_json::Value>,
|
||||
mirror: bool,
|
||||
size: usize,
|
||||
html_url: String,
|
||||
ssh_url: String,
|
||||
clone_url: String,
|
||||
original_url: String,
|
||||
website: String,
|
||||
stars_count: usize,
|
||||
forks_count: usize,
|
||||
watchers_count: usize,
|
||||
open_issues_count: usize,
|
||||
open_pr_counter: usize,
|
||||
release_counter: usize,
|
||||
default_branch: String,
|
||||
archived: bool,
|
||||
created_at: String,
|
||||
updated_at: String,
|
||||
permissions: Permissions,
|
||||
has_issues: bool,
|
||||
internal_tracker: InternalTracker,
|
||||
has_wiki: bool,
|
||||
has_pull_requests: bool,
|
||||
has_projects: bool,
|
||||
ignore_whitespace_conflicts: bool,
|
||||
allow_merge_commits: bool,
|
||||
allow_rebase: bool,
|
||||
allow_rebase_explicit: bool,
|
||||
allow_squash_merge: bool,
|
||||
default_merge_style: String,
|
||||
avatar_url: String,
|
||||
internal: bool,
|
||||
mirror_interval: String,
|
||||
mirror_updated: String,
|
||||
repo_transfer: Option<serde_json::Value>,
|
||||
pub id: usize,
|
||||
pub owner: Person,
|
||||
pub name: String,
|
||||
pub full_name: String,
|
||||
pub description: String,
|
||||
pub empty: bool,
|
||||
pub private: bool,
|
||||
pub fork: bool,
|
||||
pub template: bool,
|
||||
pub parent: Option<serde_json::Value>,
|
||||
pub mirror: bool,
|
||||
pub size: usize,
|
||||
pub html_url: String,
|
||||
pub ssh_url: String,
|
||||
pub clone_url: String,
|
||||
pub original_url: String,
|
||||
pub website: String,
|
||||
pub stars_count: usize,
|
||||
pub forks_count: usize,
|
||||
pub watchers_count: usize,
|
||||
pub open_issues_count: usize,
|
||||
pub open_pr_counter: usize,
|
||||
pub release_counter: usize,
|
||||
pub default_branch: String,
|
||||
pub archived: bool,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
pub permissions: Permissions,
|
||||
pub has_issues: bool,
|
||||
pub internal_tracker: InternalTracker,
|
||||
pub has_wiki: bool,
|
||||
pub has_pull_requests: bool,
|
||||
pub has_projects: bool,
|
||||
pub ignore_whitespace_conflicts: bool,
|
||||
pub allow_merge_commits: bool,
|
||||
pub allow_rebase: bool,
|
||||
pub allow_rebase_explicit: bool,
|
||||
pub allow_squash_merge: bool,
|
||||
pub default_merge_style: String,
|
||||
pub avatar_url: String,
|
||||
pub internal: bool,
|
||||
pub mirror_interval: String,
|
||||
pub mirror_updated: String,
|
||||
pub repo_transfer: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone, Eq, PartialEq)]
|
||||
pub struct WebhookPayload {
|
||||
#[serde(rename(serialize = "ref", deserialize = "ref"))]
|
||||
reference: String,
|
||||
before: String,
|
||||
after: String,
|
||||
compare_url: String,
|
||||
repository: Repository,
|
||||
pusher: Person,
|
||||
sender: Person,
|
||||
pub reference: String,
|
||||
pub before: String,
|
||||
pub after: String,
|
||||
pub compare_url: String,
|
||||
pub repository: Repository,
|
||||
pub pusher: Person,
|
||||
pub sender: Person,
|
||||
}
|
||||
|
||||
impl Ctx {
|
||||
|
@ -168,10 +169,9 @@ impl Ctx {
|
|||
&payload.repository.clone_url,
|
||||
] {
|
||||
if self.db.site_with_repository_exists(url).await? {
|
||||
let mut mac = HmacSha256::new_from_slice(hook.gitea_webhook_secret.as_bytes())
|
||||
.expect("HMAC can take key of any size");
|
||||
let mut mac = HmacSha256::new_from_slice(hook.gitea_webhook_secret.as_bytes())?;
|
||||
mac.update(&body);
|
||||
mac.verify_slice(&sig[..]).unwrap();
|
||||
mac.verify_slice(&sig[..])?;
|
||||
|
||||
let site = self.db.get_site_from_repo_url(url).await?;
|
||||
if payload.reference.contains(&site.branch) {
|
||||
|
@ -195,6 +195,6 @@ impl Ctx {
|
|||
"[webhook][forgejo/gitea] stray update from {} repository",
|
||||
payload.repository.html_url
|
||||
);
|
||||
Ok(())
|
||||
Err(ServiceError::WebsiteNotFound)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,8 @@ use argon2_creds::errors::CredsError;
|
|||
use config::ConfigError as ConfigErrorInner;
|
||||
use derive_more::{Display, Error};
|
||||
use git2::Error as GitError;
|
||||
use hmac::digest::InvalidLength;
|
||||
use hmac::digest::MacError;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::ParseError;
|
||||
|
||||
|
@ -184,6 +186,20 @@ pub enum ServiceError {
|
|||
WebhookNotFound,
|
||||
}
|
||||
|
||||
impl From<InvalidLength> for ServiceError {
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
fn from(_: InvalidLength) -> ServiceError {
|
||||
ServiceError::InternalServerError
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MacError> for ServiceError {
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
fn from(_: MacError) -> ServiceError {
|
||||
ServiceError::WebhookNotFound
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseError> for ServiceError {
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
fn from(_: ParseError) -> ServiceError {
|
||||
|
|
Loading…
Reference in a new issue