Align sq pki link add's user ID specification with sq pki certify.
- Align how user IDs are specified using `sq pki link add` with `sq pki certify`. Specifically, add a `--add-userid` argument and remove the `--petname` argument.
This commit is contained in:
parent
f0bfdfd1cd
commit
7dee04b9b3
4
NEWS
4
NEWS
@ -81,6 +81,10 @@
|
||||
- Removed `sq pki link add`'s positional argument for specifying a
|
||||
user ID directly or by email address. Use the named arguments,
|
||||
`--userid` or `--email` instead.
|
||||
- Add `--add-userid` to `sq pki link add`. This aligns it with `sq
|
||||
pki certify`.
|
||||
- Removed `sq pki link add`'s `--petname` argument. Use `--userid`
|
||||
in conjunction with `--add-userid` instead.
|
||||
|
||||
* Changes in 0.38.0
|
||||
** Notable changes
|
||||
|
@ -5,6 +5,8 @@ use clap::{ArgGroup, Parser, Subcommand};
|
||||
use crate::cli::examples::*;
|
||||
use crate::cli::types::CertDesignators;
|
||||
use crate::cli::types::cert_designator;
|
||||
use crate::cli::types::UserIDDesignators;
|
||||
use crate::cli::types::userid_designator;
|
||||
use crate::cli::types::Expiration;
|
||||
use crate::cli::types::TrustAmount;
|
||||
|
||||
@ -281,52 +283,9 @@ to force the signature to be re-created anyway.",
|
||||
cert_designator::CertPrefix,
|
||||
cert_designator::OneValue>,
|
||||
|
||||
#[clap(
|
||||
long = "all",
|
||||
conflicts_with_all = &[ "userid", "email", "petname" ],
|
||||
required = false,
|
||||
help = "Link all valid self-signed User ID to the certificate.",
|
||||
long_help = "Link all valid self-signed User ID to the certificate.",
|
||||
)]
|
||||
pub all: bool,
|
||||
|
||||
#[clap(
|
||||
long = "userid",
|
||||
value_name = "USERID",
|
||||
required = false,
|
||||
help = "A User ID to link to the certificate.",
|
||||
long_help = "A User ID to link to the certificate. This must match \
|
||||
a self-signed User ID. To link a User ID to the \
|
||||
certificate that does not have a self-signature, use \
|
||||
`--petname`.",
|
||||
)]
|
||||
pub userid: Vec<String>,
|
||||
#[clap(
|
||||
long = "email",
|
||||
value_name = "EMAIL",
|
||||
required = false,
|
||||
help = "An email address to link to the certificate.",
|
||||
long_help = "An email address to link to the certificate. The email \
|
||||
address must match the email address of a \
|
||||
self-signed User ID. To link an email address to the \
|
||||
certificate that does not appear in a self-signed \
|
||||
User ID, use `--petname`. If the specified email \
|
||||
appears in multiple self-signed User IDs, then all of \
|
||||
them are linked.",
|
||||
)]
|
||||
pub email: Vec<String>,
|
||||
#[clap(
|
||||
long = "petname",
|
||||
value_name = "PETNAME",
|
||||
required = false,
|
||||
help = "A User ID to link to the certificate.",
|
||||
long_help = "A User ID to link to the certificate. Unlike `--userid`, \
|
||||
this does not need to match a self-signed User ID. Bare \
|
||||
email address are automatically wrapped in angle brackets. \
|
||||
That is if `alice@example.org` is provided, it is \
|
||||
silently converted to `<alice@example.org>`.",
|
||||
)]
|
||||
pub petname: Vec<String>,
|
||||
#[command(flatten)]
|
||||
pub userids: UserIDDesignators<
|
||||
userid_designator::MaybeSelfSignedUserIDEmailAllArgs>,
|
||||
}
|
||||
|
||||
const ADD_EXAMPLES: Actions = Actions {
|
||||
|
@ -12,14 +12,23 @@ pub type UserIDArg = typenum::U1;
|
||||
/// Adds a `--email` argument.
|
||||
pub type EmailArg = typenum::U2;
|
||||
|
||||
/// Adds a `--all` argument.
|
||||
pub type AllUserIDsArg = typenum::U4;
|
||||
|
||||
/// Adds a `--add-userid` argument.
|
||||
pub type AddUserIDArg = typenum::U4;
|
||||
pub type AddUserIDArg = typenum::U8;
|
||||
|
||||
/// Enables --userid, --email, and --add-userid.
|
||||
pub type MaybeSelfSignedUserIDEmailArgs
|
||||
= <<UserIDArg as std::ops::BitOr<EmailArg>>::Output
|
||||
as std::ops::BitOr<AddUserIDArg>>::Output;
|
||||
|
||||
/// Enables --userid, --email, --all, and --add-userid.
|
||||
pub type MaybeSelfSignedUserIDEmailAllArgs
|
||||
= <<<UserIDArg as std::ops::BitOr<EmailArg>>::Output
|
||||
as std::ops::BitOr<AllUserIDsArg>>::Output
|
||||
as std::ops::BitOr<AddUserIDArg>>::Output;
|
||||
|
||||
/// Argument parser options.
|
||||
|
||||
/// Normally it is possible to designate multiple certificates. This
|
||||
@ -81,6 +90,9 @@ pub struct UserIDDesignators<Arguments, Options=typenum::U0>
|
||||
/// The set of certificate designators.
|
||||
pub designators: Vec<UserIDDesignator>,
|
||||
|
||||
/// Use all self-signed user IDs.
|
||||
pub all: Option<bool>,
|
||||
|
||||
pub add_userid: Option<bool>,
|
||||
|
||||
arguments: std::marker::PhantomData<(Arguments, Options)>,
|
||||
@ -92,6 +104,8 @@ impl<Arguments, Options> std::fmt::Debug
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("UserIDDesignators")
|
||||
.field("designators", &self.designators)
|
||||
.field("all", &self.all)
|
||||
.field("add_userid", &self.add_userid)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@ -113,6 +127,13 @@ impl<Arguments, Options> UserIDDesignators<Arguments, Options> {
|
||||
self.designators.iter()
|
||||
}
|
||||
|
||||
/// Returns whether the all flag was set.
|
||||
///
|
||||
/// If the flag was not enabled, returns `None`.
|
||||
pub fn all(&self) -> Option<bool> {
|
||||
self.all
|
||||
}
|
||||
|
||||
/// Returns whether the add user ID flag was set.
|
||||
///
|
||||
/// If the flag was not enabled, returns `None`.
|
||||
@ -132,6 +153,7 @@ where
|
||||
let arguments = Arguments::to_usize();
|
||||
let userid_arg = (arguments & UserIDArg::to_usize()) > 0;
|
||||
let email_arg = (arguments & EmailArg::to_usize()) > 0;
|
||||
let all_arg = (arguments & AllUserIDsArg::to_usize()) > 0;
|
||||
let add_userid_arg = (arguments & AddUserIDArg::to_usize()) > 0;
|
||||
|
||||
let options = Options::to_usize();
|
||||
@ -200,6 +222,18 @@ where
|
||||
arg_group = arg_group.arg(full_name);
|
||||
}
|
||||
|
||||
if all_arg {
|
||||
let full_name = "all";
|
||||
cmd = cmd.arg(
|
||||
clap::Arg::new(&full_name)
|
||||
.long(&full_name)
|
||||
.requires(&group)
|
||||
.action(clap::ArgAction::SetTrue)
|
||||
.help("\
|
||||
Uses all self-signed user IDs"));
|
||||
arg_group = arg_group.arg(full_name);
|
||||
}
|
||||
|
||||
if add_userid_arg {
|
||||
let full_name = "add-userid";
|
||||
cmd = cmd.arg(
|
||||
@ -245,6 +279,7 @@ where
|
||||
let arguments = Arguments::to_usize();
|
||||
let userid_arg = (arguments & UserIDArg::to_usize()) > 0;
|
||||
let email_arg = (arguments & EmailArg::to_usize()) > 0;
|
||||
let all_arg = (arguments & AllUserIDsArg::to_usize()) > 0;
|
||||
let add_userid_arg = (arguments & AddUserIDArg::to_usize()) > 0;
|
||||
|
||||
let mut designators = Vec::new();
|
||||
@ -278,6 +313,16 @@ where
|
||||
None
|
||||
};
|
||||
|
||||
self.all = if all_arg {
|
||||
if matches.get_flag("all") {
|
||||
Some(true)
|
||||
} else {
|
||||
Some(false)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
self.designators = designators;
|
||||
Ok(())
|
||||
}
|
||||
@ -288,6 +333,7 @@ where
|
||||
let mut designators = Self {
|
||||
designators: Vec::new(),
|
||||
arguments: std::marker::PhantomData,
|
||||
all: None,
|
||||
add_userid: None,
|
||||
};
|
||||
|
||||
@ -501,4 +547,49 @@ mod test {
|
||||
]);
|
||||
assert!(m.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn userid_designators_all() {
|
||||
use clap::Parser;
|
||||
use clap::CommandFactory;
|
||||
use clap::FromArgMatches;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(name = "prog")]
|
||||
struct CLI {
|
||||
#[command(flatten)]
|
||||
pub userids: UserIDDesignators<MaybeSelfSignedUserIDEmailAllArgs>,
|
||||
}
|
||||
|
||||
let command = CLI::command();
|
||||
|
||||
// Check if --all is recognized.
|
||||
let m = command.clone().try_get_matches_from(vec![
|
||||
"prog",
|
||||
"--userid", "alice",
|
||||
]);
|
||||
let m = m.expect("valid arguments");
|
||||
let c = CLI::from_arg_matches(&m).expect("ok");
|
||||
assert_eq!(c.userids.designators.len(), 1);
|
||||
assert_eq!(c.userids.all(), Some(false));
|
||||
|
||||
let m = command.clone().try_get_matches_from(vec![
|
||||
"prog",
|
||||
"--all"
|
||||
]);
|
||||
let m = m.expect("valid arguments");
|
||||
let c = CLI::from_arg_matches(&m).expect("ok");
|
||||
assert_eq!(c.userids.designators.len(), 0);
|
||||
assert_eq!(c.userids.all(), Some(true));
|
||||
|
||||
let m = command.clone().try_get_matches_from(vec![
|
||||
"prog",
|
||||
"--userid", "alice",
|
||||
"--all",
|
||||
]);
|
||||
let m = m.expect("valid arguments");
|
||||
let c = CLI::from_arg_matches(&m).expect("ok");
|
||||
assert_eq!(c.userids.designators.len(), 1);
|
||||
assert_eq!(c.userids.all(), Some(true));
|
||||
}
|
||||
}
|
||||
|
@ -203,40 +203,8 @@ pub fn add(sq: Sq, c: link::AddCommand)
|
||||
let (cert, _from_file)
|
||||
= sq.resolve_cert(&c.cert, sequoia_wot::FULLY_TRUSTED)?;
|
||||
|
||||
let mut userids =
|
||||
check_userids(&sq, &cert, true, &c.userid, &c.email)
|
||||
.context("sq pki link add: Invalid User IDs")?;
|
||||
userids.extend(c.petname.iter().map(|petname| {
|
||||
// If it is a bare email, we wrap it in angle brackets.
|
||||
if UserIDQueryParams::is_email(petname).is_ok() {
|
||||
UserID::from(&format!("<{}>", petname)[..])
|
||||
} else {
|
||||
UserID::from(&petname[..])
|
||||
}
|
||||
}));
|
||||
|
||||
let vc = cert.with_policy(sq.policy, Some(sq.time))?;
|
||||
|
||||
let user_supplied_userids = if userids.is_empty() {
|
||||
if c.all {
|
||||
userids = vc.userids().map(|ua| ua.userid().clone()).collect();
|
||||
} else {
|
||||
wprintln!("No User IDs specified. \
|
||||
Pass \"--all\" or one or more User IDs. \
|
||||
{}'s self-signed User IDs are:",
|
||||
cert.fingerprint());
|
||||
for (i, userid) in vc.userids().enumerate() {
|
||||
wprintln!(" {}. {:?}",
|
||||
i + 1,
|
||||
String::from_utf8_lossy(userid.value()));
|
||||
}
|
||||
return Err(anyhow::anyhow!("No User IDs specified"));
|
||||
}
|
||||
|
||||
false
|
||||
} else {
|
||||
true
|
||||
};
|
||||
let userids = c.userids.resolve(&vc)?;
|
||||
|
||||
let trust_depth: u8 = if let Some(depth) = c.depth {
|
||||
depth
|
||||
@ -293,7 +261,7 @@ pub fn add(sq: Sq, c: link::AddCommand)
|
||||
&cert,
|
||||
&userids[..],
|
||||
true, // Add userid.
|
||||
user_supplied_userids,
|
||||
true, // User-supplied user IDs.
|
||||
&templates,
|
||||
trust_depth,
|
||||
if star {
|
||||
|
@ -1,6 +1,8 @@
|
||||
use sequoia_openpgp as openpgp;
|
||||
use openpgp::cert::amalgamation::ValidAmalgamation;
|
||||
use openpgp::cert::ValidCert;
|
||||
use openpgp::packet::UserID;
|
||||
use openpgp::types::RevocationStatus;
|
||||
|
||||
use crate::Result;
|
||||
use crate::cli::types::UserIDDesignators;
|
||||
@ -16,6 +18,26 @@ impl<Arguments, Options> UserIDDesignators<Arguments, Options> {
|
||||
let mut missing = false;
|
||||
let mut bad = None;
|
||||
|
||||
if let Some(true) = self.all() {
|
||||
let all_userids = vc.userids()
|
||||
.filter_map(|ua| {
|
||||
if let RevocationStatus::Revoked(_) = ua.revocation_status() {
|
||||
None
|
||||
} else {
|
||||
Some(ua.userid().clone())
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if all_userids.is_empty() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"{} has no valid self-signed user IDs",
|
||||
vc.fingerprint()));
|
||||
}
|
||||
|
||||
userids.extend(all_userids);
|
||||
}
|
||||
|
||||
for designator in self.iter() {
|
||||
match designator {
|
||||
UserIDDesignator::UserID(userid) => {
|
||||
|
Loading…
Reference in New Issue
Block a user