#
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}