From adfb66a9d81b84490a5002e1c015e47ee3f80cf1 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 30 Sep 2025 13:38:36 +0200 Subject: [PATCH 1/4] trustpub/tokens/exchange: Move `test_case_insensitive()` test to the top --- .../trustpub/tokens/exchange/github_tests.rs | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/controllers/trustpub/tokens/exchange/github_tests.rs b/src/controllers/trustpub/tokens/exchange/github_tests.rs index 5e376d14387..e3330f3e5ba 100644 --- a/src/controllers/trustpub/tokens/exchange/github_tests.rs +++ b/src/controllers/trustpub/tokens/exchange/github_tests.rs @@ -129,6 +129,27 @@ async fn test_happy_path_with_ignored_environment() -> anyhow::Result<()> { Ok(()) } +/// Check that the owner name, repository name, and environment are accepted in +/// a case-insensitive manner. +#[tokio::test(flavor = "multi_thread")] +async fn test_case_insensitive() -> anyhow::Result<()> { + let client = prepare_with_config(|c| c.environment = Some("Prod")).await?; + + let claims = FullGitHubClaims::builder() + .owner_id(OWNER_ID) + .owner_name("RUST-lanG") + .repository_name("foo-RS") + .workflow_filename(WORKFLOW_FILENAME) + .environment("PROD") + .build(); + + let body = claims.as_exchange_body()?; + let response = client.post::<()>(URL, body).await; + assert_snapshot!(response.status(), @"200 OK"); + + Ok(()) +} + #[tokio::test(flavor = "multi_thread")] async fn test_broken_jwt() -> anyhow::Result<()> { let client = prepare().await?; @@ -354,24 +375,3 @@ async fn test_wrong_environment() -> anyhow::Result<()> { Ok(()) } - -/// Check that the owner name, repository name, and environment are accepted in -/// a case-insensitive manner. -#[tokio::test(flavor = "multi_thread")] -async fn test_case_insensitive() -> anyhow::Result<()> { - let client = prepare_with_config(|c| c.environment = Some("Prod")).await?; - - let claims = FullGitHubClaims::builder() - .owner_id(OWNER_ID) - .owner_name("RUST-lanG") - .repository_name("foo-RS") - .workflow_filename(WORKFLOW_FILENAME) - .environment("PROD") - .build(); - - let body = claims.as_exchange_body()?; - let response = client.post::<()>(URL, body).await; - assert_snapshot!(response.status(), @"200 OK"); - - Ok(()) -} From 489096247605450103f6cdffbd7c1cca1abd85fd Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 30 Sep 2025 13:38:53 +0200 Subject: [PATCH 2/4] trustpub/tokens/exchange: Add test category comments --- .../trustpub/tokens/exchange/github_tests.rs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/controllers/trustpub/tokens/exchange/github_tests.rs b/src/controllers/trustpub/tokens/exchange/github_tests.rs index e3330f3e5ba..e5c6539f3c2 100644 --- a/src/controllers/trustpub/tokens/exchange/github_tests.rs +++ b/src/controllers/trustpub/tokens/exchange/github_tests.rs @@ -68,6 +68,10 @@ fn default_claims() -> FullGitHubClaims { .build() } +// ============================================================================ +// Success cases and token generation tests +// ============================================================================ + #[tokio::test(flavor = "multi_thread")] async fn test_happy_path() -> anyhow::Result<()> { let client = prepare().await?; @@ -150,6 +154,10 @@ async fn test_case_insensitive() -> anyhow::Result<()> { Ok(()) } +// ============================================================================ +// JWT decode and validation tests +// ============================================================================ + #[tokio::test(flavor = "multi_thread")] async fn test_broken_jwt() -> anyhow::Result<()> { let client = prepare().await?; @@ -270,6 +278,10 @@ async fn test_invalid_audience() -> anyhow::Result<()> { Ok(()) } +// ============================================================================ +// JTI replay prevention tests +// ============================================================================ + /// Test that OIDC tokens can only be exchanged once #[tokio::test(flavor = "multi_thread")] async fn test_token_reuse() -> anyhow::Result<()> { @@ -289,6 +301,10 @@ async fn test_token_reuse() -> anyhow::Result<()> { Ok(()) } +// ============================================================================ +// Repository parsing tests +// ============================================================================ + #[tokio::test(flavor = "multi_thread")] async fn test_invalid_repository() -> anyhow::Result<()> { let client = prepare().await?; @@ -304,6 +320,10 @@ async fn test_invalid_repository() -> anyhow::Result<()> { Ok(()) } +// ============================================================================ +// Workflow filename extraction tests +// ============================================================================ + #[tokio::test(flavor = "multi_thread")] async fn test_invalid_workflow() -> anyhow::Result<()> { let client = prepare().await?; @@ -319,6 +339,10 @@ async fn test_invalid_workflow() -> anyhow::Result<()> { Ok(()) } +// ============================================================================ +// Repository owner ID validation tests +// ============================================================================ + #[tokio::test(flavor = "multi_thread")] async fn test_invalid_owner_id() -> anyhow::Result<()> { let client = prepare().await?; @@ -334,6 +358,10 @@ async fn test_invalid_owner_id() -> anyhow::Result<()> { Ok(()) } +// ============================================================================ +// Config lookup tests +// ============================================================================ + #[tokio::test(flavor = "multi_thread")] async fn test_missing_config() -> anyhow::Result<()> { let (_app, client, _cookie) = TestApp::full() @@ -349,6 +377,10 @@ async fn test_missing_config() -> anyhow::Result<()> { Ok(()) } +// ============================================================================ +// Environment matching tests +// ============================================================================ + #[tokio::test(flavor = "multi_thread")] async fn test_missing_environment() -> anyhow::Result<()> { let client = prepare_with_config(|c| c.environment = Some("prod")).await?; From dc9f72ee9ad5abdd7b5d411ae3783e2f79753ebd Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 30 Sep 2025 13:40:54 +0200 Subject: [PATCH 3/4] trustpub/tokens/exchange: Add `repository_id` mismatch test --- .../trustpub/tokens/exchange/github_tests.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/controllers/trustpub/tokens/exchange/github_tests.rs b/src/controllers/trustpub/tokens/exchange/github_tests.rs index e5c6539f3c2..4ff1c87a013 100644 --- a/src/controllers/trustpub/tokens/exchange/github_tests.rs +++ b/src/controllers/trustpub/tokens/exchange/github_tests.rs @@ -377,6 +377,25 @@ async fn test_missing_config() -> anyhow::Result<()> { Ok(()) } +// ============================================================================ +// Repository owner ID verification (resurrection protection) tests +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_repository_owner_id_mismatch() -> anyhow::Result<()> { + let client = prepare_with_config(|c| { + c.repository_owner_id = 999; // Different from OWNER_ID (42) + }) + .await?; + + let body = default_claims().as_exchange_body()?; + let response = client.post::<()>(URL, body).await; + assert_snapshot!(response.status(), @"400 Bad Request"); + assert_snapshot!(response.json(), @r#"{"errors":[{"detail":"The Trusted Publishing config for repository `rust-lang/foo-rs` does not match the repository owner ID (42) in the JWT. Expected owner IDs: 999. Please recreate the Trusted Publishing config to update the repository owner ID."}]}"#); + + Ok(()) +} + // ============================================================================ // Environment matching tests // ============================================================================ From bc129f4d4821cd6ca1abf8b2028746fcd6f46d95 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 30 Sep 2025 13:41:05 +0200 Subject: [PATCH 4/4] trustpub/tokens/exchange: Add `workflow_filename` mismatch test --- .../trustpub/tokens/exchange/github_tests.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/controllers/trustpub/tokens/exchange/github_tests.rs b/src/controllers/trustpub/tokens/exchange/github_tests.rs index 4ff1c87a013..067622da2a6 100644 --- a/src/controllers/trustpub/tokens/exchange/github_tests.rs +++ b/src/controllers/trustpub/tokens/exchange/github_tests.rs @@ -396,6 +396,25 @@ async fn test_repository_owner_id_mismatch() -> anyhow::Result<()> { Ok(()) } +// ============================================================================ +// Workflow filename matching tests +// ============================================================================ + +#[tokio::test(flavor = "multi_thread")] +async fn test_workflow_filename_mismatch() -> anyhow::Result<()> { + let client = prepare_with_config(|c| { + c.workflow_filename = "different.yml"; + }) + .await?; + + let body = default_claims().as_exchange_body()?; + let response = client.post::<()>(URL, body).await; + assert_snapshot!(response.status(), @"400 Bad Request"); + assert_snapshot!(response.json(), @r#"{"errors":[{"detail":"The Trusted Publishing config for repository `rust-lang/foo-rs` does not match the workflow filename `publish.yml` in the JWT. Expected workflow filenames: `different.yml`"}]}"#); + + Ok(()) +} + // ============================================================================ // Environment matching tests // ============================================================================