cargo/util/credential/
adaptor.rs

1//! Credential provider that launches an external process that only outputs a credential
2
3use std::{
4    io::Read,
5    process::{Command, Stdio},
6};
7
8use anyhow::Context;
9use cargo_credential::{
10    Action, CacheControl, Credential, CredentialResponse, RegistryInfo, Secret,
11};
12
13pub struct BasicProcessCredential {}
14
15impl Credential for BasicProcessCredential {
16    fn perform(
17        &self,
18        registry: &RegistryInfo<'_>,
19        action: &Action<'_>,
20        args: &[&str],
21    ) -> Result<CredentialResponse, cargo_credential::Error> {
22        match action {
23            Action::Get(_) => {
24                let mut args = args.iter();
25                let exe = args.next()
26                    .ok_or("The first argument to `cargo:token-from-stdout` must be a command that prints a token on stdout")?;
27                let args = args.map(|arg| arg.replace("{index_url}", registry.index_url));
28
29                let mut cmd = Command::new(exe);
30                cmd.args(args)
31                    .env("CARGO_REGISTRY_INDEX_URL", registry.index_url);
32                if let Some(name) = registry.name {
33                    cmd.env("CARGO_REGISTRY_NAME_OPT", name);
34                }
35                cmd.stdout(Stdio::piped());
36                let mut child = cmd.spawn().context("failed to spawn credential process")?;
37                let mut buffer = String::new();
38                child
39                    .stdout
40                    .take()
41                    .unwrap()
42                    .read_to_string(&mut buffer)
43                    .context("failed to read from credential provider")?;
44                if let Some(end) = buffer.find('\n') {
45                    if buffer.len() > end + 1 {
46                        return Err(format!(
47                            "process `{}` returned more than one line of output; \
48                            expected a single token",
49                            exe
50                        )
51                        .into());
52                    }
53                    buffer.truncate(end);
54                }
55                let status = child.wait().context("credential process never started")?;
56                if !status.success() {
57                    return Err(format!("process `{}` failed with status `{status}`", exe).into());
58                }
59                Ok(CredentialResponse::Get {
60                    token: Secret::from(buffer),
61                    cache: CacheControl::Session,
62                    operation_independent: true,
63                })
64            }
65            _ => Err(cargo_credential::Error::OperationNotSupported),
66        }
67    }
68}