From 589a111893946cffb9ab3bca04c116bc5eca6f9a Mon Sep 17 00:00:00 2001 From: Arlo Siemsen Date: Mon, 14 Aug 2023 14:47:58 -0500 Subject: [PATCH] login: allow passing additional args to provider --- src/bin/cargo/commands/login.rs | 12 +++++++ src/cargo/ops/registry/login.rs | 3 +- src/cargo/util/auth/mod.rs | 20 ++++++++--- src/cargo/util/credential/paseto.rs | 21 +++++++++-- tests/testsuite/cargo_login/help/stdout.log | 5 +-- tests/testsuite/credential_process.rs | 10 ++++-- tests/testsuite/login.rs | 40 +++++++++++++++++++++ 7 files changed, 97 insertions(+), 14 deletions(-) diff --git a/src/bin/cargo/commands/login.rs b/src/bin/cargo/commands/login.rs index d6769b0f8a2..e51adaa1cca 100644 --- a/src/bin/cargo/commands/login.rs +++ b/src/bin/cargo/commands/login.rs @@ -7,16 +7,28 @@ pub fn cli() -> Command { .about("Log in to a registry.") .arg(Arg::new("token").action(ArgAction::Set)) .arg(opt("registry", "Registry to use").value_name("REGISTRY")) + .arg( + Arg::new("args") + .help("Arguments for the credential provider (unstable)") + .num_args(0..) + .last(true), + ) .arg_quiet() .after_help("Run `cargo help login` for more detailed information.\n") } pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult { let registry = args.registry(config)?; + let extra_args = args + .get_many::("args") + .unwrap_or_default() + .map(String::as_str) + .collect::>(); ops::registry_login( config, args.get_one::("token").map(|s| s.as_str().into()), registry.as_deref(), + &extra_args, )?; Ok(()) } diff --git a/src/cargo/ops/registry/login.rs b/src/cargo/ops/registry/login.rs index 49f65da7855..e523737340f 100644 --- a/src/cargo/ops/registry/login.rs +++ b/src/cargo/ops/registry/login.rs @@ -21,6 +21,7 @@ pub fn registry_login( config: &Config, token_from_cmdline: Option>, reg: Option<&str>, + args: &[&str], ) -> CargoResult<()> { let source_ids = get_source_id(config, None, reg)?; @@ -50,6 +51,6 @@ pub fn registry_login( login_url: login_url.as_deref(), }; - auth::login(config, &source_ids.original, options)?; + auth::login(config, &source_ids.original, options, args)?; Ok(()) } diff --git a/src/cargo/util/auth/mod.rs b/src/cargo/util/auth/mod.rs index e1e672994a1..3ccc159bfb5 100644 --- a/src/cargo/util/auth/mod.rs +++ b/src/cargo/util/auth/mod.rs @@ -429,6 +429,7 @@ fn credential_action( sid: &SourceId, action: Action<'_>, headers: Vec, + args: &[&str], ) -> CargoResult { let name = if sid.is_crates_io() { Some(CRATES_IO_REGISTRY) @@ -442,7 +443,11 @@ fn credential_action( }; let providers = credential_provider(config, sid)?; for provider in providers { - let args: Vec<&str> = provider.iter().map(String::as_str).collect(); + let args: Vec<&str> = provider + .iter() + .map(String::as_str) + .chain(args.iter().map(|s| *s)) + .collect(); let process = args[0]; tracing::debug!("attempting credential provider: {args:?}"); let provider: Box = match process { @@ -529,7 +534,7 @@ fn auth_token_optional( } } - let credential_response = credential_action(config, sid, Action::Get(operation), headers); + let credential_response = credential_action(config, sid, Action::Get(operation), headers, &[]); if let Some(e) = credential_response.as_ref().err() { if let Some(e) = e.downcast_ref::() { if matches!(e, cargo_credential::Error::NotFound) { @@ -568,7 +573,7 @@ fn auth_token_optional( /// Log out from the given registry. pub fn logout(config: &Config, sid: &SourceId) -> CargoResult<()> { - let credential_response = credential_action(config, sid, Action::Logout, vec![]); + let credential_response = credential_action(config, sid, Action::Logout, vec![], &[]); if let Some(e) = credential_response.as_ref().err() { if let Some(e) = e.downcast_ref::() { if matches!(e, cargo_credential::Error::NotFound) { @@ -591,8 +596,13 @@ pub fn logout(config: &Config, sid: &SourceId) -> CargoResult<()> { } /// Log in to the given registry. -pub fn login(config: &Config, sid: &SourceId, options: LoginOptions<'_>) -> CargoResult<()> { - let credential_response = credential_action(config, sid, Action::Login(options), vec![])?; +pub fn login( + config: &Config, + sid: &SourceId, + options: LoginOptions<'_>, + args: &[&str], +) -> CargoResult<()> { + let credential_response = credential_action(config, sid, Action::Login(options), vec![], args)?; let CredentialResponse::Login = credential_response else { bail!("credential provider produced unexpected response for `login` request: {credential_response:?}") }; diff --git a/src/cargo/util/credential/paseto.rs b/src/cargo/util/credential/paseto.rs index 0eef3f1403e..329d1f11c74 100644 --- a/src/cargo/util/credential/paseto.rs +++ b/src/cargo/util/credential/paseto.rs @@ -4,6 +4,7 @@ use anyhow::Context; use cargo_credential::{ Action, CacheControl, Credential, CredentialResponse, Error, Operation, RegistryInfo, Secret, }; +use clap::Command; use pasetors::{ keys::{AsymmetricKeyPair, AsymmetricPublicKey, AsymmetricSecretKey, Generate}, paserk::FormatAsPaserk, @@ -14,7 +15,7 @@ use url::Url; use crate::{ core::SourceId, ops::RegistryCredentialConfig, - util::{auth::registry_credential_config_raw, config}, + util::{auth::registry_credential_config_raw, command_prelude::opt, config}, Config, }; @@ -60,7 +61,7 @@ impl<'a> Credential for PasetoCredential<'a> { &self, registry: &RegistryInfo<'_>, action: &Action<'_>, - _args: &[&str], + args: &[&str], ) -> Result { let index_url = Url::parse(registry.index_url).context("parsing index url")?; let sid = if let Some(name) = registry.name { @@ -71,6 +72,13 @@ impl<'a> Credential for PasetoCredential<'a> { let reg_cfg = registry_credential_config_raw(self.config, &sid)?; + let matches = Command::new("cargo:paseto") + .no_binary_name(true) + .arg(opt("key-subject", "Set the key subject for this registry").value_name("SUBJECT")) + .try_get_matches_from(args) + .map_err(Box::new)?; + let key_subject = matches.get_one("key-subject").map(String::as_str); + match action { Action::Get(operation) => { let Some(reg_cfg) = reg_cfg else { @@ -163,6 +171,7 @@ impl<'a> Credential for PasetoCredential<'a> { }) } Action::Login(options) => { + let old_key_subject = reg_cfg.and_then(|cfg| cfg.secret_key_subject); let new_token; let secret_key: Secret; if let Some(key) = &options.token { @@ -180,7 +189,13 @@ impl<'a> Credential for PasetoCredential<'a> { } else { return Err("not a validly formatted PASERK secret key".into()); } - new_token = RegistryCredentialConfig::AsymmetricKey((secret_key, None)); + new_token = RegistryCredentialConfig::AsymmetricKey(( + secret_key, + match key_subject { + Some(key_subject) => Some(key_subject.to_string()), + None => old_key_subject, + }, + )); config::save_credentials(self.config, Some(new_token), &sid)?; Ok(CredentialResponse::Login) } diff --git a/tests/testsuite/cargo_login/help/stdout.log b/tests/testsuite/cargo_login/help/stdout.log index 6d426ad2d5d..faec55c18e7 100644 --- a/tests/testsuite/cargo_login/help/stdout.log +++ b/tests/testsuite/cargo_login/help/stdout.log @@ -1,9 +1,10 @@ Log in to a registry. -Usage: cargo[EXE] login [OPTIONS] [token] +Usage: cargo[EXE] login [OPTIONS] [token] [-- [args]...] Arguments: - [token] + [token] + [args]... Arguments for the credential provider (unstable) Options: --registry Registry to use diff --git a/tests/testsuite/credential_process.rs b/tests/testsuite/credential_process.rs index cae453f9bce..83ccfc6243e 100644 --- a/tests/testsuite/credential_process.rs +++ b/tests/testsuite/credential_process.rs @@ -185,15 +185,19 @@ Caused by: fn login() { let registry = registry::RegistryBuilder::new() .no_configure_token() - .credential_provider(&[&build_provider("test-cred", r#"{"Ok": {"kind": "login"}}"#)]) + .credential_provider(&[ + &build_provider("test-cred", r#"{"Ok": {"kind": "login"}}"#), + "cfg1", + "--cfg2", + ]) .build(); - cargo_process("login -Z credential-process abcdefg") + cargo_process("login -Z credential-process abcdefg -- cmd3 --cmd4") .masquerade_as_nightly_cargo(&["credential-process"]) .replace_crates_io(registry.index_url()) .with_stderr( r#"[UPDATING] [..] -{"v":1,"registry":{"index-url":"https://github.com/rust-lang/crates.io-index","name":"crates-io"},"kind":"login","token":"abcdefg","login-url":"[..]","args":[]} +{"v":1,"registry":{"index-url":"https://github.com/rust-lang/crates.io-index","name":"crates-io"},"kind":"login","token":"abcdefg","login-url":"[..]","args":["cfg1","--cfg2","cmd3","--cmd4"]} "#, ) .run(); diff --git a/tests/testsuite/login.rs b/tests/testsuite/login.rs index ee6bc0873de..16bd29dce12 100644 --- a/tests/testsuite/login.rs +++ b/tests/testsuite/login.rs @@ -189,6 +189,24 @@ Caused by: ); } +#[cargo_test] +fn bad_asymmetric_token_args() { + let registry = RegistryBuilder::new() + .credential_provider(&["cargo:paseto"]) + .no_configure_token() + .build(); + + // These cases are kept brief as the implementation is covered by clap, so this is only smoke testing that we have clap configured correctly. + cargo_process("login -Zcredential-process -- --key-subject") + .masquerade_as_nightly_cargo(&["credential-process"]) + .replace_crates_io(registry.index_url()) + .with_stderr_contains( + " error: a value is required for '--key-subject ' but none was supplied", + ) + .with_status(101) + .run(); +} + #[cargo_test] fn login_with_no_cargo_dir() { // Create a config in the root directory because `login` requires the @@ -203,6 +221,28 @@ fn login_with_no_cargo_dir() { assert_eq!(credentials, "[registry]\ntoken = \"foo\"\n"); } +#[cargo_test] +fn login_with_asymmetric_token_and_subject_on_stdin() { + let registry = RegistryBuilder::new() + .credential_provider(&["cargo:paseto"]) + .no_configure_token() + .build(); + let credentials = credentials_toml(); + cargo_process("login -v -Z credential-process -- --key-subject=foo") + .masquerade_as_nightly_cargo(&["credential-process"]) + .replace_crates_io(registry.index_url()) + .with_stderr_contains( + "\ +k3.public.AmDwjlyf8jAV3gm5Z7Kz9xAOcsKslt_Vwp5v-emjFzBHLCtcANzTaVEghTNEMj9PkQ", + ) + .with_stdin("k3.secret.fNYVuMvBgOlljt9TDohnaYLblghqaHoQquVZwgR6X12cBFHZLFsaU3q7X3k1Zn36") + .run(); + let credentials = fs::read_to_string(&credentials).unwrap(); + assert!(credentials.starts_with("[registry]\n")); + assert!(credentials.contains("secret-key-subject = \"foo\"\n")); + assert!(credentials.contains("secret-key = \"k3.secret.fNYVuMvBgOlljt9TDohnaYLblghqaHoQquVZwgR6X12cBFHZLFsaU3q7X3k1Zn36\"\n")); +} + #[cargo_test] fn login_with_differently_sized_token() { // Verify that the configuration file gets properly truncated.