From 7fd68df18418ab342a98abfd4197b15cccc2f923 Mon Sep 17 00:00:00 2001 From: AidCheng Date: Fri, 29 Aug 2025 13:04:26 +0100 Subject: [PATCH 1/6] [mono]FIX: upload full mui tree node Signed-off-by: AidCheng --- ceres/src/api_service/mono_api_service.rs | 8 ++++++-- mono/src/api/mr/mr_router.rs | 5 ++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/ceres/src/api_service/mono_api_service.rs b/ceres/src/api_service/mono_api_service.rs index 32e0cdb2e..5a6d30383 100644 --- a/ceres/src/api_service/mono_api_service.rs +++ b/ceres/src/api_service/mono_api_service.rs @@ -410,7 +410,7 @@ impl MonoApiService { &self, mr_link: &str, page: Pagination, - ) -> Result<(Vec, u64), GitError> { + ) -> Result<(Vec, Vec, u64), GitError> { let per_page = page.per_page as usize; let page_id = page.page as usize; @@ -433,6 +433,10 @@ impl MonoApiService { let sorted_changed_files = self .mr_files_list(old_blobs.clone(), new_blobs.clone()) .await?; + let file_paths: Vec = sorted_changed_files + .iter() + .map(|f| f.path().to_string_lossy().to_string()) + .collect(); // ensure page_id is within bounds let start = (page_id.saturating_sub(1)) * per_page; @@ -460,7 +464,7 @@ impl MonoApiService { // calculate total pages let total = sorted_changed_files.len().div_ceil(per_page); - Ok((diff_output, total as u64)) + Ok((diff_output, file_paths, total as u64)) } async fn get_diff_by_blobs( diff --git a/mono/src/api/mr/mr_router.rs b/mono/src/api/mr/mr_router.rs index bcc3ce9dc..8ffd139e1 100644 --- a/mono/src/api/mr/mr_router.rs +++ b/mono/src/api/mr/mr_router.rs @@ -307,13 +307,12 @@ async fn mr_files_changed_by_page( state: State, Json(json): Json>, ) -> Result>, ApiError> { - let (items, total) = state + let (items, changed_files_path, total) = state .monorepo() .paged_content_diff(&link, json.pagination) .await?; - let paths = items.iter().map(|i| i.path.clone()).collect(); - let mui_trees = build_forest(paths); + let mui_trees = build_forest(changed_files_path); let res = CommonResult::success(Some(FilesChangedPage { mui_trees, page: CommonPage { total, items }, From 0506c23a6d83e26da7a7c5389436c24098103301 Mon Sep 17 00:00:00 2001 From: AidCheng Date: Sat, 30 Aug 2025 00:43:08 +0100 Subject: [PATCH 2/6] [mono]UPDATE: More Detailed Backend Log and Fixed API route Signed-off-by: AidCheng --- jupiter/callisto/src/mod.rs | 2 +- jupiter/src/storage/gpg_storage.rs | 30 ++++++++++++++++++++---------- mono/src/api/gpg/gpg_router.rs | 29 +++++++++++++++-------------- 3 files changed, 36 insertions(+), 25 deletions(-) diff --git a/jupiter/callisto/src/mod.rs b/jupiter/callisto/src/mod.rs index 13380b35b..e70edbe81 100644 --- a/jupiter/callisto/src/mod.rs +++ b/jupiter/callisto/src/mod.rs @@ -5,6 +5,7 @@ pub mod prelude; pub mod access_token; pub mod builds; pub mod check_result; +pub mod entity_ext; pub mod git_blob; pub mod git_commit; pub mod git_issue; @@ -43,4 +44,3 @@ pub mod sea_orm_active_enums; pub mod ssh_keys; pub mod user; pub mod vault; -pub mod entity_ext; diff --git a/jupiter/src/storage/gpg_storage.rs b/jupiter/src/storage/gpg_storage.rs index 541e4aa60..0f9ecb8db 100644 --- a/jupiter/src/storage/gpg_storage.rs +++ b/jupiter/src/storage/gpg_storage.rs @@ -1,5 +1,6 @@ use crate::storage::base_storage::BaseStorage; use crate::storage::base_storage::StorageConnector; +use anyhow::anyhow; use callisto::entity_ext::generate_id; use callisto::gpg_key; use common::errors::MegaError; @@ -32,7 +33,11 @@ impl GpgStorage { gpg_content: String, expires_days: Option, ) -> Result { - let (pk, _headers) = SignedPublicKey::from_string(&gpg_content)?; + let (pk, _headers) = SignedPublicKey::from_string(&gpg_content).map_err(|e| { + tracing::error!("{:?}", e); + MegaError::new(anyhow!("Failed to parse GPG key, please check format"), 1) + })?; + let key_id = format!("{:016X}", pk.key_id()); let fingerprint = format!("{:?}", pk.fingerprint()); let created_at = chrono::Utc::now().naive_utc(); @@ -60,13 +65,10 @@ impl GpgStorage { ) -> Result<(), MegaError> { let key = self.create_key(user_id, gpg_content, expired_at)?; let a_model = key.into_active_model(); - a_model.insert(self.get_connection()).await?; - Ok(()) - } - - pub async fn save_gpg_key(&self, key: gpg_key::Model) -> Result<(), MegaError> { - let a_model = key.into_active_model(); - a_model.insert(self.get_connection()).await?; + a_model.insert(self.get_connection()).await.map_err(|e| { + tracing::error!("{:?}", e); + MegaError::new(anyhow!("Failed to save GPG key"), 1) + })?; Ok(()) } @@ -75,7 +77,11 @@ impl GpgStorage { .filter(gpg_key::Column::UserId.eq(user_id)) .filter(gpg_key::Column::KeyId.eq(key_id)) .exec(self.get_connection()) - .await?; + .await + .map_err(|e| { + tracing::error!("{:?}", e); + MegaError::new(anyhow!("Failed to delete GPG key"), 1) + })?; Ok(()) } @@ -83,7 +89,11 @@ impl GpgStorage { let res: Vec = gpg_key::Entity::find() .filter(gpg_key::Column::UserId.eq(user_id)) .all(self.get_connection()) - .await?; + .await + .map_err(|e| { + tracing::error!("{:?}", e); + MegaError::new(anyhow!("Failed to get GPG keys"), 1) + })?; Ok(res) } } diff --git a/mono/src/api/gpg/gpg_router.rs b/mono/src/api/gpg/gpg_router.rs index 466387f41..86ba4cb1d 100644 --- a/mono/src/api/gpg/gpg_router.rs +++ b/mono/src/api/gpg/gpg_router.rs @@ -33,10 +33,9 @@ async fn remove_gpg( state: State, Json(req): Json, ) -> Result>, ApiError> { - let _ = state - .gpg_stg() - .remove_gpg_key(user.campsite_user_id, req.key_id) - .await; + // let uid = "exampleid".to_string(); + let uid = user.campsite_user_id.clone(); + state.gpg_stg().remove_gpg_key(uid, req.key_id).await?; Ok(Json(CommonResult::success(None))) } @@ -54,18 +53,19 @@ async fn add_gpg( state: State, Json(req): Json, ) -> Result>, ApiError> { - let _ = state + // let uid = "exampleid".to_string(); + let uid = user.campsite_user_id.clone(); + println!("Adding GPG key for user: {}", req.gpg_content.clone()); + state .gpg_stg() - .add_gpg_key(user.campsite_user_id, req.gpg_content, req.expires_days) - .await; + .add_gpg_key(uid, req.gpg_content, req.expires_days) + .await?; + Ok(Json(CommonResult::success(None))) } #[utoipa::path( get, - params( - ("id" = i64, description = "The user ID"), - ), - path = "/list/{id}", + path = "/list", responses( (status = 200, body = CommonResult>, content_type="application/json") ), @@ -75,14 +75,15 @@ async fn list_gpg( user: LoginUser, state: State, ) -> Result>>, ApiError> { - let user_id = user.campsite_user_id; - let raw_keys = state.gpg_stg().list_user_gpg(user_id.clone()).await; + // let uid = "exampleid".to_string(); + let uid = user.campsite_user_id; + let raw_keys = state.gpg_stg().list_user_gpg(uid.clone()).await; let res: Vec = raw_keys .into_iter() .flatten() .map(|k: Model| GpgKey { - user_id: user_id.clone(), + user_id: uid.clone(), key_id: k.key_id, fingerprint: k.fingerprint, created_at: k.created_at.and_utc(), From 26ecf9dfaa1774888f9da6d818e818c59c9af07c Mon Sep 17 00:00:00 2001 From: AidCheng Date: Wed, 3 Sep 2025 13:02:40 +0100 Subject: [PATCH 3/6] [mono]UPDATE:Verify Mr with Assignees Signed-off-by: AidCheng --- ceres/src/api_service/mono_api_service.rs | 98 ++++++++++++----------- mono/src/api/mr/model.rs | 3 + mono/src/api/mr/mr_router.rs | 4 +- 3 files changed, 56 insertions(+), 49 deletions(-) diff --git a/ceres/src/api_service/mono_api_service.rs b/ceres/src/api_service/mono_api_service.rs index dfb5cc8ee..2a8e91258 100644 --- a/ceres/src/api_service/mono_api_service.rs +++ b/ceres/src/api_service/mono_api_service.rs @@ -43,6 +43,7 @@ use crate::api_service::ApiHandler; use crate::model::git::CreateFileInfo; use crate::model::mr::MrDiffFile; use async_trait::async_trait; +use pgp::{Deserializable, SignedPublicKey, StandaloneSignature}; use callisto::sea_orm_active_enums::ConvTypeEnum; use callisto::{mega_blob, mega_mr, mega_tree, raw_blob}; use common::errors::MegaError; @@ -60,6 +61,7 @@ use neptune::model::diff_model::DiffItem; use neptune::neptune_engine::Diff; // use pgp::{Deserializable, SignedPublicKey, StandaloneSignature}; use regex::Regex; +use common::utils::parse_commit_msg; #[derive(Clone)] pub struct MonoApiService { @@ -590,7 +592,7 @@ impl MonoApiService { Ok(res) } - pub async fn verify_mr(&self, mr_link: &str) -> Result, MegaError> { + pub async fn verify_mr(&self, mr_link: &str, assignees: Vec) -> Result, MegaError> { let stg = self.storage.mr_storage(); let mr = stg.get_mr(mr_link).await?.ok_or_else(|| { MegaError::with_message(format!("Merge request not found: {mr_link}")) @@ -612,9 +614,7 @@ impl MonoApiService { .await? .unwrap() .id; - // let verified = self.verify_commit_gpg_signature(&content, user_id).await?; - // TODO: Temporarily disable GPG verification - let verified = false; + let verified = self.verify_commit_gpg_signature(&content, assignees).await?; res.insert(commit.commit_id.clone(), verified); Ok(res) } @@ -626,50 +626,52 @@ impl MonoApiService { .map(|m| m.as_str().to_string()) } - // TODO: implement GPG - // async fn verify_commit_gpg_signature( - // &self, - // commit_content: &str, - // user_id: String, - // ) -> Result { - // let (commit_msg, signature) = parse_commit_msg(commit_content); - // if signature.is_none() { - // return Ok(false); // No signature to verify - // } - // - // let sig_str = signature.unwrap(); - // - // // Remove "gpgsig " prefix if present - // let sig = sig_str - // .strip_prefix("gpgsig ") - // .map(|s| s.trim()) - // .unwrap_or(sig_str); - // - // let keys = self.storage.gpg_storage().list_user_gpg(user_id).await?; - // - // for key in keys { - // let verified = self - // .verify_signature_with_key(&key.public_key, sig, commit_msg) - // .await?; - // if verified { - // return Ok(true); // Signature verified successfully - // } - // } - // - // Ok(false) // No key could verify the signature - // } - // - // async fn verify_signature_with_key( - // &self, - // public_key: &str, - // signature: &str, - // message: &str, - // ) -> Result { - // let (public_key, _) = SignedPublicKey::from_string(public_key)?; - // let (signature, _) = StandaloneSignature::from_string(signature)?; - // - // Ok(signature.verify(&public_key, message.as_bytes()).is_ok()) - // } + async fn verify_commit_gpg_signature( + &self, + commit_content: &str, + assignees: Vec, + ) -> Result { + let (commit_msg, signature) = parse_commit_msg(commit_content); + if signature.is_none() { + return Ok(false); // No signature to verify + } + + let sig_str = signature.unwrap(); + + // Remove "gpgsig " prefix if present + let sig = sig_str + .strip_prefix("gpgsig ") + .map(|s| s.trim()) + .unwrap_or(sig_str); + + let mut keys = Vec::new(); + for assignee in assignees { + keys.extend(self.storage.gpg_storage().list_user_gpg(assignee).await?); + } + + for key in keys { + let verified = self + .verify_signature_with_key(&key.public_key, sig, commit_msg) + .await?; + if verified { + return Ok(true); // Signature verified successfully + } + } + + Ok(false) // No key could verify the signature + } + + async fn verify_signature_with_key( + &self, + public_key: &str, + signature: &str, + message: &str, + ) -> Result { + let (public_key, _) = SignedPublicKey::from_string(public_key)?; + let (signature, _) = StandaloneSignature::from_string(signature)?; + + Ok(signature.verify(&public_key, message.as_bytes()).is_ok()) + } pub async fn get_commit_blobs( &self, diff --git a/mono/src/api/mr/model.rs b/mono/src/api/mr/model.rs index 8b1378917..816acff37 100644 --- a/mono/src/api/mr/model.rs +++ b/mono/src/api/mr/model.rs @@ -1 +1,4 @@ +pub struct VerifyMergePayload { + pub assignees: Vec, +} \ No newline at end of file diff --git a/mono/src/api/mr/mr_router.rs b/mono/src/api/mr/mr_router.rs index 8ffd139e1..48a226d1b 100644 --- a/mono/src/api/mr/mr_router.rs +++ b/mono/src/api/mr/mr_router.rs @@ -498,6 +498,7 @@ async fn assignees( ("link", description = "MR link"), ), path = "/{link}/verify-signature", + request_body = VerifyMrPayload responses( (status = 200, body = CommonResult>, content_type = "application/json") ), @@ -506,8 +507,9 @@ async fn assignees( async fn verify_mr_signature( Path(link): Path, state: State, + payload: Json, ) -> Result>>, ApiError> { - let res = state.monorepo().verify_mr(&link).await?; + let res = state.monorepo().verify_mr(&link, payload.assignees).await?; Ok(Json(CommonResult::success(Some(res)))) } From e80141c5e0e1b5503bb84f01c0e8e75565b56f5b Mon Sep 17 00:00:00 2001 From: AidCheng Date: Wed, 3 Sep 2025 13:16:38 +0100 Subject: [PATCH 4/6] [mono]UPDATE: fmt Signed-off-by: AidCheng --- ceres/src/api_service/mono_api_service.rs | 28 ++++++++++++++--------- mono/src/api/mr/model.rs | 6 ++--- mono/src/api/mr/mr_router.rs | 8 +++++-- 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/ceres/src/api_service/mono_api_service.rs b/ceres/src/api_service/mono_api_service.rs index 2a8e91258..070c625f7 100644 --- a/ceres/src/api_service/mono_api_service.rs +++ b/ceres/src/api_service/mono_api_service.rs @@ -43,11 +43,11 @@ use crate::api_service::ApiHandler; use crate::model::git::CreateFileInfo; use crate::model::mr::MrDiffFile; use async_trait::async_trait; -use pgp::{Deserializable, SignedPublicKey, StandaloneSignature}; use callisto::sea_orm_active_enums::ConvTypeEnum; use callisto::{mega_blob, mega_mr, mega_tree, raw_blob}; use common::errors::MegaError; use common::model::Pagination; +use pgp::{Deserializable, SignedPublicKey, StandaloneSignature}; // use common::utils::parse_commit_msg; use jupiter::storage::base_storage::StorageConnector; use jupiter::storage::Storage; @@ -60,8 +60,8 @@ use mercury::internal::object::tree::{Tree, TreeItem, TreeItemMode}; use neptune::model::diff_model::DiffItem; use neptune::neptune_engine::Diff; // use pgp::{Deserializable, SignedPublicKey, StandaloneSignature}; -use regex::Regex; use common::utils::parse_commit_msg; +use regex::Regex; #[derive(Clone)] pub struct MonoApiService { @@ -592,7 +592,11 @@ impl MonoApiService { Ok(res) } - pub async fn verify_mr(&self, mr_link: &str, assignees: Vec) -> Result, MegaError> { + pub async fn verify_mr( + &self, + mr_link: &str, + assignees: Vec, + ) -> Result, MegaError> { let stg = self.storage.mr_storage(); let mr = stg.get_mr(mr_link).await?.ok_or_else(|| { MegaError::with_message(format!("Merge request not found: {mr_link}")) @@ -614,7 +618,9 @@ impl MonoApiService { .await? .unwrap() .id; - let verified = self.verify_commit_gpg_signature(&content, assignees).await?; + let verified = self + .verify_commit_gpg_signature(&content, assignees) + .await?; res.insert(commit.commit_id.clone(), verified); Ok(res) } @@ -635,20 +641,20 @@ impl MonoApiService { if signature.is_none() { return Ok(false); // No signature to verify } - + let sig_str = signature.unwrap(); - + // Remove "gpgsig " prefix if present let sig = sig_str .strip_prefix("gpgsig ") .map(|s| s.trim()) .unwrap_or(sig_str); - + let mut keys = Vec::new(); for assignee in assignees { keys.extend(self.storage.gpg_storage().list_user_gpg(assignee).await?); } - + for key in keys { let verified = self .verify_signature_with_key(&key.public_key, sig, commit_msg) @@ -657,10 +663,10 @@ impl MonoApiService { return Ok(true); // Signature verified successfully } } - + Ok(false) // No key could verify the signature } - + async fn verify_signature_with_key( &self, public_key: &str, @@ -669,7 +675,7 @@ impl MonoApiService { ) -> Result { let (public_key, _) = SignedPublicKey::from_string(public_key)?; let (signature, _) = StandaloneSignature::from_string(signature)?; - + Ok(signature.verify(&public_key, message.as_bytes()).is_ok()) } diff --git a/mono/src/api/mr/model.rs b/mono/src/api/mr/model.rs index 816acff37..8c770a489 100644 --- a/mono/src/api/mr/model.rs +++ b/mono/src/api/mr/model.rs @@ -1,4 +1,4 @@ - -pub struct VerifyMergePayload { +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, utoipa::ToSchema)] +pub struct VerifyMrPayload { pub assignees: Vec, -} \ No newline at end of file +} diff --git a/mono/src/api/mr/mr_router.rs b/mono/src/api/mr/mr_router.rs index 48a226d1b..6662fc56c 100644 --- a/mono/src/api/mr/mr_router.rs +++ b/mono/src/api/mr/mr_router.rs @@ -14,6 +14,7 @@ use common::{ }; use jupiter::service::mr_service::MRService; +use crate::api::mr::model::VerifyMrPayload; use crate::api::{ api_common::{ self, @@ -498,7 +499,7 @@ async fn assignees( ("link", description = "MR link"), ), path = "/{link}/verify-signature", - request_body = VerifyMrPayload + request_body = VerifyMrPayload, responses( (status = 200, body = CommonResult>, content_type = "application/json") ), @@ -509,7 +510,10 @@ async fn verify_mr_signature( state: State, payload: Json, ) -> Result>>, ApiError> { - let res = state.monorepo().verify_mr(&link, payload.assignees).await?; + let res = state + .monorepo() + .verify_mr(&link, payload.assignees.clone()) + .await?; Ok(Json(CommonResult::success(Some(res)))) } From 8eccefeca93b3fecef0991916d105da768618211 Mon Sep 17 00:00:00 2001 From: AidCheng Date: Thu, 4 Sep 2025 13:01:02 +0100 Subject: [PATCH 5/6] [mono]UPDATE: REMOVED expire day requirement for GPGKey Signed-off-by: AidCheng --- jupiter/src/storage/gpg_storage.rs | 8 +++----- mono/src/api/gpg/gpg_router.rs | 2 +- mono/src/api/gpg/model.rs | 1 - 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/jupiter/src/storage/gpg_storage.rs b/jupiter/src/storage/gpg_storage.rs index 0f9ecb8db..511062fe4 100644 --- a/jupiter/src/storage/gpg_storage.rs +++ b/jupiter/src/storage/gpg_storage.rs @@ -31,7 +31,6 @@ impl GpgStorage { &self, user_id: String, gpg_content: String, - expires_days: Option, ) -> Result { let (pk, _headers) = SignedPublicKey::from_string(&gpg_content).map_err(|e| { tracing::error!("{:?}", e); @@ -40,8 +39,8 @@ impl GpgStorage { let key_id = format!("{:016X}", pk.key_id()); let fingerprint = format!("{:?}", pk.fingerprint()); - let created_at = chrono::Utc::now().naive_utc(); - let expires_at = expires_days.map(|days| created_at + chrono::Duration::days(days as i64)); + let created_at = pk.created_at().naive_utc(); + let expires_at = pk.expires_at().map(|t| t.naive_utc()); let key = gpg_key::Model { id: generate_id(), @@ -61,9 +60,8 @@ impl GpgStorage { &self, user_id: String, gpg_content: String, - expired_at: Option, ) -> Result<(), MegaError> { - let key = self.create_key(user_id, gpg_content, expired_at)?; + let key = self.create_key(user_id, gpg_content)?; let a_model = key.into_active_model(); a_model.insert(self.get_connection()).await.map_err(|e| { tracing::error!("{:?}", e); diff --git a/mono/src/api/gpg/gpg_router.rs b/mono/src/api/gpg/gpg_router.rs index 86ba4cb1d..9e32e7022 100644 --- a/mono/src/api/gpg/gpg_router.rs +++ b/mono/src/api/gpg/gpg_router.rs @@ -58,7 +58,7 @@ async fn add_gpg( println!("Adding GPG key for user: {}", req.gpg_content.clone()); state .gpg_stg() - .add_gpg_key(uid, req.gpg_content, req.expires_days) + .add_gpg_key(uid, req.gpg_content) .await?; Ok(Json(CommonResult::success(None))) diff --git a/mono/src/api/gpg/model.rs b/mono/src/api/gpg/model.rs index 2d8baf4f3..2e28c5960 100644 --- a/mono/src/api/gpg/model.rs +++ b/mono/src/api/gpg/model.rs @@ -6,7 +6,6 @@ use utoipa::ToSchema; #[derive(Debug, Serialize, Deserialize, ToSchema)] pub struct NewGpgRequest { pub gpg_content: String, - pub expires_days: Option, } #[derive(Debug, Serialize, Deserialize, ToSchema)] From 76ba9315edac3d407d26ee70e04df59dadd38044 Mon Sep 17 00:00:00 2001 From: AidCheng Date: Thu, 4 Sep 2025 22:03:04 +0100 Subject: [PATCH 6/6] [ceres]FEAT: GpgSignatureChecker Signed-off-by: AidCheng --- ceres/src/api_service/mono_api_service.rs | 92 -------------- .../merge_checker/gpg_signature_checker.rs | 118 ++++++++++++++++++ ceres/src/merge_checker/mod.rs | 18 ++- jupiter/src/storage/gpg_storage.rs | 6 +- mono/src/api/gpg/gpg_router.rs | 5 +- mono/src/api/mr/mr_router.rs | 29 +---- 6 files changed, 136 insertions(+), 132 deletions(-) create mode 100644 ceres/src/merge_checker/gpg_signature_checker.rs diff --git a/ceres/src/api_service/mono_api_service.rs b/ceres/src/api_service/mono_api_service.rs index 070c625f7..25c4d7646 100644 --- a/ceres/src/api_service/mono_api_service.rs +++ b/ceres/src/api_service/mono_api_service.rs @@ -47,8 +47,6 @@ use callisto::sea_orm_active_enums::ConvTypeEnum; use callisto::{mega_blob, mega_mr, mega_tree, raw_blob}; use common::errors::MegaError; use common::model::Pagination; -use pgp::{Deserializable, SignedPublicKey, StandaloneSignature}; -// use common::utils::parse_commit_msg; use jupiter::storage::base_storage::StorageConnector; use jupiter::storage::Storage; use jupiter::utils::converter::generate_git_keep_with_timestamp; @@ -59,9 +57,6 @@ use mercury::internal::object::commit::Commit; use mercury::internal::object::tree::{Tree, TreeItem, TreeItemMode}; use neptune::model::diff_model::DiffItem; use neptune::neptune_engine::Diff; -// use pgp::{Deserializable, SignedPublicKey, StandaloneSignature}; -use common::utils::parse_commit_msg; -use regex::Regex; #[derive(Clone)] pub struct MonoApiService { @@ -592,93 +587,6 @@ impl MonoApiService { Ok(res) } - pub async fn verify_mr( - &self, - mr_link: &str, - assignees: Vec, - ) -> Result, MegaError> { - let stg = self.storage.mr_storage(); - let mr = stg.get_mr(mr_link).await?.ok_or_else(|| { - MegaError::with_message(format!("Merge request not found: {mr_link}")) - })?; - - let commit = self - .storage - .mono_storage() - .get_commit_by_hash(&mr.to_hash) - .await? - .ok_or_else(|| MegaError::with_message("Commit not found"))?; - - let mut res = HashMap::new(); - let content = commit.content.clone().unwrap_or_default(); - let _user_id = self - .storage - .user_storage() - .find_user_by_email(&self.extract_email(&content).await.unwrap_or_default()) - .await? - .unwrap() - .id; - let verified = self - .verify_commit_gpg_signature(&content, assignees) - .await?; - res.insert(commit.commit_id.clone(), verified); - Ok(res) - } - - async fn extract_email(&self, s: &str) -> Option { - let re = Regex::new(r"<\s*(?P[^<>@\s]+@[^<>@\s]+)\s*>").unwrap(); - re.captures(s) - .and_then(|c| c.get(1)) - .map(|m| m.as_str().to_string()) - } - - async fn verify_commit_gpg_signature( - &self, - commit_content: &str, - assignees: Vec, - ) -> Result { - let (commit_msg, signature) = parse_commit_msg(commit_content); - if signature.is_none() { - return Ok(false); // No signature to verify - } - - let sig_str = signature.unwrap(); - - // Remove "gpgsig " prefix if present - let sig = sig_str - .strip_prefix("gpgsig ") - .map(|s| s.trim()) - .unwrap_or(sig_str); - - let mut keys = Vec::new(); - for assignee in assignees { - keys.extend(self.storage.gpg_storage().list_user_gpg(assignee).await?); - } - - for key in keys { - let verified = self - .verify_signature_with_key(&key.public_key, sig, commit_msg) - .await?; - if verified { - return Ok(true); // Signature verified successfully - } - } - - Ok(false) // No key could verify the signature - } - - async fn verify_signature_with_key( - &self, - public_key: &str, - signature: &str, - message: &str, - ) -> Result { - let (public_key, _) = SignedPublicKey::from_string(public_key)?; - let (signature, _) = StandaloneSignature::from_string(signature)?; - - Ok(signature.verify(&public_key, message.as_bytes()).is_ok()) - } - pub async fn get_commit_blobs( &self, commit_hash: &str, diff --git a/ceres/src/merge_checker/gpg_signature_checker.rs b/ceres/src/merge_checker/gpg_signature_checker.rs new file mode 100644 index 000000000..92461c9c3 --- /dev/null +++ b/ceres/src/merge_checker/gpg_signature_checker.rs @@ -0,0 +1,118 @@ +use crate::merge_checker::{CheckResult, CheckType, Checker}; +use async_trait::async_trait; +use common::errors::MegaError; +use common::utils::parse_commit_msg; +use jupiter::model::mr_dto::MrInfoDto; +use jupiter::storage::Storage; +use pgp::{Deserializable, SignedPublicKey, StandaloneSignature}; +use serde::Deserialize; +use serde_json::Value; +use std::sync::Arc; + +pub struct GpgSignatureChecker { + pub storage: Arc, +} + +#[derive(Debug, Deserialize)] +pub(crate) struct GpgSignatureParams { + mr_to: String, + committer: String, +} + +impl GpgSignatureParams { + fn from_value(v: &serde_json::Value) -> anyhow::Result { + Ok(serde_json::from_value(v.clone())?) + } +} + +#[async_trait] +impl Checker for GpgSignatureChecker { + async fn run(&self, params: &serde_json::Value) -> crate::merge_checker::CheckResult { + let params = GpgSignatureParams::from_value(params).expect("parse params err"); + let mut res = CheckResult { + check_type_code: CheckType::GpgSignature, + status: String::from("PENDING"), + message: String::new(), + }; + + let is_verified = self + .verify_mr(¶ms.mr_to, params.committer) + .await + .expect("cannot verify commits"); + if is_verified { + res.status = String::from("PASSED"); + } else { + res.status = String::from("FAILED"); + res.message = String::from("The commit GPG signature verification failed"); + } + + res + } + + async fn build_params(&self, mr_info: &MrInfoDto) -> Result { + Ok(serde_json::json!({ + "mr_to": mr_info.to_hash, + "committer": mr_info.username, + })) + } +} + +impl GpgSignatureChecker { + async fn verify_mr(&self, mr_to: &str, assignee: String) -> Result { + let commit = self + .storage + .mono_storage() + .get_commit_by_hash(mr_to) + .await? + .ok_or_else(|| MegaError::with_message("Commit not found"))?; + + let content = commit.content.clone().unwrap_or_default(); + let verified = self.verify_commit_gpg_signature(&content, assignee).await?; + + Ok(verified) + } + + async fn verify_commit_gpg_signature( + &self, + commit_content: &str, + assignee: String, + ) -> Result { + let (commit_msg, signature) = parse_commit_msg(commit_content); + if signature.is_none() { + return Ok(false); // No signature to verify + } + + let sig_str = signature.unwrap(); + + // Remove "gpgsig " prefix if present + let sig = sig_str + .strip_prefix("gpgsig ") + .map(|s| s.trim()) + .unwrap_or(sig_str); + + let keys = self.storage.gpg_storage().list_user_gpg(assignee).await?; + + for key in keys { + let verified = self + .verify_signature_with_key(&key.public_key, sig, commit_msg) + .await?; + if verified { + return Ok(true); // Signature verified successfully + } + } + + Ok(false) // No key could verify the signature + } + + async fn verify_signature_with_key( + &self, + public_key: &str, + signature: &str, + message: &str, + ) -> Result { + let (public_key, _) = SignedPublicKey::from_string(public_key)?; + let (signature, _) = StandaloneSignature::from_string(signature)?; + + Ok(signature.verify(&public_key, message.as_bytes()).is_ok()) + } +} diff --git a/ceres/src/merge_checker/mod.rs b/ceres/src/merge_checker/mod.rs index a91534b04..a5c2ff912 100644 --- a/ceres/src/merge_checker/mod.rs +++ b/ceres/src/merge_checker/mod.rs @@ -4,12 +4,13 @@ use async_trait::async_trait; use serde::Serialize; use utoipa::ToSchema; +use crate::merge_checker::gpg_signature_checker::GpgSignatureChecker; +use crate::merge_checker::mr_sync_checker::MrSyncChecker; use callisto::{check_result, sea_orm_active_enums::CheckTypeEnum}; use common::errors::MegaError; use jupiter::{model::mr_dto::MrInfoDto, storage::Storage}; -use crate::merge_checker::mr_sync_checker::MrSyncChecker; - +mod gpg_signature_checker; pub mod mr_sync_checker; #[async_trait] @@ -105,7 +106,18 @@ impl CheckerRegistry { storage: storage.clone(), username, }; - r.register(CheckType::MrSync, Box::new(MrSyncChecker { storage })); + r.register( + CheckType::MrSync, + Box::new(MrSyncChecker { + storage: storage.clone(), + }), + ); + r.register( + CheckType::GpgSignature, + Box::new(GpgSignatureChecker { + storage: storage.clone(), + }), + ); r } diff --git a/jupiter/src/storage/gpg_storage.rs b/jupiter/src/storage/gpg_storage.rs index 511062fe4..c1ce76888 100644 --- a/jupiter/src/storage/gpg_storage.rs +++ b/jupiter/src/storage/gpg_storage.rs @@ -56,11 +56,7 @@ impl GpgStorage { Ok(key) } - pub async fn add_gpg_key( - &self, - user_id: String, - gpg_content: String, - ) -> Result<(), MegaError> { + pub async fn add_gpg_key(&self, user_id: String, gpg_content: String) -> Result<(), MegaError> { let key = self.create_key(user_id, gpg_content)?; let a_model = key.into_active_model(); a_model.insert(self.get_connection()).await.map_err(|e| { diff --git a/mono/src/api/gpg/gpg_router.rs b/mono/src/api/gpg/gpg_router.rs index 9e32e7022..0343dbf91 100644 --- a/mono/src/api/gpg/gpg_router.rs +++ b/mono/src/api/gpg/gpg_router.rs @@ -56,10 +56,7 @@ async fn add_gpg( // let uid = "exampleid".to_string(); let uid = user.campsite_user_id.clone(); println!("Adding GPG key for user: {}", req.gpg_content.clone()); - state - .gpg_stg() - .add_gpg_key(uid, req.gpg_content) - .await?; + state.gpg_stg().add_gpg_key(uid, req.gpg_content).await?; Ok(Json(CommonResult::success(None))) } diff --git a/mono/src/api/mr/mr_router.rs b/mono/src/api/mr/mr_router.rs index ec93b3f66..bbb2f6fe2 100644 --- a/mono/src/api/mr/mr_router.rs +++ b/mono/src/api/mr/mr_router.rs @@ -1,4 +1,3 @@ -use std::collections::HashMap; use std::path::PathBuf; use axum::{ @@ -14,7 +13,6 @@ use common::{ }; use jupiter::service::mr_service::MRService; -use crate::api::mr::model::VerifyMrPayload; use crate::api::{ api_common::{ self, @@ -46,8 +44,7 @@ pub fn routers() -> OpenApiRouter { .routes(routes!(save_comment)) .routes(routes!(labels)) .routes(routes!(assignees)) - .routes(routes!(edit_title)) - .routes(routes!(verify_mr_signature)), + .routes(routes!(edit_title)), ) } @@ -493,30 +490,6 @@ async fn assignees( api_common::label_assignee::assignees_update(user, state, payload, String::from("mr")).await } -#[utoipa::path( - get, - params( - ("link", description = "MR link"), - ), - path = "/{link}/verify-signature", - request_body = VerifyMrPayload, - responses( - (status = 200, body = CommonResult>, content_type = "application/json") - ), - tag = MR_TAG -)] -async fn verify_mr_signature( - Path(link): Path, - state: State, - payload: Json, -) -> Result>>, ApiError> { - let res = state - .monorepo() - .verify_mr(&link, payload.assignees.clone()) - .await?; - Ok(Json(CommonResult::success(Some(res)))) -} - fn build_forest(paths: Vec) -> Vec { let mut roots: Vec = Vec::new();