Skip to content

Commit 66b89b2

Browse files
authored
trustpub: Implement DELETE /api/v1/trusted_publishing/gitlab_configs API endpoint (#12285)
1 parent 9851286 commit 66b89b2

10 files changed

+522
-0
lines changed
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
use crate::app::AppState;
2+
use crate::auth::AuthCheck;
3+
use crate::controllers::trustpub::emails::{ConfigDeletedEmail, ConfigType};
4+
use crate::util::errors::{AppResult, bad_request, not_found};
5+
use anyhow::Context;
6+
use axum::extract::Path;
7+
use crates_io_database::models::token::EndpointScope;
8+
use crates_io_database::models::trustpub::GitLabConfig;
9+
use crates_io_database::models::{Crate, OwnerKind};
10+
use crates_io_database::schema::{crate_owners, crates, emails, trustpub_configs_gitlab, users};
11+
use diesel::prelude::*;
12+
use diesel_async::RunQueryDsl;
13+
use http::StatusCode;
14+
use http::request::Parts;
15+
use tracing::warn;
16+
17+
/// Delete Trusted Publishing configuration for GitLab CI/CD.
18+
#[utoipa::path(
19+
delete,
20+
path = "/api/v1/trusted_publishing/gitlab_configs/{id}",
21+
params(
22+
("id" = i32, Path, description = "ID of the Trusted Publishing configuration"),
23+
),
24+
security(("cookie" = []), ("api_token" = [])),
25+
tag = "trusted_publishing",
26+
responses((status = 204, description = "Successful Response")),
27+
)]
28+
pub async fn delete_trustpub_gitlab_config(
29+
state: AppState,
30+
Path(id): Path<i32>,
31+
parts: Parts,
32+
) -> AppResult<StatusCode> {
33+
let mut conn = state.db_write().await?;
34+
35+
// First, find the config and crate to get the crate name for scope validation
36+
let (config, krate) = trustpub_configs_gitlab::table
37+
.inner_join(crates::table)
38+
.filter(trustpub_configs_gitlab::id.eq(id))
39+
.select((GitLabConfig::as_select(), Crate::as_select()))
40+
.first::<(GitLabConfig, Crate)>(&mut conn)
41+
.await
42+
.optional()?
43+
.ok_or_else(not_found)?;
44+
45+
let auth = AuthCheck::default()
46+
.with_endpoint_scope(EndpointScope::TrustedPublishing)
47+
.for_crate(&krate.name)
48+
.check(&parts, &mut conn)
49+
.await?;
50+
let auth_user = auth.user();
51+
52+
// Load all crate owners for the given crate ID
53+
let user_owners = crate_owners::table
54+
.filter(crate_owners::crate_id.eq(config.crate_id))
55+
.filter(crate_owners::deleted.eq(false))
56+
.filter(crate_owners::owner_kind.eq(OwnerKind::User))
57+
.inner_join(users::table)
58+
.inner_join(emails::table.on(users::id.eq(emails::user_id)))
59+
.select((users::id, users::gh_login, emails::email, emails::verified))
60+
.load::<(i32, String, String, bool)>(&mut conn)
61+
.await?;
62+
63+
// Check if the authenticated user is an owner of the crate
64+
if !user_owners.iter().any(|owner| owner.0 == auth_user.id) {
65+
return Err(bad_request("You are not an owner of this crate"));
66+
}
67+
68+
// Delete the configuration from the database
69+
diesel::delete(trustpub_configs_gitlab::table.filter(trustpub_configs_gitlab::id.eq(id)))
70+
.execute(&mut conn)
71+
.await?;
72+
73+
// Send notification emails to crate owners
74+
75+
let recipients = user_owners
76+
.into_iter()
77+
.filter(|(_, _, _, verified)| *verified)
78+
.map(|(_, login, email, _)| (login, email))
79+
.collect::<Vec<_>>();
80+
81+
for (recipient, email_address) in &recipients {
82+
let config = ConfigType::GitLab(&config);
83+
84+
let context = ConfigDeletedEmail {
85+
recipient,
86+
auth_user,
87+
krate: &krate,
88+
config,
89+
};
90+
91+
if let Err(err) = send_notification_email(&state, email_address, context).await {
92+
warn!("Failed to send trusted publishing notification to {email_address}: {err}");
93+
}
94+
}
95+
96+
Ok(StatusCode::NO_CONTENT)
97+
}
98+
99+
async fn send_notification_email(
100+
state: &AppState,
101+
email_address: &str,
102+
context: ConfigDeletedEmail<'_>,
103+
) -> anyhow::Result<()> {
104+
let email = context.render();
105+
let email = email.context("Failed to render email template")?;
106+
107+
state
108+
.emails
109+
.send(email_address, email)
110+
.await
111+
.context("Failed to send email")
112+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
pub mod create;
2+
pub mod delete;
23
pub mod json;

src/router.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ pub fn build_axum_router(state: AppState) -> Router<()> {
100100
))
101101
.routes(routes!(
102102
trustpub::gitlab_configs::create::create_trustpub_gitlab_config,
103+
trustpub::gitlab_configs::delete::delete_trustpub_gitlab_config,
103104
))
104105
.split_for_parts();
105106

0 commit comments

Comments
 (0)