From bf39475a16553683b9dd5c6dec927d6a3127900f Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Wed, 24 Sep 2025 16:03:07 +0200 Subject: [PATCH] trustpub/emails: Add support for GitLab configs --- src/controllers/trustpub/emails.rs | 94 +++++++++++++++++-- .../trustpub/github_configs/create/mod.rs | 6 +- .../trustpub/github_configs/delete/mod.rs | 6 +- ..._tests__config_created_email_gitlab-2.snap | 16 ++++ ...eated_email_gitlab_with_environment-2.snap | 16 ++++ ..._tests__config_deleted_email_gitlab-2.snap | 14 +++ ...leted_email_gitlab_with_environment-2.snap | 14 +++ .../trustpub_config_created/body.txt.j2 | 17 +++- .../trustpub_config_deleted/body.txt.j2 | 18 +++- 9 files changed, 182 insertions(+), 19 deletions(-) create mode 100644 src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_created_email_gitlab-2.snap create mode 100644 src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_created_email_gitlab_with_environment-2.snap create mode 100644 src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_deleted_email_gitlab-2.snap create mode 100644 src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_deleted_email_gitlab_with_environment-2.snap diff --git a/src/controllers/trustpub/emails.rs b/src/controllers/trustpub/emails.rs index e6e45d885e2..9f5c4374465 100644 --- a/src/controllers/trustpub/emails.rs +++ b/src/controllers/trustpub/emails.rs @@ -1,7 +1,14 @@ use crate::email::EmailMessage; -use crates_io_database::models::trustpub::GitHubConfig; +use crates_io_database::models::trustpub::{GitHubConfig, GitLabConfig}; use crates_io_database::models::{Crate, User}; +#[derive(Debug, Clone, Copy, serde::Serialize)] +#[serde(tag = "type")] +pub enum ConfigType<'a> { + GitHub(&'a GitHubConfig), + GitLab(&'a GitLabConfig), +} + #[derive(serde::Serialize)] pub struct ConfigCreatedEmail<'a> { /// The GitHub login of the email recipient. @@ -11,7 +18,7 @@ pub struct ConfigCreatedEmail<'a> { /// The crate for which the trusted publishing configuration was created. pub krate: &'a Crate, /// The trusted publishing configuration that was created. - pub saved_config: &'a GitHubConfig, + pub saved_config: ConfigType<'a>, } impl ConfigCreatedEmail<'_> { @@ -29,7 +36,7 @@ pub struct ConfigDeletedEmail<'a> { /// The crate for which the trusted publishing configuration was deleted. pub krate: &'a Crate, /// The trusted publishing configuration that was deleted. - pub config: &'a GitHubConfig, + pub config: ConfigType<'a>, } impl ConfigDeletedEmail<'_> { @@ -88,13 +95,26 @@ mod tests { } } + fn test_gitlab_config(environment: Option<&str>) -> GitLabConfig { + GitLabConfig { + id: 1, + created_at: Utc::now(), + crate_id: 1, + namespace_id: None, + namespace: "rust-lang".into(), + project: "my-crate".into(), + workflow_filepath: ".gitlab-ci.yml".into(), + environment: environment.map(String::from), + } + } + #[test] fn test_config_created_email() { let email = ConfigCreatedEmail { recipient: "octocat", auth_user: &test_user(), krate: &test_crate(), - saved_config: &test_github_config(None), + saved_config: ConfigType::GitHub(&test_github_config(None)), }; let rendered = assert_ok!(email.render()); @@ -108,7 +128,7 @@ mod tests { recipient: "octocat", auth_user: &test_user(), krate: &test_crate(), - saved_config: &test_github_config(Some("production")), + saved_config: ConfigType::GitHub(&test_github_config(Some("production"))), }; let rendered = assert_ok!(email.render()); @@ -122,7 +142,35 @@ mod tests { recipient: "team-member", auth_user: &test_user(), krate: &test_crate(), - saved_config: &test_github_config(None), + saved_config: ConfigType::GitHub(&test_github_config(None)), + }; + + let rendered = assert_ok!(email.render()); + assert_snapshot!(rendered.subject, @"crates.io: Trusted Publishing configuration added to my-crate"); + assert_snapshot!(rendered.body_text); + } + + #[test] + fn test_config_created_email_gitlab() { + let email = ConfigCreatedEmail { + recipient: "octocat", + auth_user: &test_user(), + krate: &test_crate(), + saved_config: ConfigType::GitLab(&test_gitlab_config(None)), + }; + + let rendered = assert_ok!(email.render()); + assert_snapshot!(rendered.subject, @"crates.io: Trusted Publishing configuration added to my-crate"); + assert_snapshot!(rendered.body_text); + } + + #[test] + fn test_config_created_email_gitlab_with_environment() { + let email = ConfigCreatedEmail { + recipient: "octocat", + auth_user: &test_user(), + krate: &test_crate(), + saved_config: ConfigType::GitLab(&test_gitlab_config(Some("production"))), }; let rendered = assert_ok!(email.render()); @@ -136,7 +184,7 @@ mod tests { recipient: "octocat", auth_user: &test_user(), krate: &test_crate(), - config: &test_github_config(None), + config: ConfigType::GitHub(&test_github_config(None)), }; let rendered = assert_ok!(email.render()); @@ -150,7 +198,7 @@ mod tests { recipient: "octocat", auth_user: &test_user(), krate: &test_crate(), - config: &test_github_config(Some("production")), + config: ConfigType::GitHub(&test_github_config(Some("production"))), }; let rendered = assert_ok!(email.render()); @@ -164,7 +212,35 @@ mod tests { recipient: "team-member", auth_user: &test_user(), krate: &test_crate(), - config: &test_github_config(None), + config: ConfigType::GitHub(&test_github_config(None)), + }; + + let rendered = assert_ok!(email.render()); + assert_snapshot!(rendered.subject, @"crates.io: Trusted Publishing configuration removed from my-crate"); + assert_snapshot!(rendered.body_text); + } + + #[test] + fn test_config_deleted_email_gitlab() { + let email = ConfigDeletedEmail { + recipient: "octocat", + auth_user: &test_user(), + krate: &test_crate(), + config: ConfigType::GitLab(&test_gitlab_config(None)), + }; + + let rendered = assert_ok!(email.render()); + assert_snapshot!(rendered.subject, @"crates.io: Trusted Publishing configuration removed from my-crate"); + assert_snapshot!(rendered.body_text); + } + + #[test] + fn test_config_deleted_email_gitlab_with_environment() { + let email = ConfigDeletedEmail { + recipient: "octocat", + auth_user: &test_user(), + krate: &test_crate(), + config: ConfigType::GitLab(&test_gitlab_config(Some("production"))), }; let rendered = assert_ok!(email.render()); diff --git a/src/controllers/trustpub/github_configs/create/mod.rs b/src/controllers/trustpub/github_configs/create/mod.rs index 4ef98783eea..525c874905a 100644 --- a/src/controllers/trustpub/github_configs/create/mod.rs +++ b/src/controllers/trustpub/github_configs/create/mod.rs @@ -1,7 +1,7 @@ use crate::app::AppState; use crate::auth::AuthCheck; use crate::controllers::krate::load_crate; -use crate::controllers::trustpub::emails::ConfigCreatedEmail; +use crate::controllers::trustpub::emails::{ConfigCreatedEmail, ConfigType}; use crate::controllers::trustpub::github_configs::json; use crate::util::errors::{AppResult, bad_request, forbidden, server_error}; use anyhow::Context; @@ -117,11 +117,13 @@ pub async fn create_trustpub_github_config( .collect::>(); for (recipient, email_address) in &recipients { + let saved_config = ConfigType::GitHub(&saved_config); + let context = ConfigCreatedEmail { recipient, auth_user, krate: &krate, - saved_config: &saved_config, + saved_config, }; if let Err(err) = send_notification_email(&state, email_address, context).await { diff --git a/src/controllers/trustpub/github_configs/delete/mod.rs b/src/controllers/trustpub/github_configs/delete/mod.rs index aaacf67fca8..94e81814e2d 100644 --- a/src/controllers/trustpub/github_configs/delete/mod.rs +++ b/src/controllers/trustpub/github_configs/delete/mod.rs @@ -1,6 +1,6 @@ use crate::app::AppState; use crate::auth::AuthCheck; -use crate::controllers::trustpub::emails::ConfigDeletedEmail; +use crate::controllers::trustpub::emails::{ConfigDeletedEmail, ConfigType}; use crate::util::errors::{AppResult, bad_request, not_found}; use anyhow::Context; use axum::extract::Path; @@ -82,11 +82,13 @@ pub async fn delete_trustpub_github_config( .collect::>(); for (recipient, email_address) in &recipients { + let config = ConfigType::GitHub(&config); + let context = ConfigDeletedEmail { recipient, auth_user, krate: &krate, - config: &config, + config, }; if let Err(err) = send_notification_email(&state, email_address, context).await { diff --git a/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_created_email_gitlab-2.snap b/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_created_email_gitlab-2.snap new file mode 100644 index 00000000000..939a97e3d0c --- /dev/null +++ b/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_created_email_gitlab-2.snap @@ -0,0 +1,16 @@ +--- +source: src/controllers/trustpub/emails.rs +expression: rendered.body_text +--- +Hello octocat! + +You added a new "Trusted Publishing" configuration for GitLab CI to your crate "my-crate". Trusted publishers act as trusted users and can publish new versions of the crate automatically. + +This configuration allows the workflow file at https://gitlab.com/rust-lang/my-crate/-/blob/HEAD/.gitlab-ci.yml to publish new versions of this crate. + +If you did not make this change and you think it was made maliciously, you can remove the configuration from the crate via the "Settings" tab on the crate's page. + +If you are unable to revert the change and need to do so, you can email help@crates.io for assistance. + +-- +The crates.io Team diff --git a/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_created_email_gitlab_with_environment-2.snap b/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_created_email_gitlab_with_environment-2.snap new file mode 100644 index 00000000000..4246f4be4b7 --- /dev/null +++ b/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_created_email_gitlab_with_environment-2.snap @@ -0,0 +1,16 @@ +--- +source: src/controllers/trustpub/emails.rs +expression: rendered.body_text +--- +Hello octocat! + +You added a new "Trusted Publishing" configuration for GitLab CI to your crate "my-crate". Trusted publishers act as trusted users and can publish new versions of the crate automatically. + +This configuration allows the workflow file at https://gitlab.com/rust-lang/my-crate/-/blob/HEAD/.gitlab-ci.yml to publish new versions of this crate. The workflow must use the `production` environment. + +If you did not make this change and you think it was made maliciously, you can remove the configuration from the crate via the "Settings" tab on the crate's page. + +If you are unable to revert the change and need to do so, you can email help@crates.io for assistance. + +-- +The crates.io Team diff --git a/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_deleted_email_gitlab-2.snap b/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_deleted_email_gitlab-2.snap new file mode 100644 index 00000000000..8b592c37f8e --- /dev/null +++ b/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_deleted_email_gitlab-2.snap @@ -0,0 +1,14 @@ +--- +source: src/controllers/trustpub/emails.rs +expression: rendered.body_text +--- +Hello octocat! + +You removed a "Trusted Publishing" configuration for GitLab CI from your crate "my-crate". + +The removed configuration was for the workflow file at https://gitlab.com/rust-lang/my-crate/-/blob/HEAD/.gitlab-ci.yml. + +If you did not make this change and you think it was made maliciously, you can email help@crates.io for assistance. + +-- +The crates.io Team diff --git a/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_deleted_email_gitlab_with_environment-2.snap b/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_deleted_email_gitlab_with_environment-2.snap new file mode 100644 index 00000000000..0c8550ad1c7 --- /dev/null +++ b/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_deleted_email_gitlab_with_environment-2.snap @@ -0,0 +1,14 @@ +--- +source: src/controllers/trustpub/emails.rs +expression: rendered.body_text +--- +Hello octocat! + +You removed a "Trusted Publishing" configuration for GitLab CI from your crate "my-crate". + +The removed configuration was for the workflow file at https://gitlab.com/rust-lang/my-crate/-/blob/HEAD/.gitlab-ci.yml using the `production` environment. + +If you did not make this change and you think it was made maliciously, you can email help@crates.io for assistance. + +-- +The crates.io Team diff --git a/src/email/templates/trustpub_config_created/body.txt.j2 b/src/email/templates/trustpub_config_created/body.txt.j2 index c124dde9ef6..6b22c2ced4e 100644 --- a/src/email/templates/trustpub_config_created/body.txt.j2 +++ b/src/email/templates/trustpub_config_created/body.txt.j2 @@ -1,18 +1,29 @@ {% extends "base.txt.j2" %} +{% if saved_config.type == "GitHub" %} + {% set ci_provider = "GitHub Actions" %} +{% elif saved_config.type == "GitLab" %} + {% set ci_provider = "GitLab CI" %} +{% endif %} + {% block content %} Hello {{ recipient }}! {% if recipient == auth_user.gh_login -%} -You added a new "Trusted Publishing" configuration for GitHub Actions to your crate "{{ krate.name }}". Trusted publishers act as trusted users and can publish new versions of the crate automatically. +You added a new "Trusted Publishing" configuration for {{ ci_provider }} to your crate "{{ krate.name }}". Trusted publishers act as trusted users and can publish new versions of the crate automatically. {%- else -%} -crates.io user {{ auth_user.gh_login }} added a new "Trusted Publishing" configuration for GitHub Actions to a crate that you manage ("{{ krate.name }}"). Trusted publishers act as trusted users and can publish new versions of the crate automatically. +crates.io user {{ auth_user.gh_login }} added a new "Trusted Publishing" configuration for {{ ci_provider }} to a crate that you manage ("{{ krate.name }}"). Trusted publishers act as trusted users and can publish new versions of the crate automatically. {%- endif %} +{% if saved_config.type == "GitHub" -%} This configuration allows the workflow file at https://github.com/{{ saved_config.repository_owner }}/{{ saved_config.repository_name }}/blob/HEAD/.github/workflows/{{ saved_config.workflow_filename }} to publish new versions of this crate. {%- if saved_config.environment %} The workflow must use the `{{ saved_config.environment }}` environment (https://github.com/{{ saved_config.repository_owner }}/{{ saved_config.repository_name }}/deployments/{{ saved_config.environment }}). {%- endif %} - +{% elif saved_config.type == "GitLab" -%} +This configuration allows the workflow file at https://gitlab.com/{{ saved_config.namespace }}/{{ saved_config.project }}/-/blob/HEAD/{{ saved_config.workflow_filepath }} to publish new versions of this crate. +{%- if saved_config.environment %} The workflow must use the `{{ saved_config.environment }}` environment. +{%- endif %} +{% endif %} If you did not make this change and you think it was made maliciously, you can remove the configuration from the crate via the "Settings" tab on the crate's page. If you are unable to revert the change and need to do so, you can email help@crates.io for assistance. diff --git a/src/email/templates/trustpub_config_deleted/body.txt.j2 b/src/email/templates/trustpub_config_deleted/body.txt.j2 index 761ab54e44d..ec9c3e9cddf 100644 --- a/src/email/templates/trustpub_config_deleted/body.txt.j2 +++ b/src/email/templates/trustpub_config_deleted/body.txt.j2 @@ -1,18 +1,30 @@ {% extends "base.txt.j2" %} +{% if config.type == "GitHub" %} + {% set ci_provider = "GitHub Actions" %} +{% elif config.type == "GitLab" %} + {% set ci_provider = "GitLab CI" %} +{% endif %} + {% block content %} Hello {{ recipient }}! {% if recipient == auth_user.gh_login -%} -You removed a "Trusted Publishing" configuration for GitHub Actions from your crate "{{ krate.name }}". +You removed a "Trusted Publishing" configuration for {{ ci_provider }} from your crate "{{ krate.name }}". {%- else -%} -crates.io user {{ auth_user.gh_login }} removed a "Trusted Publishing" configuration for GitHub Actions from a crate that you manage ("{{ krate.name }}"). +crates.io user {{ auth_user.gh_login }} removed a "Trusted Publishing" configuration for {{ ci_provider }} from a crate that you manage ("{{ krate.name }}"). {%- endif %} +{% if config.type == "GitHub" -%} The removed configuration was for the workflow file at https://github.com/{{ config.repository_owner }}/{{ config.repository_name }}/blob/HEAD/.github/workflows/{{ config.workflow_filename }} {%- if config.environment %} using the `{{ config.environment }}` environment {%- endif -%} . - +{% elif config.type == "GitLab" -%} +The removed configuration was for the workflow file at https://gitlab.com/{{ config.namespace }}/{{ config.project }}/-/blob/HEAD/{{ config.workflow_filepath }} +{%- if config.environment %} using the `{{ config.environment }}` environment +{%- endif -%} +. +{% endif %} If you did not make this change and you think it was made maliciously, you can email help@crates.io for assistance. {% endblock %}