Skip to content

Commit 73eb0bf

Browse files
authored
Merge pull request #11513 from Turbo87/trustpub-data
Save and show JWT claims subset when Trusted Publishing is used
2 parents 24ce626 + 1870ecf commit 73eb0bf

File tree

59 files changed

+320
-38
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+320
-38
lines changed

app/components/version-list/row.hbs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,23 @@
4545
{{or @version.published_by.name @version.published_by.login}}
4646
</LinkTo>
4747
</span>
48+
{{else if @version.trustpubPublisher}}
49+
<span local-class="publisher trustpub">
50+
via
51+
{{#if @version.trustpubUrl}}
52+
<a href={{@version.trustpubUrl}} target="_blank" rel="nofollow noopener noreferrer">
53+
{{#if (eq @version.trustpub_data.provider "github")}}
54+
{{svg-jar "github"}}
55+
{{/if}}
56+
{{@version.trustpubPublisher}}
57+
</a>
58+
{{else}}
59+
{{#if (eq @version.trustpub_data.provider "github")}}
60+
{{svg-jar "github"}}
61+
{{/if}}
62+
{{@version.trustpubPublisher}}
63+
{{/if}}
64+
</span>
4865
{{/if}}
4966

5067
<time

app/models/version.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,30 @@ export default class Version extends Model {
4343
/** @type {string[] | null} */
4444
@attr bin_names;
4545

46+
/**
47+
* Information about the trusted publisher that published this version, if any.
48+
* @type {Object | null}
49+
*/
50+
@attr trustpub_data;
51+
52+
/**
53+
* The name of the trusted publisher that published this version, if any.
54+
* @type {string | null}
55+
*/
56+
get trustpubPublisher() {
57+
return this.trustpub_data?.provider === 'github' ? 'GitHub' : null;
58+
}
59+
60+
/**
61+
* The URL to the trusted publisher that published this version, if any.
62+
* @type {string | null}
63+
*/
64+
get trustpubUrl() {
65+
return this.trustpub_data?.provider === 'github'
66+
? `https://github.com/${this.trustpub_data.repository}/actions/runs/${this.trustpub_data.run_id}`
67+
: null;
68+
}
69+
4670
@belongsTo('crate', { async: false, inverse: 'versions' }) crate;
4771

4872
@belongsTo('user', { async: false, inverse: null }) published_by;

crates/crates_io_database/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,5 @@ utoipa = { version = "=5.4.0", features = ["chrono"] }
3131
claims = "=0.8.0"
3232
crates_io_test_db = { path = "../crates_io_test_db" }
3333
googletest = "=0.14.2"
34-
insta = "=1.43.1"
34+
insta = { version = "=1.43.1", features = ["json"] }
3535
tokio = { version = "=1.46.1", features = ["macros", "rt"] }

crates/crates_io_database/src/models/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub use self::krate::{Crate, CrateName, NewCrate, RecentCrateDownloads};
1414
pub use self::owner::{CrateOwner, Owner, OwnerKind};
1515
pub use self::team::{NewTeam, Team};
1616
pub use self::token::ApiToken;
17+
pub use self::trustpub::TrustpubData;
1718
pub use self::user::{NewUser, User};
1819
pub use self::version::{NewVersion, TopVersions, Version};
1920

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
use diesel::deserialize::{self, FromSql};
2+
use diesel::pg::{Pg, PgValue};
3+
use diesel::serialize::{self, Output, ToSql};
4+
use diesel::sql_types::Jsonb;
5+
use diesel::{AsExpression, FromSqlRow};
6+
use serde::{Deserialize, Serialize};
7+
8+
/// Data structure containing trusted publisher information extracted from JWT claims
9+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, FromSqlRow, AsExpression)]
10+
#[diesel(sql_type = Jsonb)]
11+
#[serde(tag = "provider")]
12+
pub enum TrustpubData {
13+
#[serde(rename = "github")]
14+
GitHub {
15+
/// Repository (e.g. "octo-org/octo-repo")
16+
repository: String,
17+
/// Workflow run ID
18+
run_id: String,
19+
/// SHA of the commit
20+
sha: String,
21+
},
22+
}
23+
24+
impl ToSql<Jsonb, Pg> for TrustpubData {
25+
fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Pg>) -> serialize::Result {
26+
let json = serde_json::to_value(self)?;
27+
<serde_json::Value as ToSql<Jsonb, Pg>>::to_sql(&json, &mut out.reborrow())
28+
}
29+
}
30+
31+
impl FromSql<Jsonb, Pg> for TrustpubData {
32+
fn from_sql(bytes: PgValue<'_>) -> deserialize::Result<Self> {
33+
let json = <serde_json::Value as FromSql<Jsonb, Pg>>::from_sql(bytes)?;
34+
Ok(serde_json::from_value(json)?)
35+
}
36+
}
37+
38+
#[cfg(test)]
39+
mod tests {
40+
use super::*;
41+
use insta::assert_json_snapshot;
42+
43+
#[test]
44+
fn test_serialization() {
45+
let data = TrustpubData::GitHub {
46+
repository: "octo-org/octo-repo".to_string(),
47+
run_id: "example-run-id".to_string(),
48+
sha: "example-sha".to_string(),
49+
};
50+
51+
assert_json_snapshot!(data, @r#"
52+
{
53+
"provider": "github",
54+
"repository": "octo-org/octo-repo",
55+
"run_id": "example-run-id",
56+
"sha": "example-sha"
57+
}
58+
"#);
59+
}
60+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
mod data;
12
mod github_config;
23
mod token;
34
mod used_jti;
45

6+
pub use self::data::TrustpubData;
57
pub use self::github_config::{GitHubConfig, NewGitHubConfig};
68
pub use self::token::NewToken;
79
pub use self::used_jti::NewUsedJti;

crates/crates_io_database/src/models/trustpub/token.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::models::TrustpubData;
12
use crate::schema::trustpub_tokens;
23
use chrono::{DateTime, Utc};
34
use diesel::prelude::*;
@@ -9,6 +10,7 @@ pub struct NewToken<'a> {
910
pub expires_at: DateTime<Utc>,
1011
pub hashed_token: &'a [u8],
1112
pub crate_ids: &'a [i32],
13+
pub trustpub_data: Option<&'a TrustpubData>,
1214
}
1315

1416
impl NewToken<'_> {

crates/crates_io_database/src/models/version.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use diesel::prelude::*;
77
use diesel_async::{AsyncPgConnection, RunQueryDsl};
88
use serde::Deserialize;
99

10-
use crate::models::{Crate, User};
10+
use crate::models::{Crate, TrustpubData, User};
1111
use crate::schema::{readme_renderings, users, versions};
1212

1313
// Queryable has a custom implementation below
@@ -36,6 +36,7 @@ pub struct Version {
3636
pub homepage: Option<String>,
3737
pub documentation: Option<String>,
3838
pub repository: Option<String>,
39+
pub trustpub_data: Option<TrustpubData>,
3940
}
4041

4142
impl Version {
@@ -103,6 +104,7 @@ pub struct NewVersion<'a> {
103104
repository: Option<&'a str>,
104105
categories: Option<&'a [&'a str]>,
105106
keywords: Option<&'a [&'a str]>,
107+
trustpub_data: Option<&'a TrustpubData>,
106108
}
107109

108110
impl NewVersion<'_> {

crates/crates_io_database/src/schema.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,8 @@ diesel::table! {
800800
hashed_token -> Bytea,
801801
/// Unique identifiers of the crates that can be published using this token
802802
crate_ids -> Array<Nullable<Int4>>,
803+
/// JSONB data containing JWT claims from the trusted publisher (e.g., GitHub Actions context like repository, run_id, sha)
804+
trustpub_data -> Nullable<Jsonb>,
803805
}
804806
}
805807

@@ -1077,6 +1079,8 @@ diesel::table! {
10771079
keywords -> Array<Nullable<Text>>,
10781080
/// JSONB representation of the version number for sorting purposes.
10791081
semver_ord -> Nullable<Jsonb>,
1082+
/// JSONB data containing JWT claims from the trusted publisher (e.g., GitHub Actions context like repository, run_id, sha)
1083+
trustpub_data -> Nullable<Jsonb>,
10801084
}
10811085
}
10821086

crates/crates_io_database_dump/src/dump-db.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ created_at = "private"
206206
expires_at = "private"
207207
hashed_token = "private"
208208
crate_ids = "private"
209+
trustpub_data = "private"
209210

210211
[trustpub_used_jtis.columns]
211212
id = "private"
@@ -280,6 +281,8 @@ documentation = "public"
280281
repository = "public"
281282
categories = "public"
282283
keywords = "public"
284+
# The following column is private for now, until we can guarantee a stable data schema.
285+
trustpub_data = "private"
283286

284287
[versions_published_by.columns]
285288
version_id = "private"

0 commit comments

Comments
 (0)