summaryrefslogtreecommitdiffstats
path: root/src/tools/cargo/crates/credential/cargo-credential-1password
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-18 02:49:50 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-18 02:49:50 +0000
commit9835e2ae736235810b4ea1c162ca5e65c547e770 (patch)
tree3fcebf40ed70e581d776a8a4c65923e8ec20e026 /src/tools/cargo/crates/credential/cargo-credential-1password
parentReleasing progress-linux version 1.70.0+dfsg2-1~progress7.99u1. (diff)
downloadrustc-9835e2ae736235810b4ea1c162ca5e65c547e770.tar.xz
rustc-9835e2ae736235810b4ea1c162ca5e65c547e770.zip
Merging upstream version 1.71.1+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/tools/cargo/crates/credential/cargo-credential-1password')
-rw-r--r--src/tools/cargo/crates/credential/cargo-credential-1password/Cargo.toml12
-rw-r--r--src/tools/cargo/crates/credential/cargo-credential-1password/src/main.rs314
2 files changed, 0 insertions, 326 deletions
diff --git a/src/tools/cargo/crates/credential/cargo-credential-1password/Cargo.toml b/src/tools/cargo/crates/credential/cargo-credential-1password/Cargo.toml
deleted file mode 100644
index 093fde8e5..000000000
--- a/src/tools/cargo/crates/credential/cargo-credential-1password/Cargo.toml
+++ /dev/null
@@ -1,12 +0,0 @@
-[package]
-name = "cargo-credential-1password"
-version = "0.2.0"
-edition = "2021"
-license = "MIT OR Apache-2.0"
-repository = "https://github.com/rust-lang/cargo"
-description = "A Cargo credential process that stores tokens in a 1password vault."
-
-[dependencies]
-cargo-credential = { version = "0.2.0", path = "../cargo-credential" }
-serde = { version = "1.0.117", features = ["derive"] }
-serde_json = "1.0.59"
diff --git a/src/tools/cargo/crates/credential/cargo-credential-1password/src/main.rs b/src/tools/cargo/crates/credential/cargo-credential-1password/src/main.rs
deleted file mode 100644
index 4f512b717..000000000
--- a/src/tools/cargo/crates/credential/cargo-credential-1password/src/main.rs
+++ /dev/null
@@ -1,314 +0,0 @@
-//! Cargo registry 1password credential process.
-
-use cargo_credential::{Credential, Error};
-use serde::Deserialize;
-use std::io::Read;
-use std::process::{Command, Stdio};
-
-const CARGO_TAG: &str = "cargo-registry";
-
-/// Implementation of 1password keychain access for Cargo registries.
-struct OnePasswordKeychain {
- account: Option<String>,
- vault: Option<String>,
-}
-
-/// 1password Login item type, used for the JSON output of `op item get`.
-#[derive(Deserialize)]
-struct Login {
- fields: Vec<Field>,
-}
-
-#[derive(Deserialize)]
-struct Field {
- id: String,
- value: Option<String>,
-}
-
-/// 1password item from `op items list`.
-#[derive(Deserialize)]
-struct ListItem {
- id: String,
- urls: Vec<Url>,
-}
-
-#[derive(Deserialize)]
-struct Url {
- href: String,
-}
-
-impl OnePasswordKeychain {
- fn new() -> Result<OnePasswordKeychain, Error> {
- let mut args = std::env::args().skip(1);
- let mut action = false;
- let mut account = None;
- let mut vault = None;
- while let Some(arg) = args.next() {
- match arg.as_str() {
- "--account" => {
- account = Some(args.next().ok_or("--account needs an arg")?);
- }
- "--vault" => {
- vault = Some(args.next().ok_or("--vault needs an arg")?);
- }
- s if s.starts_with('-') => {
- return Err(format!("unknown option {}", s).into());
- }
- _ => {
- if action {
- return Err("too many arguments".into());
- } else {
- action = true;
- }
- }
- }
- }
- Ok(OnePasswordKeychain { account, vault })
- }
-
- fn signin(&self) -> Result<Option<String>, Error> {
- // If there are any session env vars, we'll assume that this is the
- // correct account, and that the user knows what they are doing.
- if std::env::vars().any(|(name, _)| name.starts_with("OP_SESSION_")) {
- return Ok(None);
- }
- let mut cmd = Command::new("op");
- cmd.args(&["signin", "--raw"]);
- cmd.stdout(Stdio::piped());
- self.with_tty(&mut cmd)?;
- let mut child = cmd
- .spawn()
- .map_err(|e| format!("failed to spawn `op`: {}", e))?;
- let mut buffer = String::new();
- child
- .stdout
- .as_mut()
- .unwrap()
- .read_to_string(&mut buffer)
- .map_err(|e| format!("failed to get session from `op`: {}", e))?;
- if let Some(end) = buffer.find('\n') {
- buffer.truncate(end);
- }
- let status = child
- .wait()
- .map_err(|e| format!("failed to wait for `op`: {}", e))?;
- if !status.success() {
- return Err(format!("failed to run `op signin`: {}", status).into());
- }
- if buffer.is_empty() {
- // When using CLI integration, `op signin` returns no output,
- // so there is no need to set the session.
- return Ok(None);
- }
- Ok(Some(buffer))
- }
-
- fn make_cmd(&self, session: &Option<String>, args: &[&str]) -> Command {
- let mut cmd = Command::new("op");
- cmd.args(args);
- if let Some(account) = &self.account {
- cmd.arg("--account");
- cmd.arg(account);
- }
- if let Some(vault) = &self.vault {
- cmd.arg("--vault");
- cmd.arg(vault);
- }
- if let Some(session) = session {
- cmd.arg("--session");
- cmd.arg(session);
- }
- cmd
- }
-
- fn with_tty(&self, cmd: &mut Command) -> Result<(), Error> {
- #[cfg(unix)]
- const IN_DEVICE: &str = "/dev/tty";
- #[cfg(windows)]
- const IN_DEVICE: &str = "CONIN$";
- let stdin = std::fs::OpenOptions::new()
- .read(true)
- .write(true)
- .open(IN_DEVICE)?;
- cmd.stdin(stdin);
- Ok(())
- }
-
- fn run_cmd(&self, mut cmd: Command) -> Result<String, Error> {
- cmd.stdout(Stdio::piped());
- let mut child = cmd
- .spawn()
- .map_err(|e| format!("failed to spawn `op`: {}", e))?;
- let mut buffer = String::new();
- child
- .stdout
- .as_mut()
- .unwrap()
- .read_to_string(&mut buffer)
- .map_err(|e| format!("failed to read `op` output: {}", e))?;
- let status = child
- .wait()
- .map_err(|e| format!("failed to wait for `op`: {}", e))?;
- if !status.success() {
- return Err(format!("`op` command exit error: {}", status).into());
- }
- Ok(buffer)
- }
-
- fn search(&self, session: &Option<String>, index_url: &str) -> Result<Option<String>, Error> {
- let cmd = self.make_cmd(
- session,
- &[
- "items",
- "list",
- "--categories",
- "Login",
- "--tags",
- CARGO_TAG,
- "--format",
- "json",
- ],
- );
- let buffer = self.run_cmd(cmd)?;
- let items: Vec<ListItem> = serde_json::from_str(&buffer)
- .map_err(|e| format!("failed to deserialize JSON from 1password list: {}", e))?;
- let mut matches = items
- .into_iter()
- .filter(|item| item.urls.iter().any(|url| url.href == index_url));
- match matches.next() {
- Some(login) => {
- // Should this maybe just sort on `updatedAt` and return the newest one?
- if matches.next().is_some() {
- return Err(format!(
- "too many 1password logins match registry `{}`, \
- consider deleting the excess entries",
- index_url
- )
- .into());
- }
- Ok(Some(login.id))
- }
- None => Ok(None),
- }
- }
-
- fn modify(
- &self,
- session: &Option<String>,
- id: &str,
- token: &str,
- _name: Option<&str>,
- ) -> Result<(), Error> {
- let cmd = self.make_cmd(
- session,
- &["item", "edit", id, &format!("password={}", token)],
- );
- self.run_cmd(cmd)?;
- Ok(())
- }
-
- fn create(
- &self,
- session: &Option<String>,
- index_url: &str,
- token: &str,
- name: Option<&str>,
- ) -> Result<(), Error> {
- let title = match name {
- Some(name) => format!("Cargo registry token for {}", name),
- None => "Cargo registry token".to_string(),
- };
- let mut cmd = self.make_cmd(
- session,
- &[
- "item",
- "create",
- "--category",
- "Login",
- &format!("password={}", token),
- &format!("url={}", index_url),
- "--title",
- &title,
- "--tags",
- CARGO_TAG,
- ],
- );
- // For unknown reasons, `op item create` seems to not be happy if
- // stdin is not a tty. Otherwise it returns with a 0 exit code without
- // doing anything.
- self.with_tty(&mut cmd)?;
- self.run_cmd(cmd)?;
- Ok(())
- }
-
- fn get_token(&self, session: &Option<String>, id: &str) -> Result<String, Error> {
- let cmd = self.make_cmd(session, &["item", "get", "--format=json", id]);
- let buffer = self.run_cmd(cmd)?;
- let item: Login = serde_json::from_str(&buffer)
- .map_err(|e| format!("failed to deserialize JSON from 1password get: {}", e))?;
- let password = item.fields.into_iter().find(|item| item.id == "password");
- match password {
- Some(password) => password
- .value
- .ok_or_else(|| format!("missing password value for entry").into()),
- None => Err("could not find password field".into()),
- }
- }
-
- fn delete(&self, session: &Option<String>, id: &str) -> Result<(), Error> {
- let cmd = self.make_cmd(session, &["item", "delete", id]);
- self.run_cmd(cmd)?;
- Ok(())
- }
-}
-
-impl Credential for OnePasswordKeychain {
- fn name(&self) -> &'static str {
- env!("CARGO_PKG_NAME")
- }
-
- fn get(&self, index_url: &str) -> Result<String, Error> {
- let session = self.signin()?;
- if let Some(id) = self.search(&session, index_url)? {
- self.get_token(&session, &id)
- } else {
- return Err(format!(
- "no 1password entry found for registry `{}`, try `cargo login` to add a token",
- index_url
- )
- .into());
- }
- }
-
- fn store(&self, index_url: &str, token: &str, name: Option<&str>) -> Result<(), Error> {
- let session = self.signin()?;
- // Check if an item already exists.
- if let Some(id) = self.search(&session, index_url)? {
- self.modify(&session, &id, token, name)
- } else {
- self.create(&session, index_url, token, name)
- }
- }
-
- fn erase(&self, index_url: &str) -> Result<(), Error> {
- let session = self.signin()?;
- // Check if an item already exists.
- if let Some(id) = self.search(&session, index_url)? {
- self.delete(&session, &id)?;
- } else {
- eprintln!("not currently logged in to `{}`", index_url);
- }
- Ok(())
- }
-}
-
-fn main() {
- let op = match OnePasswordKeychain::new() {
- Ok(op) => op,
- Err(e) => {
- eprintln!("error: {}", e);
- std::process::exit(1);
- }
- };
- cargo_credential::main(op);
-}