Skip to content

Commit 40f1f67

Browse files
committed
Auto merge of #12590 - arlosi:cred-unsupported-error, r=epage
fix: add error for unsupported credential provider version Cargo currently ignores the version in the `CredentialHello` message, and proceeds to use version `1` regardless of what the credential provider claims it can support. This change does the following: * Adds a new error if Cargo doesn't support any of the supported protocol versions offered by the provider. * Kills the credential provider subprocess if it fails. This prevents it from hanging or printing spurious errors such as "broken pipe" when it's attempting to read the next JSON message. * Adds a new test for an unsupported credential provider protocol.
2 parents 1da01e5 + 39db61e commit 40f1f67

File tree

5 files changed

+99
-21
lines changed

5 files changed

+99
-21
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

credential/cargo-credential/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "cargo-credential"
3-
version = "0.3.0"
3+
version = "0.3.1"
44
edition.workspace = true
55
license.workspace = true
66
repository = "https://github.com/rust-lang/cargo"

credential/cargo-credential/src/lib.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,9 +197,11 @@ pub enum CacheControl {
197197
Unknown,
198198
}
199199

200-
/// Credential process JSON protocol version. Incrementing
201-
/// this version will prevent new credential providers
202-
/// from working with older versions of Cargo.
200+
/// Credential process JSON protocol version. If the protocol needs to make
201+
/// a breaking change, a new protocol version should be defined (`PROTOCOL_VERSION_2`).
202+
/// This library should offer support for both protocols if possible, by signaling
203+
/// in the `CredentialHello` message. Cargo will then choose which protocol to use,
204+
/// or it will error if there are no common protocol versions available.
203205
pub const PROTOCOL_VERSION_1: u32 = 1;
204206
pub trait Credential {
205207
/// Retrieves a token for the given registry.

src/cargo/util/credential/process.rs

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
use std::{
55
io::{BufRead, BufReader, Write},
66
path::PathBuf,
7-
process::{Command, Stdio},
7+
process::{Child, Command, Stdio},
88
};
99

1010
use anyhow::Context;
1111
use cargo_credential::{
12-
Action, Credential, CredentialHello, CredentialRequest, CredentialResponse, RegistryInfo,
12+
Action, Credential, CredentialHello, CredentialRequest, CredentialResponse, Error, RegistryInfo,
1313
};
1414

1515
pub struct CredentialProcessCredential {
@@ -22,31 +22,38 @@ impl<'a> CredentialProcessCredential {
2222
path: PathBuf::from(path),
2323
}
2424
}
25-
}
2625

