diff --git a/src/cargo/ops/registry.rs b/src/cargo/ops/registry.rs index c18b5d1eddea..513a004b4c9a 100644 --- a/src/cargo/ops/registry.rs +++ b/src/cargo/ops/registry.rs @@ -40,7 +40,7 @@ use crate::util::auth; /// Registry settings loaded from config files. /// /// This is loaded based on the `--registry` flag and the config settings. -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum RegistryCredentialConfig { None, /// The authentication token. @@ -211,12 +211,14 @@ pub fn publish(ws: &Workspace<'_>, opts: &PublishOpts<'_>) -> CargoResult<()> { }; if !opts.dry_run { - registry.set_token(Some(auth::auth_token( - &opts.config, - ®_id_no_replacement, - None, - Some(mutation), - )?)); + registry.set_token(Some( + auth::auth_token(&opts.config, ®_id_no_replacement)?.as_header( + &opts.config, + ®_id_no_replacement, + None, + Some(&mutation), + )?, + )); } opts.config @@ -513,11 +515,11 @@ fn registry( Use the --token command-line flag to remove this warning.", )?; } - Some(auth::auth_token( + Some(auth::auth_token(config, &sid_no_replacement)?.as_header( config, &sid_no_replacement, None, - token_required, + token_required.as_ref(), )?) } else { None diff --git a/src/cargo/sources/registry/download.rs b/src/cargo/sources/registry/download.rs index 723c55ffd913..62b6e051f1f0 100644 --- a/src/cargo/sources/registry/download.rs +++ b/src/cargo/sources/registry/download.rs @@ -71,7 +71,12 @@ pub(super) fn download( } let authorization = if registry_config.auth_required { - Some(auth::auth_token(config, &pkg.source_id(), None, None)?) + Some(auth::auth_token(config, &pkg.source_id())?.as_header( + config, + &pkg.source_id(), + None, + None, + )?) } else { None }; diff --git a/src/cargo/sources/registry/http_remote.rs b/src/cargo/sources/registry/http_remote.rs index d5b156f02561..937abc4d1347 100644 --- a/src/cargo/sources/registry/http_remote.rs +++ b/src/cargo/sources/registry/http_remote.rs @@ -541,8 +541,13 @@ impl<'cfg> RegistryData for HttpRegistry<'cfg> { "authenticated registries require `-Z registry-auth`" ))); } - let authorization = - auth::auth_token(self.config, &self.source_id, self.login_url.as_ref(), None)?; + let credential = auth::auth_token(self.config, &self.source_id)?; + let authorization = credential.as_header( + self.config, + &self.source_id, + self.login_url.as_ref(), + None, + )?; headers.append(&format!("authorization: {}", authorization))?; trace!("including authorization for {}", full_url); } diff --git a/src/cargo/util/auth.rs b/src/cargo/util/auth.rs index 3829e653f2f1..237dc68ffd91 100644 --- a/src/cargo/util/auth.rs +++ b/src/cargo/util/auth.rs @@ -12,6 +12,7 @@ use std::error::Error; use std::io::{Read, Write}; use std::path::PathBuf; use std::process::{Command, Stdio}; +use std::rc::Rc; use time::format_description::well_known::Rfc3339; use time::OffsetDateTime; use url::Url; @@ -269,98 +270,91 @@ my-registry = {{ index = "{}" }} // Store a token in the cache for future calls. pub fn cache_token(config: &Config, sid: &SourceId, token: &str) { let url = sid.canonical_url(); - config - .credential_cache() - .insert(url.clone(), token.to_string()); + config.credential_cache().insert( + url.clone(), + Rc::new(RegistryCredentialConfig::Token(token.to_string())), + ); } /// Returns the token to use for the given registry. -/// If a `login_url` is provided and a token is not available, the -/// login_url will be included in the returned error. -pub fn auth_token( - config: &Config, - sid: &SourceId, - login_url: Option<&Url>, - mutation: Option>, -) -> CargoResult { - match auth_token_optional(config, sid, mutation.as_ref())? { - Some(token) => Ok(token), - None => Err(AuthorizationError { - sid: sid.clone(), - login_url: login_url.cloned(), - reason: AuthorizationErrorReason::TokenMissing, - } - .into()), - } -} - -/// Returns the token to use for the given registry. -fn auth_token_optional( - config: &Config, - sid: &SourceId, - mutation: Option<&'_ Mutation<'_>>, -) -> CargoResult> { +pub fn auth_token(config: &Config, sid: &SourceId) -> CargoResult> { let mut cache = config.credential_cache(); let url = sid.canonical_url(); - if mutation.is_none() { - if let Some(token) = cache.get(url) { - return Ok(Some(token.clone())); - } + if let Some(token) = cache.get(url) { + return Ok(Rc::clone(token)); } - let credential = registry_credential_config(config, sid)?; - let token = match credential { - RegistryCredentialConfig::None => return Ok(None), - RegistryCredentialConfig::Token(config_token) => config_token.to_string(), - RegistryCredentialConfig::Process(process) => { - // todo: PASETO with process - run_command(config, &process, sid, Action::Get)?.unwrap() - } - RegistryCredentialConfig::Key((private_key, private_key_subject)) => { - let secret: AsymmetricSecretKey = - private_key.as_str().try_into()?; - let public: AsymmetricPublicKey = (&secret).try_into()?; - let public_key_id: pasetors::paserk::Id = (&public).into(); - let mut kip = String::new(); - FormatAsPaserk::fmt(&public_key_id, &mut kip).unwrap(); - let iat = OffsetDateTime::now_utc(); - - let message = Message { - iat: &iat.format(&Rfc3339)?, - sub: private_key_subject.as_deref(), - mutation: mutation.and_then(|m| m.mutation), - name: mutation.and_then(|m| m.name), - vers: mutation.and_then(|m| m.vers), - cksum: mutation.and_then(|m| m.cksum), - challenge: None, // todo: PASETO with challenges, - v: None, - }; - let footer = Footer { - url: &sid.url().to_string(), - kip: &kip, - }; - - pasetors::version3::PublicToken::sign( - &secret, - &public, - serde_json::to_string(&message) - .expect("cannot serialize") - .as_bytes(), - Some( - serde_json::to_string(&footer) + let credential = Rc::new(registry_credential_config(config, sid)?); + + cache.insert(url.clone(), Rc::clone(&credential)); + Ok(credential) +} + +impl RegistryCredentialConfig { + /// If a `login_url` is provided and a token is not available, the + /// login_url will be included in the returned error. + pub fn as_header( + self: &Rc, + config: &Config, + sid: &SourceId, + login_url: Option<&Url>, + mutation: Option<&'_ Mutation<'_>>, + ) -> CargoResult { + Ok(match &**self { + RegistryCredentialConfig::None => { + return Err(AuthorizationError { + sid: sid.clone(), + login_url: login_url.cloned(), + reason: AuthorizationErrorReason::TokenMissing, + } + .into()) + } + RegistryCredentialConfig::Token(config_token) => config_token.to_string(), + RegistryCredentialConfig::Process(process) => { + // todo: PASETO with process + run_command(config, &process, sid, Action::Get)?.unwrap() + } + RegistryCredentialConfig::Key((private_key, private_key_subject)) => { + let secret: AsymmetricSecretKey = + private_key.as_str().try_into()?; + let public: AsymmetricPublicKey = (&secret).try_into()?; + let public_key_id: pasetors::paserk::Id = (&public).into(); + let mut kip = String::new(); + FormatAsPaserk::fmt(&public_key_id, &mut kip).unwrap(); + let iat = OffsetDateTime::now_utc(); + + let message = Message { + iat: &iat.format(&Rfc3339)?, + sub: private_key_subject.as_deref(), + mutation: mutation.and_then(|m| m.mutation), + name: mutation.and_then(|m| m.name), + vers: mutation.and_then(|m| m.vers), + cksum: mutation.and_then(|m| m.cksum), + challenge: None, // todo: PASETO with challenges, + v: None, + }; + let footer = Footer { + url: &sid.url().to_string(), + kip: &kip, + }; + + pasetors::version3::PublicToken::sign( + &secret, + &public, + serde_json::to_string(&message) .expect("cannot serialize") .as_bytes(), - ), - None, - )? - } - }; - - if mutation.is_none() { - cache.insert(url.clone(), token.clone()); + Some( + serde_json::to_string(&footer) + .expect("cannot serialize") + .as_bytes(), + ), + None, + )? + } + }) } - Ok(Some(token)) } pub struct Mutation<'a> { diff --git a/src/cargo/util/config/mod.rs b/src/cargo/util/config/mod.rs index 21995bf235be..3cc2e87b5455 100644 --- a/src/cargo/util/config/mod.rs +++ b/src/cargo/util/config/mod.rs @@ -61,6 +61,7 @@ use std::io::prelude::*; use std::io::{self, SeekFrom}; use std::mem; use std::path::{Path, PathBuf}; +use std::rc::Rc; use std::str::FromStr; use std::sync::Once; use std::time::Instant; @@ -179,7 +180,7 @@ pub struct Config { updated_sources: LazyCell>>, /// Cache of credentials from configuration or credential providers. /// Maps from url to credential value. - credential_cache: LazyCell>>, + credential_cache: LazyCell>>>, /// Lock, if held, of the global package cache along with the number of /// acquisitions so far. package_cache_lock: RefCell, usize)>>, @@ -438,7 +439,9 @@ impl Config { } /// Cached credentials from credential providers or configuration. - pub fn credential_cache(&self) -> RefMut<'_, HashMap> { + pub fn credential_cache( + &self, + ) -> RefMut<'_, HashMap>> { self.credential_cache .borrow_with(|| RefCell::new(HashMap::new())) .borrow_mut() diff --git a/tests/testsuite/registry_auth.rs b/tests/testsuite/registry_auth.rs index ca7c9f7299a0..5d9b33646f8b 100644 --- a/tests/testsuite/registry_auth.rs +++ b/tests/testsuite/registry_auth.rs @@ -6,7 +6,7 @@ use cargo_test_support::{project, Execs, Project}; fn cargo(p: &Project, s: &str) -> Execs { let mut e = p.cargo(s); e.masquerade_as_nightly_cargo() - .arg("-Zhttp-registry") + .arg("-Zsparse-registry") .arg("-Zregistry-auth") .arg("-Zasymmetric-tokens"); e