Compare commits
2 commits
6acefda5d3
...
ad358ad7c2
Author | SHA1 | Date | |
---|---|---|---|
ad358ad7c2 | |||
067296ffab |
13 changed files with 63 additions and 65 deletions
|
@ -6,6 +6,15 @@ license = { workspace = true }
|
||||||
repository = { workspace = true }
|
repository = { workspace = true }
|
||||||
description = "Forgejo support for git-next, the trunk-based development manager"
|
description = "Forgejo support for git-next, the trunk-based development manager"
|
||||||
|
|
||||||
|
[lints.clippy]
|
||||||
|
nursery = { level = "warn", priority = -1 }
|
||||||
|
pedantic = { level = "warn", priority = -1 }
|
||||||
|
unwrap_used = "warn"
|
||||||
|
expect_used = "warn"
|
||||||
|
|
||||||
|
[lints.rust]
|
||||||
|
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tarpaulin_include)'] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
git-next-core = { workspace = true }
|
git-next-core = { workspace = true }
|
||||||
|
|
||||||
|
@ -32,12 +41,3 @@ tokio = { workspace = true }
|
||||||
# Testing
|
# Testing
|
||||||
assert2 = { workspace = true }
|
assert2 = { workspace = true }
|
||||||
rand = { workspace = true }
|
rand = { workspace = true }
|
||||||
|
|
||||||
[lints.clippy]
|
|
||||||
nursery = { level = "warn", priority = -1 }
|
|
||||||
# pedantic = "warn"
|
|
||||||
unwrap_used = "warn"
|
|
||||||
expect_used = "warn"
|
|
||||||
|
|
||||||
[lints.rust]
|
|
||||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tarpaulin_include)'] }
|
|
||||||
|
|
|
@ -4,6 +4,8 @@ mod tests;
|
||||||
|
|
||||||
mod webhook;
|
mod webhook;
|
||||||
|
|
||||||
|
use std::borrow::ToOwned;
|
||||||
|
|
||||||
use git_next_core::{
|
use git_next_core::{
|
||||||
self as core,
|
self as core,
|
||||||
git::{self, forge::commit::Status},
|
git::{self, forge::commit::Status},
|
||||||
|
@ -20,6 +22,7 @@ pub struct ForgeJo {
|
||||||
net: Network,
|
net: Network,
|
||||||
}
|
}
|
||||||
impl ForgeJo {
|
impl ForgeJo {
|
||||||
|
#[must_use]
|
||||||
pub const fn new(repo_details: git::RepoDetails, net: Network) -> Self {
|
pub const fn new(repo_details: git::RepoDetails, net: Network) -> Self {
|
||||||
Self { repo_details, net }
|
Self { repo_details, net }
|
||||||
}
|
}
|
||||||
|
@ -37,10 +40,9 @@ impl git::ForgeLike for ForgeJo {
|
||||||
let authorization = msg.header("authorization");
|
let authorization = msg.header("authorization");
|
||||||
tracing::info!(?authorization, %expected, "is message authorised?");
|
tracing::info!(?authorization, %expected, "is message authorised?");
|
||||||
authorization
|
authorization
|
||||||
.and_then(|header| header.strip_prefix("Basic ").map(|v| v.to_owned()))
|
.and_then(|header| header.strip_prefix("Basic ").map(ToOwned::to_owned))
|
||||||
.and_then(|value| WebhookAuth::try_new(value.as_str()).ok())
|
.and_then(|value| WebhookAuth::try_new(value.as_str()).ok())
|
||||||
.map(|auth| &auth == expected)
|
.is_some_and(|auth| &auth == expected)
|
||||||
.unwrap_or(false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_webhook_body(
|
fn parse_webhook_body(
|
||||||
|
@ -75,10 +77,8 @@ impl git::ForgeLike for ForgeJo {
|
||||||
Ok(response) => match response.response_body() {
|
Ok(response) => match response.response_body() {
|
||||||
Some(status) => match status.state {
|
Some(status) => match status.state {
|
||||||
ForgejoState::Success => Status::Pass,
|
ForgejoState::Success => Status::Pass,
|
||||||
ForgejoState::Pending => Status::Pending,
|
ForgejoState::Pending | ForgejoState::Blank => Status::Pending,
|
||||||
ForgejoState::Failure => Status::Fail,
|
ForgejoState::Failure | ForgejoState::Error => Status::Fail,
|
||||||
ForgejoState::Error => Status::Fail,
|
|
||||||
ForgejoState::Blank => Status::Pending,
|
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
#[cfg(not(tarpaulin_include))]
|
#[cfg(not(tarpaulin_include))]
|
||||||
|
|
|
@ -284,7 +284,7 @@ mod forgejo {
|
||||||
|
|
||||||
let forge = given::a_forgejo_forge(&repo_details, net);
|
let forge = given::a_forgejo_forge(&repo_details, net);
|
||||||
|
|
||||||
let_assert!(Ok(_) = forge.unregister_webhook(&webhook_id).await);
|
let_assert!(Ok(()) = forge.unregister_webhook(&webhook_id).await);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -397,13 +397,15 @@ mod forgejo {
|
||||||
repo_path,
|
repo_path,
|
||||||
token,
|
token,
|
||||||
};
|
};
|
||||||
let hook1 = with::ReturnedWebhook::new(given::a_forgejo_webhook_id(), &repo_listen_url);
|
let hook_1 =
|
||||||
let hook2 = with::ReturnedWebhook::new(given::a_forgejo_webhook_id(), &repo_listen_url);
|
with::ReturnedWebhook::new(given::a_forgejo_webhook_id(), &repo_listen_url);
|
||||||
let hook3 = with::ReturnedWebhook::new(
|
let hook_2 =
|
||||||
|
with::ReturnedWebhook::new(given::a_forgejo_webhook_id(), &repo_listen_url);
|
||||||
|
let hook_3 = with::ReturnedWebhook::new(
|
||||||
given::a_forgejo_webhook_id(),
|
given::a_forgejo_webhook_id(),
|
||||||
&given::a_repo_listen_url(&repo_details),
|
&given::a_repo_listen_url(&repo_details),
|
||||||
);
|
);
|
||||||
let hooks = [hook1, hook2, hook3];
|
let hooks = [hook_1, hook_2, hook_3];
|
||||||
|
|
||||||
// there are three existing webhooks, two are matching webhooks
|
// there are three existing webhooks, two are matching webhooks
|
||||||
with::get_webhooks_by_page(1, &hooks, &mut args);
|
with::get_webhooks_by_page(1, &hooks, &mut args);
|
||||||
|
@ -585,7 +587,7 @@ mod forgejo {
|
||||||
a_commit_status_url(repo_details, commit).as_str(),
|
a_commit_status_url(repo_details, commit).as_str(),
|
||||||
StatusCode::OK,
|
StatusCode::OK,
|
||||||
response.to_string().as_str(),
|
response.to_string().as_str(),
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn a_commit_status_url(
|
pub fn a_commit_status_url(
|
||||||
|
@ -709,7 +711,7 @@ mod forgejo {
|
||||||
a_name(),
|
a_name(),
|
||||||
a_name(),
|
a_name(),
|
||||||
a_name(),
|
a_name(),
|
||||||
Default::default(), // no repos
|
BTreeMap::default(), // no repos
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,12 +4,12 @@ use git_next_core::{git, webhook, BranchName, WebhookId};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
mod list;
|
mod list;
|
||||||
mod parse;
|
mod parser;
|
||||||
mod register;
|
mod register;
|
||||||
mod unregister;
|
mod unregister;
|
||||||
|
|
||||||
pub use list::list;
|
pub use list::list;
|
||||||
pub use parse::parse_body;
|
pub use parser::parse_body;
|
||||||
pub use register::register;
|
pub use register::register;
|
||||||
pub use unregister::unregister;
|
pub use unregister::unregister;
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
use git_next_core::server::RepoListenUrl;
|
|
||||||
//
|
//
|
||||||
use git_next_core::{git, RegisteredWebhook, WebhookAuth, WebhookId};
|
use git_next_core::{git, server::RepoListenUrl, RegisteredWebhook, WebhookAuth, WebhookId};
|
||||||
|
|
||||||
use kxio::network;
|
use kxio::network;
|
||||||
use tracing::{info, warn};
|
use secrecy::ExposeSecret as _;
|
||||||
|
use tracing::{info, instrument, warn};
|
||||||
|
|
||||||
use crate::webhook;
|
use crate::webhook;
|
||||||
use crate::webhook::Hook;
|
use crate::webhook::Hook;
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn register(
|
pub async fn register(
|
||||||
repo_details: &git::RepoDetails,
|
repo_details: &git::RepoDetails,
|
||||||
repo_listen_url: &RepoListenUrl,
|
repo_listen_url: &RepoListenUrl,
|
||||||
|
@ -26,7 +26,6 @@ pub async fn register(
|
||||||
|
|
||||||
let hostname = &repo_details.forge.hostname();
|
let hostname = &repo_details.forge.hostname();
|
||||||
let repo_path = &repo_details.repo_path;
|
let repo_path = &repo_details.repo_path;
|
||||||
use secrecy::ExposeSecret;
|
|
||||||
let token = repo_details.forge.token().expose_secret();
|
let token = repo_details.forge.token().expose_secret();
|
||||||
let url = network::NetUrl::new(format!(
|
let url = network::NetUrl::new(format!(
|
||||||
"https://{hostname}/api/v1/repos/{repo_path}/hooks?token={token}"
|
"https://{hostname}/api/v1/repos/{repo_path}/hooks?token={token}"
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
use git_next_core::{git, WebhookId};
|
use git_next_core::{git, WebhookId};
|
||||||
|
|
||||||
use kxio::network;
|
use kxio::network;
|
||||||
|
use secrecy::ExposeSecret as _;
|
||||||
|
|
||||||
pub async fn unregister(
|
pub async fn unregister(
|
||||||
webhook_id: &WebhookId,
|
webhook_id: &WebhookId,
|
||||||
|
@ -10,7 +11,6 @@ pub async fn unregister(
|
||||||
) -> git::forge::webhook::Result<()> {
|
) -> git::forge::webhook::Result<()> {
|
||||||
let hostname = &repo_details.forge.hostname();
|
let hostname = &repo_details.forge.hostname();
|
||||||
let repo_path = &repo_details.repo_path;
|
let repo_path = &repo_details.repo_path;
|
||||||
use secrecy::ExposeSecret;
|
|
||||||
let token = repo_details.forge.token().expose_secret();
|
let token = repo_details.forge.token().expose_secret();
|
||||||
let url = network::NetUrl::new(format!(
|
let url = network::NetUrl::new(format!(
|
||||||
"https://{hostname}/api/v1/repos/{repo_path}/hooks/{webhook_id}?token={token}"
|
"https://{hostname}/api/v1/repos/{repo_path}/hooks/{webhook_id}?token={token}"
|
||||||
|
|
|
@ -6,6 +6,15 @@ license = { workspace = true }
|
||||||
repository = { workspace = true }
|
repository = { workspace = true }
|
||||||
description = "GitHub support for git-next, the trunk-based development manager"
|
description = "GitHub support for git-next, the trunk-based development manager"
|
||||||
|
|
||||||
|
[lints.clippy]
|
||||||
|
nursery = { level = "warn", priority = -1 }
|
||||||
|
pedantic = { level = "warn", priority = -1 }
|
||||||
|
unwrap_used = "warn"
|
||||||
|
expect_used = "warn"
|
||||||
|
|
||||||
|
[lints.rust]
|
||||||
|
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tarpaulin_include)'] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
git-next-core = { workspace = true }
|
git-next-core = { workspace = true }
|
||||||
|
|
||||||
|
@ -43,12 +52,3 @@ tokio = { workspace = true }
|
||||||
# Testing
|
# Testing
|
||||||
assert2 = { workspace = true }
|
assert2 = { workspace = true }
|
||||||
rand = { workspace = true }
|
rand = { workspace = true }
|
||||||
|
|
||||||
[lints.clippy]
|
|
||||||
nursery = { level = "warn", priority = -1 }
|
|
||||||
# pedantic = "warn"
|
|
||||||
unwrap_used = "warn"
|
|
||||||
expect_used = "warn"
|
|
||||||
|
|
||||||
[lints.rust]
|
|
||||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tarpaulin_include)'] }
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ use kxio::network;
|
||||||
|
|
||||||
/// Checks the results of any (e.g. CI) status checks for the commit.
|
/// Checks the results of any (e.g. CI) status checks for the commit.
|
||||||
///
|
///
|
||||||
/// GitHub: https://docs.github.com/en/rest/commits/statuses?apiVersion=2022-11-28#list-commit-statuses-for-a-reference
|
/// GitHub: <https://docs.github.com/en/rest/commits/statuses?apiVersion=2022-11-28#list-commit-statuses-for-a-reference>
|
||||||
pub async fn status(github: &github::Github, commit: &git::Commit) -> git::forge::commit::Status {
|
pub async fn status(github: &github::Github, commit: &git::Commit) -> git::forge::commit::Status {
|
||||||
let repo_details = &github.repo_details;
|
let repo_details = &github.repo_details;
|
||||||
let hostname = repo_details.forge.hostname();
|
let hostname = repo_details.forge.hostname();
|
||||||
|
@ -33,17 +33,13 @@ pub async fn status(github: &github::Github, commit: &git::Commit) -> git::forge
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|status| match status.state {
|
.map(|status| match status.state {
|
||||||
GithubState::Success => Status::Pass,
|
GithubState::Success => Status::Pass,
|
||||||
GithubState::Pending => Status::Pending,
|
GithubState::Pending | GithubState::Blank => Status::Pending,
|
||||||
GithubState::Failure => Status::Fail,
|
GithubState::Failure | GithubState::Error => Status::Fail,
|
||||||
GithubState::Error => Status::Fail,
|
|
||||||
GithubState::Blank => Status::Pending,
|
|
||||||
})
|
})
|
||||||
.reduce(|l, r| match (l, r) {
|
.reduce(|l, r| match (l, r) {
|
||||||
(Status::Pass, Status::Pass) => Status::Pass,
|
(Status::Pass, Status::Pass) => Status::Pass,
|
||||||
(_, Status::Fail) => Status::Fail,
|
(_, Status::Fail) | (Status::Fail, _) => Status::Fail,
|
||||||
(Status::Fail, _) => Status::Fail,
|
(_, Status::Pending) | (Status::Pending, _) => Status::Pending,
|
||||||
(_, Status::Pending) => Status::Pending,
|
|
||||||
(Status::Pending, _) => Status::Pending,
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
|
|
|
@ -231,7 +231,7 @@ mod github {
|
||||||
|
|
||||||
let forge = given::a_github_forge(&repo_details, net);
|
let forge = given::a_github_forge(&repo_details, net);
|
||||||
|
|
||||||
let_assert!(Ok(_) = forge.unregister_webhook(&webhook_id).await);
|
let_assert!(Ok(()) = forge.unregister_webhook(&webhook_id).await);
|
||||||
}
|
}
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn should_return_error_on_network_error() {
|
async fn should_return_error_on_network_error() {
|
||||||
|
@ -335,11 +335,11 @@ mod github {
|
||||||
hostname,
|
hostname,
|
||||||
repo_path,
|
repo_path,
|
||||||
};
|
};
|
||||||
let hook1 = with::ReturnedWebhook::new(given::a_github_webhook_id(), &repo_listen_url);
|
let hook_1 = with::ReturnedWebhook::new(given::a_github_webhook_id(), &repo_listen_url);
|
||||||
let hook2 = with::ReturnedWebhook::new(given::a_github_webhook_id(), &repo_listen_url);
|
let hook_2 = with::ReturnedWebhook::new(given::a_github_webhook_id(), &repo_listen_url);
|
||||||
let hook3 =
|
let hook_3 =
|
||||||
with::ReturnedWebhook::new(given::a_github_webhook_id(), &given::any_webhook_url());
|
with::ReturnedWebhook::new(given::a_github_webhook_id(), &given::any_webhook_url());
|
||||||
let hooks = [hook1, hook2, hook3];
|
let hooks = [hook_1, hook_2, hook_3];
|
||||||
// there are three existing webhooks, two are matching webhooks
|
// there are three existing webhooks, two are matching webhooks
|
||||||
with::get_webhooks_by_page(1, &hooks, &mut args);
|
with::get_webhooks_by_page(1, &hooks, &mut args);
|
||||||
with::get_webhooks_by_page(2, &[], &mut args);
|
with::get_webhooks_by_page(2, &[], &mut args);
|
||||||
|
@ -511,7 +511,7 @@ mod github {
|
||||||
a_commit_status_url(repo_details, commit).as_str(),
|
a_commit_status_url(repo_details, commit).as_str(),
|
||||||
StatusCode::OK,
|
StatusCode::OK,
|
||||||
response.to_string().as_str(),
|
response.to_string().as_str(),
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn a_commit_status_url(
|
pub fn a_commit_status_url(
|
||||||
|
|
|
@ -1,17 +1,20 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
|
use std::string::ToString;
|
||||||
|
|
||||||
use git_next_core::{ForgeNotification, WebhookAuth};
|
use git_next_core::{ForgeNotification, WebhookAuth};
|
||||||
|
|
||||||
|
use hmac::Mac;
|
||||||
|
type HmacSha256 = hmac::Hmac<sha2::Sha256>;
|
||||||
|
|
||||||
pub fn is_authorised(msg: &ForgeNotification, webhook_auth: &WebhookAuth) -> bool {
|
pub fn is_authorised(msg: &ForgeNotification, webhook_auth: &WebhookAuth) -> bool {
|
||||||
msg.header("x-hub-signature-256")
|
msg.header("x-hub-signature-256")
|
||||||
.map(|x| x.trim_matches('"').to_string())
|
.map(|x| x.trim_matches('"').to_string())
|
||||||
.and_then(|sha| sha.strip_prefix("sha256=").map(|k| k.to_string()))
|
.and_then(|sha| sha.strip_prefix("sha256=").map(ToString::to_string))
|
||||||
.and_then(|github_signature| hex::decode(github_signature).ok())
|
.and_then(|github_signature| hex::decode(github_signature).ok())
|
||||||
.and_then(|gh_sig| {
|
.and_then(|gh_sig| {
|
||||||
let payload = &msg.body().as_str();
|
let payload = &msg.body().as_str();
|
||||||
|
|
||||||
use hmac::Mac;
|
|
||||||
type HmacSha256 = hmac::Hmac<sha2::Sha256>;
|
|
||||||
let mut hmac = HmacSha256::new_from_slice(webhook_auth.to_string().as_bytes()).ok()?;
|
let mut hmac = HmacSha256::new_from_slice(webhook_auth.to_string().as_bytes()).ok()?;
|
||||||
hmac::Mac::update(&mut hmac, payload.as_ref());
|
hmac::Mac::update(&mut hmac, payload.as_ref());
|
||||||
Some(hmac::Mac::verify_slice(hmac, gh_sig.as_ref()).is_ok())
|
Some(hmac::Mac::verify_slice(hmac, gh_sig.as_ref()).is_ok())
|
||||||
|
@ -25,8 +28,6 @@ pub fn sign_body(
|
||||||
body: &git_next_core::webhook::forge_notification::Body,
|
body: &git_next_core::webhook::forge_notification::Body,
|
||||||
) -> Option<String> {
|
) -> Option<String> {
|
||||||
let payload = body.as_str();
|
let payload = body.as_str();
|
||||||
use hmac::Mac;
|
|
||||||
type HmacSha256 = hmac::Hmac<sha2::Sha256>;
|
|
||||||
let mut hmac = HmacSha256::new_from_slice(webhook_auth.to_string().as_bytes()).ok()?;
|
let mut hmac = HmacSha256::new_from_slice(webhook_auth.to_string().as_bytes()).ok()?;
|
||||||
hmac::Mac::update(&mut hmac, payload.as_ref());
|
hmac::Mac::update(&mut hmac, payload.as_ref());
|
||||||
let f = hmac::Mac::finalize(hmac);
|
let f = hmac::Mac::finalize(hmac);
|
|
@ -1,20 +1,20 @@
|
||||||
//
|
//
|
||||||
use git_next_core::{git, webhook, ApiToken, BranchName};
|
use git_next_core::{git, webhook, ApiToken, BranchName};
|
||||||
|
|
||||||
mod authorised;
|
mod authorisation;
|
||||||
mod list;
|
mod list;
|
||||||
mod parse;
|
mod parser;
|
||||||
mod register;
|
mod register;
|
||||||
mod unregister;
|
mod unregister;
|
||||||
|
|
||||||
pub use authorised::is_authorised;
|
pub use authorisation::is_authorised;
|
||||||
pub use list::list;
|
pub use list::list;
|
||||||
pub use parse::parse_body;
|
pub use parser::parse_body;
|
||||||
pub use register::register;
|
pub use register::register;
|
||||||
pub use unregister::unregister;
|
pub use unregister::unregister;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub use authorised::sign_body;
|
pub use authorisation::sign_body;
|
||||||
|
|
||||||
pub fn headers(token: &ApiToken) -> kxio::network::NetRequestHeaders {
|
pub fn headers(token: &ApiToken) -> kxio::network::NetRequestHeaders {
|
||||||
use secrecy::ExposeSecret;
|
use secrecy::ExposeSecret;
|
||||||
|
|
Loading…
Reference in a new issue