Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 0 additions & 84 deletions ceres/src/api_service/mono_api_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +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 common::utils::parse_commit_msg;
use jupiter::storage::base_storage::StorageConnector;
use jupiter::storage::Storage;
use jupiter::utils::converter::generate_git_keep_with_timestamp;
Expand All @@ -58,8 +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 regex::Regex;

#[derive(Clone)]
pub struct MonoApiService {
Expand Down Expand Up @@ -590,87 +587,6 @@ impl MonoApiService {
Ok(res)
}

pub async fn verify_mr(&self, mr_link: &str) -> Result<HashMap<String, bool>, 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, user_id).await?;
// TODO: Temporarily disable GPG verification
let verified = false;
res.insert(commit.commit_id.clone(), verified);
Ok(res)
}

async fn extract_email(&self, s: &str) -> Option<String> {
let re = Regex::new(r"<\s*(?P<email>[^<>@\s]+@[^<>@\s]+)\s*>").unwrap();
re.captures(s)
.and_then(|c| c.get(1))
.map(|m| m.as_str().to_string())
}

// TODO: implement GPG
// async fn verify_commit_gpg_signature(
// &self,
// commit_content: &str,
// user_id: String,
// ) -> Result<bool, MegaError> {
// 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<bool, MegaError> {
// 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,
Expand Down
118 changes: 118 additions & 0 deletions ceres/src/merge_checker/gpg_signature_checker.rs
Original file line number Diff line number Diff line change
@@ -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<Storage>,
}

#[derive(Debug, Deserialize)]
pub(crate) struct GpgSignatureParams {
mr_to: String,
committer: String,
}

impl GpgSignatureParams {
fn from_value(v: &serde_json::Value) -> anyhow::Result<Self> {
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(&params.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<Value, MegaError> {
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<bool, MegaError> {
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<bool, MegaError> {
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<bool, MegaError> {
let (public_key, _) = SignedPublicKey::from_string(public_key)?;
let (signature, _) = StandaloneSignature::from_string(signature)?;

Ok(signature.verify(&public_key, message.as_bytes()).is_ok())
}
}
18 changes: 15 additions & 3 deletions ceres/src/merge_checker/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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
}

Expand Down
14 changes: 4 additions & 10 deletions jupiter/src/storage/gpg_storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ impl GpgStorage {
&self,
user_id: String,
gpg_content: String,
expires_days: Option<i32>,
) -> Result<gpg_key::Model, MegaError> {
let (pk, _headers) = SignedPublicKey::from_string(&gpg_content).map_err(|e| {
tracing::error!("{:?}", e);
Expand All @@ -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(),
Expand All @@ -57,13 +56,8 @@ impl GpgStorage {
Ok(key)
}

pub async fn add_gpg_key(
&self,
user_id: String,
gpg_content: String,
expired_at: Option<i32>,
) -> Result<(), MegaError> {
let key = self.create_key(user_id, gpg_content, expired_at)?;
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| {
tracing::error!("{:?}", e);
Expand Down
5 changes: 1 addition & 4 deletions mono/src/api/gpg/gpg_router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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, req.expires_days)
.await?;
state.gpg_stg().add_gpg_key(uid, req.gpg_content).await?;

Ok(Json(CommonResult::success(None)))
}
Expand Down
1 change: 0 additions & 1 deletion mono/src/api/gpg/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ use utoipa::ToSchema;
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct NewGpgRequest {
pub gpg_content: String,
pub expires_days: Option<i32>,
}

#[derive(Debug, Serialize, Deserialize, ToSchema)]
Expand Down
5 changes: 4 additions & 1 deletion mono/src/api/mr/model.rs
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@

#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, utoipa::ToSchema)]
pub struct VerifyMrPayload {
pub assignees: Vec<String>,
}
23 changes: 1 addition & 22 deletions mono/src/api/mr/mr_router.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use std::collections::HashMap;
use std::path::PathBuf;

use axum::{
Expand Down Expand Up @@ -45,8 +44,7 @@ pub fn routers() -> OpenApiRouter<MonoApiServiceState> {
.routes(routes!(save_comment))
.routes(routes!(labels))
.routes(routes!(assignees))
.routes(routes!(edit_title))
.routes(routes!(verify_mr_signature)),
.routes(routes!(edit_title)),
)
}

Expand Down Expand Up @@ -492,25 +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",
responses(
(status = 200, body = CommonResult<HashMap<String, bool>>, content_type = "application/json")
),
tag = MR_TAG
)]
async fn verify_mr_signature(
Path(link): Path<String>,
state: State<MonoApiServiceState>,
) -> Result<Json<CommonResult<HashMap<String, bool>>>, ApiError> {
let res = state.monorepo().verify_mr(&link).await?;
Ok(Json(CommonResult::success(Some(res))))
}

fn build_forest(paths: Vec<String>) -> Vec<MuiTreeNode> {
let mut roots: Vec<MuiTreeNode> = Vec::new();

Expand Down
Loading