#
cargo/util/credential/
process.rs

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115

    //! Credential provider that launches an external process using Cargo's credential
    //! protocol.

    use std::{
        io::{BufRead, BufReader, Write},
        path::PathBuf,
        process::{Child, Command, Stdio},
    };

    use anyhow::Context;
    use cargo_credential::{
        Action, Credential, CredentialHello, CredentialRequest, CredentialResponse, Error, RegistryInfo,
    };

    pub struct CredentialProcessCredential {
        path: PathBuf,
    }

    impl<'a> CredentialProcessCredential {
        pub fn new(path: &str) -> Self {
            Self {
                path: PathBuf::from(path),
            }
        }

        fn run(
            &self,
            child: &mut Child,
            action: &Action<'_>,
            registry: &RegistryInfo<'_>,
            args: &[&str],
        ) -> Result<Result<CredentialResponse, Error>, Error> {
            let mut output_from_child = BufReader::new(child.stdout.take().unwrap());
            let mut input_to_child = child.stdin.take().unwrap();
            let mut buffer = String::new();

            // Read the CredentialHello
            output_from_child
                .read_line(&mut buffer)
                .context("failed to read hello from credential provider")?;
            let credential_hello: CredentialHello =
                serde_json::from_str(&buffer).context("failed to deserialize hello")?;
            tracing::debug!("credential-process > {credential_hello:?}");
            if !credential_hello
                .v
                .contains(&cargo_credential::PROTOCOL_VERSION_1)
            {
                return Err(format!(
                    "credential provider supports protocol versions {:?}, while Cargo supports {:?}",
                    credential_hello.v,
                    [cargo_credential::PROTOCOL_VERSION_1]
                )
                .into());
            }

            // Send the Credential Request
            let req = CredentialRequest {
                v: cargo_credential::PROTOCOL_VERSION_1,
                action: action.clone(),
                registry: registry.clone(),
                args: args.to_vec(),
            };
            let request = serde_json::to_string(&req).context("failed to serialize request")?;
            tracing::debug!("credential-process < {req:?}");
            writeln!(input_to_child, "{request}").context("failed to write to credential provider")?;
            buffer.clear();
            output_from_child
                .read_line(&mut buffer)
                .context("failed to read response from credential provider")?;

            // Read the Credential Response
            let response: Result<CredentialResponse, Error> =
                serde_json::from_str(&buffer).context("failed to deserialize response")?;
            tracing::debug!("credential-process > {response:?}");

            // Tell the credential process we're done by closing stdin. It should exit cleanly.
            drop(input_to_child);
            let status = child.wait().context("credential process never started")?;
            if !status.success() {
                return Err(anyhow::anyhow!(
                    "credential process `{}` failed with status {}`",
                    self.path.display(),
                    status
                )
                .into());
            }
            tracing::trace!("credential process exited successfully");
            Ok(response)
        }
    }

    impl<'a> Credential for CredentialProcessCredential {
        fn perform(
            &self,
            registry: &RegistryInfo<'_>,
            action: &Action<'_>,
            args: &[&str],
        ) -> Result<CredentialResponse, Error> {
            let mut cmd = Command::new(&self.path);
            cmd.stdout(Stdio::piped());
            cmd.stdin(Stdio::piped());
            cmd.arg("--cargo-plugin");
            tracing::debug!("credential-process: {cmd:?}");
            let mut child = cmd.spawn().context("failed to spawn credential process")?;
            match self.run(&mut child, action, registry, args) {
                Err(e) => {
                    // Since running the credential process itself failed, ensure the
                    // process is stopped.
                    let _ = child.kill();
                    Err(e)
                }
                Ok(response) => response,
            }
        }
    }