Skip to content

Commit cb95045

Browse files
committed
Only allow rust-lang-owner as the user owner
1 parent 7c7132d commit cb95045

File tree

2 files changed

+92
-4
lines changed

2 files changed

+92
-4
lines changed

sync-team/src/crates_io/api.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,49 @@ impl CratesIoApi {
6060
Ok(response.github_configs)
6161
}
6262

63+
/// List owners of a given crate.
64+
pub(crate) fn list_crate_owners(&self, krate: &str) -> anyhow::Result<Vec<CratesIoOwner>> {
65+
#[derive(serde::Deserialize)]
66+
struct OwnersResponse {
67+
users: Vec<CratesIoOwner>,
68+
}
69+
70+
let response: OwnersResponse = self
71+
.req::<()>(
72+
reqwest::Method::GET,
73+
&format!("/crates/{krate}/owners"),
74+
None,
75+
)?
76+
.error_for_status()?
77+
.json_annotated()?;
78+
79+
Ok(response.users)
80+
}
81+
82+
/// Delete the specified owner(s) of a given crate.
83+
pub(crate) fn delete_crate_owners(
84+
&self,
85+
krate: &str,
86+
owners: &[CratesIoOwner],
87+
) -> anyhow::Result<()> {
88+
#[derive(serde::Serialize)]
89+
struct DeleteOwnersRequest<'a> {
90+
owners: &'a [&'a str],
91+
}
92+
93+
let owners = owners.iter().map(|o| o.login.as_str()).collect::<Vec<_>>();
94+
95+
self.req(
96+
reqwest::Method::DELETE,
97+
&format!("/crates/{krate}/owners"),
98+
Some(&DeleteOwnersRequest { owners: &owners }),
99+
)?
100+
.error_for_status()
101+
.with_context(|| anyhow::anyhow!("Cannot delete owner(s) {owners:?} from krate {krate}"))?;
102+
103+
Ok(())
104+
}
105+
63106
/// Create a new trusted publishing configuration for a given crate.
64107
pub(crate) fn create_trusted_publishing_github_config(
65108
&self,
@@ -227,3 +270,16 @@ pub(crate) struct CratesIoCrate {
227270
#[serde(rename = "trustpub_only")]
228271
pub(crate) trusted_publishing_only: bool,
229272
}
273+
274+
#[derive(serde::Deserialize, Debug)]
275+
#[serde(rename_all = "kebab-case")]
276+
pub(crate) enum OwnerKind {
277+
User,
278+
Team,
279+
}
280+
281+
#[derive(serde::Deserialize, Debug)]
282+
pub(crate) struct CratesIoOwner {
283+
pub(crate) login: String,
284+
pub(crate) kind: OwnerKind,
285+
}

sync-team/src/crates_io/mod.rs

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ mod api;
33
use crate::team_api::TeamApi;
44
use std::cmp::Ordering;
55

6-
use crate::crates_io::api::{CratesIoApi, TrustedPublishingGitHubConfig};
6+
use crate::crates_io::api::{CratesIoApi, CratesIoOwner, OwnerKind, TrustedPublishingGitHubConfig};
77
use anyhow::Context;
88
use secrecy::SecretString;
99
use std::collections::BTreeMap;
@@ -78,6 +78,7 @@ impl SyncCratesIo {
7878

7979
// Note: we currently only support one trusted publishing configuration per crate
8080
for (krate, desired) in &self.crates {
81+
// Sync trusted publishing configs
8182
let mut configs = self
8283
.crates_io_api
8384
.list_trusted_publishing_github_configs(&krate.0)
@@ -113,6 +114,7 @@ impl SyncCratesIo {
113114
// Non-matching configs should be deleted
114115
config_diffs.extend(configs.into_iter().map(ConfigDiff::Delete));
115116

117+
// Sync "trusted publishing only" crate option
116118
let trusted_publish_only_expected = desired.trusted_publishing_only;
117119
let crates_io_crate = self
118120
.crates_io_api
@@ -124,11 +126,28 @@ impl SyncCratesIo {
124126
value: trusted_publish_only_expected,
125127
});
126128
}
129+
130+
// Sync crate owners
131+
let owners = self
132+
.crates_io_api
133+
.list_crate_owners(&krate.0)
134+
.with_context(|| anyhow::anyhow!("Cannot list crate owners of {krate}"))?;
135+
// Make sure that the only user owner is `rust-lang-owner`
136+
let user_owners = owners
137+
.into_iter()
138+
.filter(|owner| match owner.kind {
139+
OwnerKind::User => owner.login != "rust-lang-owner",
140+
OwnerKind::Team => false,
141+
})
142+
.collect::<Vec<_>>();
143+
crate_diffs.push(CrateDiff::RemoveOwners {
144+
krate: krate.to_string(),
145+
owners: user_owners,
146+
})
127147
}
128148

129149
// We want to apply deletions first, and only then create new configs, to ensure that we
130-
// don't try to create a duplicate config where e.g. only the environment differs, which
131-
// would be an error in crates.io.
150+
// don't try to create a duplicate config where e.g. only the environment differs.
132151
config_diffs.sort_by(|a, b| match &(a, b) {
133152
(ConfigDiff::Delete(_), ConfigDiff::Create(_)) => Ordering::Less,
134153
(ConfigDiff::Create(_), ConfigDiff::Delete(_)) => Ordering::Greater,
@@ -237,7 +256,14 @@ impl std::fmt::Display for ConfigDiff {
237256
}
238257

239258
enum CrateDiff {
240-
SetTrustedPublishingOnly { krate: String, value: bool },
259+
SetTrustedPublishingOnly {
260+
krate: String,
261+
value: bool,
262+
},
263+
RemoveOwners {
264+
krate: String,
265+
owners: Vec<CratesIoOwner>,
266+
},
241267
}
242268

243269
impl CrateDiff {
@@ -246,6 +272,9 @@ impl CrateDiff {
246272
Self::SetTrustedPublishingOnly { krate, value } => sync
247273
.crates_io_api
248274
.set_trusted_publishing_only(krate, *value),
275+
CrateDiff::RemoveOwners { krate, owners } => {
276+
sync.crates_io_api.delete_crate_owners(krate, owners)
277+
}
249278
}
250279
}
251280
}
@@ -259,6 +288,9 @@ impl std::fmt::Display for CrateDiff {
259288
" Setting trusted publishing only option for krate `{krate}` to `{value}`",
260289
)?;
261290
}
291+
CrateDiff::RemoveOwners { krate, owners } => {
292+
writeln!(f, " Removing owner(s) `{owners:?}` from krate `{krate}`")?;
293+
}
262294
}
263295
Ok(())
264296
}

0 commit comments

Comments
 (0)