mirror of
https://github.com/rust-lang/cargo.git
synced 2025-10-01 11:30:39 +00:00
Update 1password to the version 2 CLI
This commit is contained in:
parent
82c3bb79e3
commit
7a67332d3c
@ -11,37 +11,30 @@ const CARGO_TAG: &str = "cargo-registry";
|
|||||||
struct OnePasswordKeychain {
|
struct OnePasswordKeychain {
|
||||||
account: Option<String>,
|
account: Option<String>,
|
||||||
vault: Option<String>,
|
vault: Option<String>,
|
||||||
sign_in_address: Option<String>,
|
|
||||||
email: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 1password Login item type, used for the JSON output of `op get item`.
|
/// 1password Login item type, used for the JSON output of `op item get`.
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct Login {
|
struct Login {
|
||||||
details: Details,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct Details {
|
|
||||||
fields: Vec<Field>,
|
fields: Vec<Field>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct Field {
|
struct Field {
|
||||||
designation: String,
|
id: String,
|
||||||
value: String,
|
value: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 1password item from `op list items`.
|
/// 1password item from `op items list`.
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct ListItem {
|
struct ListItem {
|
||||||
uuid: String,
|
id: String,
|
||||||
overview: Overview,
|
urls: Vec<Url>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct Overview {
|
struct Url {
|
||||||
url: String,
|
href: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OnePasswordKeychain {
|
impl OnePasswordKeychain {
|
||||||
@ -50,8 +43,6 @@ impl OnePasswordKeychain {
|
|||||||
let mut action = false;
|
let mut action = false;
|
||||||
let mut account = None;
|
let mut account = None;
|
||||||
let mut vault = None;
|
let mut vault = None;
|
||||||
let mut sign_in_address = None;
|
|
||||||
let mut email = None;
|
|
||||||
while let Some(arg) = args.next() {
|
while let Some(arg) = args.next() {
|
||||||
match arg.as_str() {
|
match arg.as_str() {
|
||||||
"--account" => {
|
"--account" => {
|
||||||
@ -60,12 +51,6 @@ impl OnePasswordKeychain {
|
|||||||
"--vault" => {
|
"--vault" => {
|
||||||
vault = Some(args.next().ok_or("--vault needs an arg")?);
|
vault = Some(args.next().ok_or("--vault needs an arg")?);
|
||||||
}
|
}
|
||||||
"--sign-in-address" => {
|
|
||||||
sign_in_address = Some(args.next().ok_or("--sign-in-address needs an arg")?);
|
|
||||||
}
|
|
||||||
"--email" => {
|
|
||||||
email = Some(args.next().ok_or("--email needs an arg")?);
|
|
||||||
}
|
|
||||||
s if s.starts_with('-') => {
|
s if s.starts_with('-') => {
|
||||||
return Err(format!("unknown option {}", s).into());
|
return Err(format!("unknown option {}", s).into());
|
||||||
}
|
}
|
||||||
@ -78,15 +63,7 @@ impl OnePasswordKeychain {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if sign_in_address.is_none() && email.is_some() {
|
Ok(OnePasswordKeychain { account, vault })
|
||||||
return Err("--email requires --sign-in-address".into());
|
|
||||||
}
|
|
||||||
Ok(OnePasswordKeychain {
|
|
||||||
account,
|
|
||||||
vault,
|
|
||||||
sign_in_address,
|
|
||||||
email,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signin(&self) -> Result<Option<String>, Error> {
|
fn signin(&self) -> Result<Option<String>, Error> {
|
||||||
@ -96,24 +73,9 @@ impl OnePasswordKeychain {
|
|||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
let mut cmd = Command::new("op");
|
let mut cmd = Command::new("op");
|
||||||
cmd.arg("signin");
|
cmd.args(&["signin", "--raw"]);
|
||||||
if let Some(addr) = &self.sign_in_address {
|
|
||||||
cmd.arg(addr);
|
|
||||||
if let Some(email) = &self.email {
|
|
||||||
cmd.arg(email);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cmd.arg("--raw");
|
|
||||||
cmd.stdout(Stdio::piped());
|
cmd.stdout(Stdio::piped());
|
||||||
#[cfg(unix)]
|
self.with_tty(&mut cmd)?;
|
||||||
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);
|
|
||||||
let mut child = cmd
|
let mut child = cmd
|
||||||
.spawn()
|
.spawn()
|
||||||
.map_err(|e| format!("failed to spawn `op`: {}", e))?;
|
.map_err(|e| format!("failed to spawn `op`: {}", e))?;
|
||||||
@ -133,6 +95,11 @@ impl OnePasswordKeychain {
|
|||||||
if !status.success() {
|
if !status.success() {
|
||||||
return Err(format!("failed to run `op signin`: {}", status).into());
|
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))
|
Ok(Some(buffer))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,6 +121,19 @@ impl OnePasswordKeychain {
|
|||||||
cmd
|
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> {
|
fn run_cmd(&self, mut cmd: Command) -> Result<String, Error> {
|
||||||
cmd.stdout(Stdio::piped());
|
cmd.stdout(Stdio::piped());
|
||||||
let mut child = cmd
|
let mut child = cmd
|
||||||
@ -179,12 +159,14 @@ impl OnePasswordKeychain {
|
|||||||
let cmd = self.make_cmd(
|
let cmd = self.make_cmd(
|
||||||
session,
|
session,
|
||||||
&[
|
&[
|
||||||
"list",
|
|
||||||
"items",
|
"items",
|
||||||
|
"list",
|
||||||
"--categories",
|
"--categories",
|
||||||
"Login",
|
"Login",
|
||||||
"--tags",
|
"--tags",
|
||||||
CARGO_TAG,
|
CARGO_TAG,
|
||||||
|
"--format",
|
||||||
|
"json",
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
let buffer = self.run_cmd(cmd)?;
|
let buffer = self.run_cmd(cmd)?;
|
||||||
@ -192,7 +174,7 @@ impl OnePasswordKeychain {
|
|||||||
.map_err(|e| format!("failed to deserialize JSON from 1password list: {}", e))?;
|
.map_err(|e| format!("failed to deserialize JSON from 1password list: {}", e))?;
|
||||||
let mut matches = items
|
let mut matches = items
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|item| item.overview.url == index_url);
|
.filter(|item| item.urls.iter().any(|url| url.href == index_url));
|
||||||
match matches.next() {
|
match matches.next() {
|
||||||
Some(login) => {
|
Some(login) => {
|
||||||
// Should this maybe just sort on `updatedAt` and return the newest one?
|
// Should this maybe just sort on `updatedAt` and return the newest one?
|
||||||
@ -204,7 +186,7 @@ impl OnePasswordKeychain {
|
|||||||
)
|
)
|
||||||
.into());
|
.into());
|
||||||
}
|
}
|
||||||
Ok(Some(login.uuid))
|
Ok(Some(login.id))
|
||||||
}
|
}
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
}
|
}
|
||||||
@ -213,13 +195,13 @@ impl OnePasswordKeychain {
|
|||||||
fn modify(
|
fn modify(
|
||||||
&self,
|
&self,
|
||||||
session: &Option<String>,
|
session: &Option<String>,
|
||||||
uuid: &str,
|
id: &str,
|
||||||
token: &str,
|
token: &str,
|
||||||
_name: Option<&str>,
|
_name: Option<&str>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let cmd = self.make_cmd(
|
let cmd = self.make_cmd(
|
||||||
session,
|
session,
|
||||||
&["edit", "item", uuid, &format!("password={}", token)],
|
&["item", "edit", id, &format!("password={}", token)],
|
||||||
);
|
);
|
||||||
self.run_cmd(cmd)?;
|
self.run_cmd(cmd)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -236,11 +218,12 @@ impl OnePasswordKeychain {
|
|||||||
Some(name) => format!("Cargo registry token for {}", name),
|
Some(name) => format!("Cargo registry token for {}", name),
|
||||||
None => "Cargo registry token".to_string(),
|
None => "Cargo registry token".to_string(),
|
||||||
};
|
};
|
||||||
let cmd = self.make_cmd(
|
let mut cmd = self.make_cmd(
|
||||||
session,
|
session,
|
||||||
&[
|
&[
|
||||||
"create",
|
|
||||||
"item",
|
"item",
|
||||||
|
"create",
|
||||||
|
"--category",
|
||||||
"Login",
|
"Login",
|
||||||
&format!("password={}", token),
|
&format!("password={}", token),
|
||||||
&format!("url={}", index_url),
|
&format!("url={}", index_url),
|
||||||
@ -250,28 +233,30 @@ impl OnePasswordKeychain {
|
|||||||
CARGO_TAG,
|
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)?;
|
self.run_cmd(cmd)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_token(&self, session: &Option<String>, uuid: &str) -> Result<String, Error> {
|
fn get_token(&self, session: &Option<String>, id: &str) -> Result<String, Error> {
|
||||||
let cmd = self.make_cmd(session, &["get", "item", uuid]);
|
let cmd = self.make_cmd(session, &["item", "get", "--format=json", id]);
|
||||||
let buffer = self.run_cmd(cmd)?;
|
let buffer = self.run_cmd(cmd)?;
|
||||||
let item: Login = serde_json::from_str(&buffer)
|
let item: Login = serde_json::from_str(&buffer)
|
||||||
.map_err(|e| format!("failed to deserialize JSON from 1password get: {}", e))?;
|
.map_err(|e| format!("failed to deserialize JSON from 1password get: {}", e))?;
|
||||||
let password = item
|
let password = item.fields.into_iter().find(|item| item.id == "password");
|
||||||
.details
|
|
||||||
.fields
|
|
||||||
.into_iter()
|
|
||||||
.find(|item| item.designation == "password");
|
|
||||||
match password {
|
match password {
|
||||||
Some(password) => Ok(password.value),
|
Some(password) => password
|
||||||
|
.value
|
||||||
|
.ok_or_else(|| format!("missing password value for entry").into()),
|
||||||
None => Err("could not find password field".into()),
|
None => Err("could not find password field".into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete(&self, session: &Option<String>, uuid: &str) -> Result<(), Error> {
|
fn delete(&self, session: &Option<String>, id: &str) -> Result<(), Error> {
|
||||||
let cmd = self.make_cmd(session, &["delete", "item", uuid]);
|
let cmd = self.make_cmd(session, &["item", "delete", id]);
|
||||||
self.run_cmd(cmd)?;
|
self.run_cmd(cmd)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -284,8 +269,8 @@ impl Credential for OnePasswordKeychain {
|
|||||||
|
|
||||||
fn get(&self, index_url: &str) -> Result<String, Error> {
|
fn get(&self, index_url: &str) -> Result<String, Error> {
|
||||||
let session = self.signin()?;
|
let session = self.signin()?;
|
||||||
if let Some(uuid) = self.search(&session, index_url)? {
|
if let Some(id) = self.search(&session, index_url)? {
|
||||||
self.get_token(&session, &uuid)
|
self.get_token(&session, &id)
|
||||||
} else {
|
} else {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"no 1password entry found for registry `{}`, try `cargo login` to add a token",
|
"no 1password entry found for registry `{}`, try `cargo login` to add a token",
|
||||||
@ -298,8 +283,8 @@ impl Credential for OnePasswordKeychain {
|
|||||||
fn store(&self, index_url: &str, token: &str, name: Option<&str>) -> Result<(), Error> {
|
fn store(&self, index_url: &str, token: &str, name: Option<&str>) -> Result<(), Error> {
|
||||||
let session = self.signin()?;
|
let session = self.signin()?;
|
||||||
// Check if an item already exists.
|
// Check if an item already exists.
|
||||||
if let Some(uuid) = self.search(&session, index_url)? {
|
if let Some(id) = self.search(&session, index_url)? {
|
||||||
self.modify(&session, &uuid, token, name)
|
self.modify(&session, &id, token, name)
|
||||||
} else {
|
} else {
|
||||||
self.create(&session, index_url, token, name)
|
self.create(&session, index_url, token, name)
|
||||||
}
|
}
|
||||||
@ -308,8 +293,8 @@ impl Credential for OnePasswordKeychain {
|
|||||||
fn erase(&self, index_url: &str) -> Result<(), Error> {
|
fn erase(&self, index_url: &str) -> Result<(), Error> {
|
||||||
let session = self.signin()?;
|
let session = self.signin()?;
|
||||||
// Check if an item already exists.
|
// Check if an item already exists.
|
||||||
if let Some(uuid) = self.search(&session, index_url)? {
|
if let Some(id) = self.search(&session, index_url)? {
|
||||||
self.delete(&session, &uuid)?;
|
self.delete(&session, &id)?;
|
||||||
} else {
|
} else {
|
||||||
eprintln!("not currently logged in to `{}`", index_url);
|
eprintln!("not currently logged in to `{}`", index_url);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user