cargo/util/credential/
process.rs

1//! Credential provider that launches an external process using Cargo's credential
2//! protocol.
3
4use std::{
5    io::{BufRead, BufReader, Write},
6    path::PathBuf,
7    process::{Child, Command, Stdio},
8};
9
10use anyhow::Context;
11use cargo_credential::{
12    Action, Credential, CredentialHello, CredentialRequest, CredentialResponse, Error, RegistryInfo,
13};
14
15pub struct CredentialProcessCredential {
16    path: PathBuf,
17}
18
19impl<'a> CredentialProcessCredential {
20    pub fn new(path: &str) -> Self {
21        Self {
22            path: PathBuf::from(path),
23        }
24    }
25
26    fn run(
27        &self,
28        child: &mut Child,
29        action: &Action<'_>,
30        registry: &RegistryInfo<'_>,
31        args: &[&str],
32    ) -> Result<Result<CredentialResponse, Error>, Error> {
33        let mut output_from_child = BufReader::new(child.stdout.take().unwrap());
34        let mut input_to_child = child.stdin.take().unwrap();
35        let mut buffer = String::new();
36
37        // Read the CredentialHello
38        output_from_child
39            .read_line(&mut buffer)
40            .context("failed to read hello from credential provider")?;
41        let credential_hello: CredentialHello =
42            serde_json::from_str(&buffer).context("failed to deserialize hello")?;
43        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        }
55
56        // Send the Credential Request
57        let req = CredentialRequest {
58            v: cargo_credential::PROTOCOL_VERSION_1,
59            action: action.clone(),
60            registry: registry.clone(),
61            args: args.to_vec(),
62        };
63        let request = serde_json::to_string(&req).context("failed to serialize request")?;
64        tracing::debug!("credential-process < {req:?}");
65        writeln!(input_to_child, "{request}").context("failed to write to credential provider")?;
66        buffer.clear();
67        output_from_child
68            .read_line(&mut buffer)
69            .context("failed to read response from credential provider")?;
70
71        // Read the Credential Response
72        let response: Result<CredentialResponse, Error> =
73            serde_json::from_str(&buffer).context("failed to deserialize response")?;
74        tracing::debug!("credential-process > {response:?}");
75
76        // Tell the credential process we're done by closing stdin. It should exit cleanly.
77        drop(input_to_child);
78        let status = child.wait().context("credential process never started")?;
79        if !status.success() {
80            return Err(anyhow::anyhow!(
81                "credential process `{}` failed with status {}`",
82                self.path.display(),
83                status
84            )
85            .into());
86        }
87        tracing::trace!("credential process exited successfully");
88        Ok(response)
89    }
90}
91
92impl<'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        }
114    }
115}