From ffa09541d76a9ad2fc56afee26e33a7c39489f1f Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Thu, 30 Nov 2023 19:54:54 +0100 Subject: [PATCH] fix: batch upload authentication (#21397) --- cli/tools/registry/mod.rs | 129 +++++++++++++++++++++----------------- cli/tools/registry/tar.rs | 26 +++++--- 2 files changed, 90 insertions(+), 65 deletions(-) diff --git a/cli/tools/registry/mod.rs b/cli/tools/registry/mod.rs index 733ea301fe..42bb060878 100644 --- a/cli/tools/registry/mod.rs +++ b/cli/tools/registry/mod.rs @@ -4,6 +4,7 @@ use std::fmt::Write; use std::io::IsTerminal; use std::path::Path; use std::path::PathBuf; +use std::rc::Rc; use std::sync::Arc; use base64::prelude::BASE64_STANDARD; @@ -227,21 +228,21 @@ async fn perform_publish( let client = http_client.client()?; let registry_url = crate::cache::DENO_REGISTRY_URL.to_string(); - let authorization = match auth_method { + let permissions = packages + .iter() + .map(|package| Permission::VersionPublish { + scope: &package.scope, + package: &package.package, + version: &package.version, + tarball_hash: &package.tarball_hash, + }) + .collect::>(); + + let authorizations = match auth_method { AuthMethod::Interactive => { let verifier = uuid::Uuid::new_v4().to_string(); let challenge = BASE64_STANDARD.encode(sha2::Sha256::digest(&verifier)); - let permissions = packages - .iter() - .map(|package| Permission::VersionPublish { - scope: &package.scope, - package: &package.package, - version: &package.version, - tarball_hash: &package.tarball_hash, - }) - .collect::>(); - let response = client .post(format!("{}authorizations", registry_url)) .json(&serde_json::json!({ @@ -290,7 +291,12 @@ async fn perform_publish( colors::gray("Authenticated as"), colors::cyan(res.user.name) ); - break format!("Bearer {}", res.token); + let authorization: Rc = format!("Bearer {}", res.token).into(); + let mut authorizations = Vec::new(); + for _ in &packages { + authorizations.push(authorization.clone()); + } + break authorizations; } Err(err) => { if err.code == "authorizationPending" { @@ -302,54 +308,65 @@ async fn perform_publish( } } } - AuthMethod::Token(token) => format!("Bearer {}", token), - AuthMethod::Oidc(oidc_config) => { - let permissions = packages - .iter() - .map(|package| Permission::VersionPublish { - scope: &package.scope, - package: &package.package, - version: &package.version, - tarball_hash: &package.tarball_hash, - }) - .collect::>(); - let audience = json!({ "permissions": permissions }).to_string(); - - let url = format!( - "{}&audience={}", - oidc_config.url, - percent_encoding::percent_encode( - audience.as_bytes(), - percent_encoding::NON_ALPHANUMERIC - ) - ); - - let response = client - .get(url) - .bearer_auth(oidc_config.token) - .send() - .await - .context("Failed to get OIDC token")?; - let status = response.status(); - let text = response.text().await.with_context(|| { - format!("Failed to get OIDC token: status {}", status) - })?; - if !status.is_success() { - bail!( - "Failed to get OIDC token: status {}, response: '{}'", - status, - text - ); + AuthMethod::Token(token) => { + let authorization: Rc = format!("Bearer {}", token).into(); + let mut authorizations = Vec::new(); + for _ in &packages { + authorizations.push(authorization.clone()); } - let OidcTokenResponse { value } = serde_json::from_str(&text) - .with_context(|| { - format!("Failed to parse OIDC token: '{}' (status {})", text, status) + authorizations + } + AuthMethod::Oidc(oidc_config) => { + let mut authorizations = Vec::new(); + for permissions in permissions.chunks(16) { + let audience = json!({ "permissions": permissions }).to_string(); + let url = format!( + "{}&audience={}", + oidc_config.url, + percent_encoding::percent_encode( + audience.as_bytes(), + percent_encoding::NON_ALPHANUMERIC + ) + ); + + let response = client + .get(url) + .bearer_auth(&oidc_config.token) + .send() + .await + .context("Failed to get OIDC token")?; + let status = response.status(); + let text = response.text().await.with_context(|| { + format!("Failed to get OIDC token: status {}", status) })?; - format!("githuboidc {}", value) + if !status.is_success() { + bail!( + "Failed to get OIDC token: status {}, response: '{}'", + status, + text + ); + } + let OidcTokenResponse { value } = serde_json::from_str(&text) + .with_context(|| { + format!( + "Failed to parse OIDC token: '{}' (status {})", + text, status + ) + })?; + + let authorization: Rc = format!("githuboidc {}", value).into(); + for _ in &packages { + authorizations.push(authorization.clone()); + } + } + authorizations } }; - for package in packages { + assert_eq!(packages.len(), authorizations.len()); + for (package, authorization) in + packages.into_iter().zip(authorizations.into_iter()) + { println!( "{} @{}/{}@{} ...", colors::intense_blue("Publishing"), @@ -365,7 +382,7 @@ async fn perform_publish( let response = client .post(url) - .header(AUTHORIZATION, &authorization) + .header(AUTHORIZATION, &*authorization) .header(CONTENT_ENCODING, "gzip") .body(package.tarball) .send() diff --git a/cli/tools/registry/tar.rs b/cli/tools/registry/tar.rs index e8097357d0..61532917a4 100644 --- a/cli/tools/registry/tar.rs +++ b/cli/tools/registry/tar.rs @@ -18,25 +18,33 @@ pub fn create_gzipped_tarball( unfurler: ImportMapUnfurler, ) -> Result { let mut tar = TarGzArchive::new(); - let dir_url = Url::from_directory_path(&dir).unwrap(); + let dir = dir + .canonicalize() + .map_err(|_| anyhow::anyhow!("Unable to canonicalize path {:?}", dir))?; - for entry in walkdir::WalkDir::new(dir).follow_links(false) { + for entry in walkdir::WalkDir::new(&dir).follow_links(false) { let entry = entry?; if entry.file_type().is_file() { let url = Url::from_file_path(entry.path()) - .map_err(|_| anyhow::anyhow!("Invalid file path {:?}", entry.path()))?; - let relative_path = dir_url - .make_relative(&url) - .expect("children can be relative to parent"); + .map_err(|_| anyhow::anyhow!("Unable to convert path to url"))?; + let relative_path = entry + .path() + .strip_prefix(&dir) + .map_err(|err| anyhow::anyhow!("Unable to strip prefix: {err}"))?; + let relative_path = relative_path.to_str().ok_or_else(|| { + anyhow::anyhow!("Unable to convert path to string {:?}", relative_path) + })?; let data = std::fs::read(entry.path()) .with_context(|| format!("Unable to read file {:?}", entry.path()))?; let content = unfurler .unfurl(&url, data) .with_context(|| format!("Unable to unfurl file {:?}", entry.path()))?; - tar.add_file(relative_path, &content).with_context(|| { - format!("Unable to add file to tarball {:?}", entry.path()) - })?; + tar + .add_file(relative_path.to_string(), &content) + .with_context(|| { + format!("Unable to add file to tarball {:?}", entry.path()) + })?; } else if entry.file_type().is_dir() { // skip } else {