    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

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

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

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

    pub struct CredentialProcessCredential {
        path: PathBuf,
    }

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

    impl<'a> Credential for CredentialProcessCredential {
        fn perform(
            &self,
            registry: &RegistryInfo<'_>,
            action: &Action<'_>,
            args: &[&str],
        ) -> Result<CredentialResponse, cargo_credential::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")?;
            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();
            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:?}");

            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")?;
            let response: Result<CredentialResponse, cargo_credential::Error> =
                serde_json::from_str(&buffer).context("failed to deserialize response")?;
            tracing::debug!("credential-process > {response:?}");
            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");
            response
        }
    }