27-
impl<'a> Credential for CredentialProcessCredential {
28-
fn perform(
26+
fn run(
2927
&self,
30-
registry: &RegistryInfo<'_>,
28+
child: &mut Child,
3129
action: &Action<'_>,
30+
registry: &RegistryInfo<'_>,
3231
args: &[&str],
33-
) -> Result<CredentialResponse, cargo_credential::Error> {
34-
let mut cmd = Command::new(&self.path);
35-
cmd.stdout(Stdio::piped());
36-
cmd.stdin(Stdio::piped());
37-
cmd.arg("--cargo-plugin");
38-
tracing::debug!("credential-process: {cmd:?}");
39-
let mut child = cmd.spawn().context("failed to spawn credential process")?;
32+
) -> Result<Result<CredentialResponse, Error>, Error> {
4033
let mut output_from_child = BufReader::new(child.stdout.take().unwrap());
4134
let mut input_to_child = child.stdin.take().unwrap();
4235
let mut buffer = String::new();
36+
37+
// Read the CredentialHello
4338
output_from_child
4439
.read_line(&mut buffer)
4540
.context("failed to read hello from credential provider")?;
4641
let credential_hello: CredentialHello =
4742
serde_json::from_str(&buffer).context("failed to deserialize hello")?;
4843
tracing::debug!("credential-process > {credential_hello:?}");
44+
if !credential_hello
45+
.v
46+
.contains(&cargo_credential::PROTOCOL_VERSION_1)
47+
{
48+
return Err(format!(
49+
"credential provider supports protocol versions {:?}, while Cargo supports {:?}",
50+
credential_hello.v,
51+
[cargo_credential::PROTOCOL_VERSION_1]
52+
)
53+
.into());
54+
}
4955

56+
// Send the Credential Request
5057
let req = CredentialRequest {
5158
v: cargo_credential::PROTOCOL_VERSION_1,
5259
action: action.clone(),
@@ -56,14 +63,17 @@ impl<'a> Credential for CredentialProcessCredential {
5663
let request = serde_json::to_string(&req).context("failed to serialize request")?;
5764
tracing::debug!("credential-process < {req:?}");
5865
writeln!(input_to_child, "{request}").context("failed to write to credential provider")?;
59-
6066
buffer.clear();
6167
output_from_child
6268
.read_line(&mut buffer)
6369
.context("failed to read response from credential provider")?;
64-
let response: Result<CredentialResponse, cargo_credential::Error> =
70+
71+
// Read the Credential Response
72+
let response: Result<CredentialResponse, Error> =
6573
serde_json::from_str(&buffer).context("failed to deserialize response")?;
6674
tracing::debug!("credential-process > {response:?}");
75+
76+
// Tell the credential process we're done by closing stdin. It should exit cleanly.
6777
drop(input_to_child);
6878
let status = child.wait().context("credential process never started")?;
6979
if !status.success() {
@@ -75,6 +85,31 @@ impl<'a> Credential for CredentialProcessCredential {
7585
.into());
7686
}
7787
tracing::trace!("credential process exited successfully");
78-
response
88+
Ok(response)
89+
}
90+
}
91+
92+
impl<'a> Credential for CredentialProcessCredential {
93+
fn perform(
94+
&self,
95+
registry: &RegistryInfo<'_>,
96+
action: &Action<'_>,
97+
args: &[&str],
98+
) -> Result<CredentialResponse, Error> {
99+
let mut cmd = Command::new(&self.path);
100+
cmd.stdout(Stdio::piped());
101+
cmd.stdin(Stdio::piped());
102+
cmd.arg("--cargo-plugin");
103+
tracing::debug!("credential-process: {cmd:?}");
104+
let mut child = cmd.spawn().context("failed to spawn credential process")?;
105+
match self.run(&mut child, action, registry, args) {
106+
Err(e) => {
107+
// Since running the credential process itself failed, ensure the
108+
// process is stopped.
109+
let _ = child.kill();
110+
Err(e)
111+
}
112+
Ok(response) => response,
113+
}
79114
}
80115
}

tests/testsuite/credential_process.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,3 +656,44 @@ CARGO_REGISTRY_INDEX_URL=Some([..])
656656
)
657657
.run();
658658
}
659+
660+
#[cargo_test]
661+
fn unsupported_version() {
662+
let cred_proj = project()
663+
.at("new-vers")
664+
.file("Cargo.toml", &basic_manifest("new-vers", "1.0.0"))
665+
.file(
666+
"src/main.rs",
667+
&r####"
668+
fn main() {
669+
println!(r#"{{"v":[998, 999]}}"#);
670+
assert_eq!(std::env::args().skip(1).next().unwrap(), "--cargo-plugin");
671+
let mut buffer = String::new();
672+
std::io::stdin().read_line(&mut buffer).unwrap();
673+
std::thread::sleep(std::time::Duration::from_secs(1));
674+
panic!("child process should have been killed before getting here");
675+
} "####,
676+
)
677+
.build();
678+
cred_proj.cargo("build").run();
679+
let provider = toml_bin(&cred_proj, "new-vers");
680+
681+
let registry = registry::RegistryBuilder::new()
682+
.no_configure_token()
683+
.credential_provider(&[&provider])
684+
.build();
685+
686+
cargo_process("login -Z credential-process abcdefg")
687+
.masquerade_as_nightly_cargo(&["credential-process"])
688+
.replace_crates_io(registry.index_url())
689+
.with_status(101)
690+
.with_stderr(
691+
r#"[UPDATING] [..]
692+
[ERROR] credential provider `[..]` failed action `login`
693+
694+
Caused by:
695+
credential provider supports protocol versions [998, 999], while Cargo supports [1]
696+
"#,
697+
)
698+
.run();
699+
}

0 commit comments

Comments
 (0)