Skip to content

Commit

Permalink
Cache CredentialConfig's not tokens.
Browse files Browse the repository at this point in the history
  • Loading branch information
Eh2406 committed Dec 9, 2022
1 parent 9f349ae commit e832894
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 123 deletions.
20 changes: 11 additions & 9 deletions src/cargo/ops/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ use crate::{drop_print, drop_println, version};
/// 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.
Expand Down Expand Up @@ -212,12 +212,14 @@ pub fn publish(ws: &Workspace<'_>, opts: &PublishOpts<'_>) -> CargoResult<()> {
};

if !opts.dry_run {
registry.set_token(Some(auth::auth_token(
&opts.config,
&reg_ids.original,
None,
Some(mutation),
)?));
registry.set_token(Some(
auth::auth_token(&opts.config, &reg_ids.original)?.as_header(
&opts.config,
&reg_ids.original,
None,
Some(&mutation),
)?,
));
}

opts.config
Expand Down Expand Up @@ -548,11 +550,11 @@ fn registry(
.api
.ok_or_else(|| format_err!("{} does not support API commands", source_ids.replacement))?;
let token = if token_required.is_some() || cfg.auth_required {
Some(auth::auth_token(
Some(auth::auth_token(config, &source_ids.original)?.as_header(
config,
&source_ids.original,
None,
token_required,
token_required.as_ref(),
)?)
} else {
None
Expand Down
7 changes: 6 additions & 1 deletion src/cargo/sources/registry/download.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
};
Expand Down
9 changes: 7 additions & 2 deletions src/cargo/sources/registry/http_remote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -591,8 +591,13 @@ impl<'cfg> RegistryData for HttpRegistry<'cfg> {
}
if self.auth_required {
self.check_registry_auth_unstable()?;
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);
}
Expand Down
212 changes: 103 additions & 109 deletions src/cargo/util/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,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;
Expand Down Expand Up @@ -294,126 +295,119 @@ 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<Mutation<'_>>,
) -> CargoResult<String> {
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<Option<String>> {
pub fn auth_token(config: &Config, sid: &SourceId) -> CargoResult<Rc<RegistryCredentialConfig>> {
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::AsymmetricKey((secret_key, secret_key_subject)) => {
let secret: AsymmetricSecretKey<pasetors::version3::V3> =
secret_key.as_str().try_into()?;
let public: AsymmetricPublicKey<pasetors::version3::V3> = (&secret).try_into()?;
let kip: pasetors::paserk::Id = (&public).try_into()?;
let iat = OffsetDateTime::now_utc();

let message = Message {
iat: &iat.format(&Rfc3339)?,
sub: secret_key_subject.as_deref(),
mutation: mutation.and_then(|m| {
Some(match m {
Mutation::PrePublish => return None,
Mutation::Publish { .. } => "publish",
Mutation::Yank { .. } => "yank",
Mutation::Unyank { .. } => "unyank",
Mutation::Owners { .. } => "owners",
})
}),
name: mutation.and_then(|m| {
Some(match m {
Mutation::PrePublish => return None,
Mutation::Publish { name, .. }
| Mutation::Yank { name, .. }
| Mutation::Unyank { name, .. }
| Mutation::Owners { name, .. } => *name,
})
}),
vers: mutation.and_then(|m| {
Some(match m {
Mutation::PrePublish | Mutation::Owners { .. } => return None,
Mutation::Publish { vers, .. }
| Mutation::Yank { vers, .. }
| Mutation::Unyank { vers, .. } => *vers,
})
}),
cksum: mutation.and_then(|m| {
Some(match m {
Mutation::PrePublish
| Mutation::Yank { .. }
| Mutation::Unyank { .. }
| Mutation::Owners { .. } => return None,
Mutation::Publish { cksum, .. } => *cksum,
})
}),
challenge: None, // todo: PASETO with challenges
v: None,
};
let footer = Footer {
url: &sid.url().to_string(),
kip,
};

pasetors::version3::PublicToken::sign(
&secret,
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<RegistryCredentialConfig>,
config: &Config,
sid: &SourceId,
login_url: Option<&Url>,
mutation: Option<&'_ Mutation<'_>>,
) -> CargoResult<String> {
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::AsymmetricKey((secret_key, secret_key_subject)) => {
let secret: AsymmetricSecretKey<pasetors::version3::V3> =
secret_key.as_str().try_into()?;
let public: AsymmetricPublicKey<pasetors::version3::V3> = (&secret).try_into()?;
let kip: pasetors::paserk::Id = (&public).into();
let iat = OffsetDateTime::now_utc();

let message = Message {
iat: &iat.format(&Rfc3339)?,
sub: secret_key_subject.as_deref(),
mutation: mutation.and_then(|m| {
Some(match m {
Mutation::PrePublish => return None,
Mutation::Publish { .. } => "publish",
Mutation::Yank { .. } => "yank",
Mutation::Unyank { .. } => "unyank",
Mutation::Owners { .. } => "owners",
})
}),
name: mutation.and_then(|m| {
Some(match m {
Mutation::PrePublish => return None,
Mutation::Publish { name, .. }
| Mutation::Yank { name, .. }
| Mutation::Unyank { name, .. }
| Mutation::Owners { name, .. } => *name,
})
}),
vers: mutation.and_then(|m| {
Some(match m {
Mutation::PrePublish | Mutation::Owners { .. } => return None,
Mutation::Publish { vers, .. }
| Mutation::Yank { vers, .. }
| Mutation::Unyank { vers, .. } => *vers,
})
}),
cksum: mutation.and_then(|m| {
Some(match m {
Mutation::PrePublish
| Mutation::Yank { .. }
| Mutation::Unyank { .. }
| Mutation::Owners { .. } => return None,
Mutation::Publish { cksum, .. } => *cksum,
})
}),
challenge: None, // todo: PASETO with challenges
v: None,
};
let footer = Footer {
url: &sid.url().to_string(),
kip,
};

pasetors::version3::PublicToken::sign(
&secret,
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 enum Mutation<'a> {
Expand Down
7 changes: 5 additions & 2 deletions src/cargo/util/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -193,7 +194,7 @@ pub struct Config {
updated_sources: LazyCell<RefCell<HashSet<SourceId>>>,
/// Cache of credentials from configuration or credential providers.
/// Maps from url to credential value.
credential_cache: LazyCell<RefCell<HashMap<CanonicalUrl, String>>>,
credential_cache: LazyCell<RefCell<HashMap<CanonicalUrl, Rc<RegistryCredentialConfig>>>>,
/// Lock, if held, of the global package cache along with the number of
/// acquisitions so far.
package_cache_lock: RefCell<Option<(Option<FileLock>, usize)>>,
Expand Down Expand Up @@ -468,7 +469,9 @@ impl Config {
}

/// Cached credentials from credential providers or configuration.
pub fn credential_cache(&self) -> RefMut<'_, HashMap<CanonicalUrl, String>> {
pub fn credential_cache(
&self,
) -> RefMut<'_, HashMap<CanonicalUrl, Rc<RegistryCredentialConfig>>> {
self.credential_cache
.borrow_with(|| RefCell::new(HashMap::new()))
.borrow_mut()
Expand Down

0 comments on commit e832894

Please sign in to comment.