Skip to content

Commit a3318cb

Browse files
committed
trustpub: Implement FullGitHubClaims struct for testing purposes
1 parent 4bfd29a commit a3318cb

File tree

5 files changed

+166
-1
lines changed

5 files changed

+166
-1
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/crates_io_trustpub/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ edition = "2024"
88
workspace = true
99

1010
[features]
11-
test-helpers = ["dep:mockall", "dep:serde_json"]
11+
test-helpers = ["dep:bon", "dep:mockall", "dep:serde_json"]
1212

1313
[dependencies]
1414
anyhow = "=1.0.98"
1515
async-trait = "=0.1.88"
16+
bon = { version = "=3.6.3", optional = true }
1617
chrono = { version = "=0.4.41", features = ["serde"] }
1718
jsonwebtoken = "=9.3.1"
1819
mockall = { version = "=0.13.1", optional = true }
@@ -24,6 +25,7 @@ thiserror = "=2.0.12"
2425
tokio = { version = "=1.45.0", features = ["sync"] }
2526

2627
[dev-dependencies]
28+
bon = "=3.6.3"
2729
claims = "=0.8.0"
2830
insta = { version = "=1.43.1", features = ["json", "redactions"] }
2931
mockito = "=1.7.0"

crates/crates_io_trustpub/src/github/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
mod claims;
2+
#[cfg(any(test, feature = "test-helpers"))]
3+
pub mod test_helpers;
24
pub mod validation;
35
mod workflows;
46

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
source: crates/crates_io_trustpub/src/github/test_helpers.rs
3+
expression: claims
4+
---
5+
{
6+
"iss": "https://token.actions.githubusercontent.com",
7+
"nbf": "[timestamp]",
8+
"exp": "[timestamp]",
9+
"iat": "[timestamp]",
10+
"jti": "example-id",
11+
"sub": "repo:octocat/hello-world",
12+
"aud": "crates.io",
13+
"ref": "refs/heads/main",
14+
"sha": "example-sha",
15+
"repository": "octocat/hello-world",
16+
"repository_owner": "octocat",
17+
"actor_id": "12",
18+
"repository_visibility": "private",
19+
"repository_id": "74",
20+
"repository_owner_id": "123",
21+
"run_id": "example-run-id",
22+
"run_number": "10",
23+
"run_attempt": "2",
24+
"runner_environment": "github-hosted",
25+
"actor": "octocat",
26+
"workflow": "example-workflow",
27+
"head_ref": "",
28+
"base_ref": "",
29+
"event_name": "workflow_dispatch",
30+
"ref_type": "branch",
31+
"workflow_ref": "octocat/hello-world/.github/workflows/ci.yml@refs/heads/main"
32+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
use crate::github::GITHUB_ISSUER_URL;
2+
use crate::test_keys::encode_for_testing;
3+
use bon::bon;
4+
use serde_json::json;
5+
6+
pub const AUDIENCE: &str = "crates.io";
7+
8+
/// A struct representing all the claims in a GitHub Actions OIDC token.
9+
///
10+
/// This struct is used to create a JWT for testing purposes.
11+
#[derive(Debug, serde::Serialize)]
12+
pub struct FullGitHubClaims {
13+
pub iss: String,
14+
pub nbf: i64,
15+
pub exp: i64,
16+
pub iat: i64,
17+
pub jti: String,
18+
pub sub: String,
19+
pub aud: String,
20+
21+
#[serde(skip_serializing_if = "Option::is_none")]
22+
pub environment: Option<String>,
23+
#[serde(rename = "ref")]
24+
pub r#ref: String,
25+
pub sha: String,
26+
pub repository: String,
27+
pub repository_owner: String,
28+
pub actor_id: String,
29+
pub repository_visibility: String,
30+
pub repository_id: String,
31+
pub repository_owner_id: String,
32+
pub run_id: String,
33+
pub run_number: String,
34+
pub run_attempt: String,
35+
pub runner_environment: String,
36+
pub actor: String,
37+
pub workflow: String,
38+
pub head_ref: String,
39+
pub base_ref: String,
40+
pub event_name: String,
41+
pub ref_type: String,
42+
pub workflow_ref: String,
43+
}
44+
45+
#[bon]
46+
impl FullGitHubClaims {
47+
#[builder]
48+
pub fn new(
49+
owner_id: i32,
50+
owner_name: &str,
51+
repository_name: &str,
52+
workflow_filename: &str,
53+
environment: Option<&str>,
54+
) -> Self {
55+
let now = chrono::Utc::now().timestamp();
56+
57+
Self {
58+
iss: GITHUB_ISSUER_URL.into(),
59+
nbf: now,
60+
iat: now,
61+
exp: now + 30 * 60,
62+
jti: "example-id".into(),
63+
sub: format!("repo:{owner_name}/{repository_name}"),
64+
aud: AUDIENCE.into(),
65+
66+
environment: environment.map(|s| s.into()),
67+
r#ref: "refs/heads/main".into(),
68+
sha: "example-sha".into(),
69+
repository: format!("{owner_name}/{repository_name}"),
70+
repository_owner: owner_name.into(),
71+
actor_id: "12".into(),
72+
repository_visibility: "private".into(),
73+
repository_id: "74".into(),
74+
repository_owner_id: owner_id.to_string(),
75+
run_id: "example-run-id".into(),
76+
run_number: "10".into(),
77+
run_attempt: "2".into(),
78+
runner_environment: "github-hosted".into(),
79+
actor: "octocat".into(),
80+
workflow: "example-workflow".into(),
81+
head_ref: "".into(),
82+
base_ref: "".into(),
83+
event_name: "workflow_dispatch".into(),
84+
ref_type: "branch".into(),
85+
workflow_ref: format!(
86+
"{owner_name}/{repository_name}/.github/workflows/{workflow_filename}@refs/heads/main"
87+
),
88+
}
89+
}
90+
91+
pub fn encoded(&self) -> anyhow::Result<String> {
92+
Ok(encode_for_testing(self)?)
93+
}
94+
95+
pub fn as_exchange_body(&self) -> anyhow::Result<String> {
96+
let jwt = self.encoded()?;
97+
Ok(serde_json::to_string(&json!({ "jwt": jwt }))?)
98+
}
99+
}
100+
101+
#[cfg(test)]
102+
mod tests {
103+
use super::*;
104+
use claims::assert_ok;
105+
use insta::assert_json_snapshot;
106+
107+
#[test]
108+
fn test_github_claims() {
109+
let claims = FullGitHubClaims::builder()
110+
.owner_id(123)
111+
.owner_name("octocat")
112+
.repository_name("hello-world")
113+
.workflow_filename("ci.yml")
114+
.build();
115+
116+
assert_json_snapshot!(claims, {
117+
".nbf" => "[timestamp]",
118+
".iat" => "[timestamp]",
119+
".exp" => "[timestamp]",
120+
});
121+
122+
let encoded = assert_ok!(claims.encoded());
123+
assert!(!encoded.is_empty());
124+
125+
let exchange_body = assert_ok!(claims.as_exchange_body());
126+
assert!(exchange_body.contains(&encoded));
127+
}
128+
}

0 commit comments

Comments
 (0)