Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make yes/no prompts typo tolerant, and accept "y" or "n" as inputs #498

Merged
merged 2 commits into from
Dec 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 20 additions & 10 deletions src/integration/tests/boot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,22 +189,32 @@ async fn standard_boot_e2e() {

assert_eq!(
&stdout.next().unwrap().unwrap(),
"Is this the correct namespace name: quit-coding-to-vape? (yes/no)"
"Is this the correct namespace name: quit-coding-to-vape? (y/n)"
);
stdin.write_all("yes\n".as_bytes()).expect("Failed to write to stdin");
stdin.write_all("y\n".as_bytes()).expect("Failed to write to stdin");

assert_eq!(
&stdout.next().unwrap().unwrap(),
"Is this the correct namespace nonce: 2? (yes/no)"
"Is this the correct namespace nonce: 2? (y/n)"
);
stdin.write_all("yes\n".as_bytes()).expect("Failed to write to stdin");
// On purpose, try to input a bad value, neither yes or no
stdin
.write_all("maybe\n".as_bytes())
.expect("Failed to write to stdin");

assert_eq!(
&stdout.next().unwrap().unwrap(),
"Is this the correct pivot restart policy: RestartPolicy::Never? (yes/no)"
"Please answer with either \"yes\" (y) or \"no\" (n)"
);
// Try the longer option ("yes" rather than "y")
stdin.write_all("yes\n".as_bytes()).expect("Failed to write to stdin");

assert_eq!(
&stdout.next().unwrap().unwrap(),
"Is this the correct pivot restart policy: RestartPolicy::Never? (y/n)"
);
stdin.write_all("y\n".as_bytes()).expect("Failed to write to stdin");

assert_eq!(
&stdout.next().unwrap().unwrap(),
"Are these the correct pivot args:"
Expand All @@ -213,8 +223,8 @@ async fn standard_boot_e2e() {
&stdout.next().unwrap().unwrap(),
"[\"--msg\", \"testing420\"]?"
);
assert_eq!(&stdout.next().unwrap().unwrap(), "(yes/no)");
stdin.write_all("yes\n".as_bytes()).expect("Failed to write to stdin");
assert_eq!(&stdout.next().unwrap().unwrap(), "(y/n)");
stdin.write_all("y\n".as_bytes()).expect("Failed to write to stdin");

// Wait for the command to write the approval and exit
assert!(child.wait().unwrap().success());
Expand Down Expand Up @@ -390,19 +400,19 @@ async fn standard_boot_e2e() {
// Answer prompts with yes
assert_eq!(
&stdout.next().unwrap().unwrap(),
"Is this the correct namespace name: quit-coding-to-vape? (yes/no)"
"Is this the correct namespace name: quit-coding-to-vape? (y/n)"
);
stdin.write_all("yes\n".as_bytes()).expect("Failed to write to stdin");

assert_eq!(
&stdout.next().unwrap().unwrap(),
"Is this the correct namespace nonce: 2? (yes/no)"
"Is this the correct namespace nonce: 2? (y/n)"
);
stdin.write_all("yes\n".as_bytes()).expect("Failed to write to stdin");

assert_eq!(
&stdout.next().unwrap().unwrap(),
"Does this AWS IAM role belong to the intended organization: arn:aws:iam::123456789012:role/Webserver? (yes/no)"
"Does this AWS IAM role belong to the intended organization: arn:aws:iam::123456789012:role/Webserver? (y/n)"
);
stdin.write_all("yes\n".as_bytes()).expect("Failed to write to stdin");

Expand Down
11 changes: 8 additions & 3 deletions src/integration/tests/preprod_sharding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,14 @@ fn preprod_reshard_ceremony() {
.unwrap();

// For each of the enclaves...
for enclave_name in
["ump", "evm-parser", "notarizer", "signer", "tls-fetcher", "deploy-test"]
{
for enclave_name in [
"ump",
"evm-parser",
"notarizer",
"signer",
"tls-fetcher",
"deploy-test",
] {
// Decrypt the old dev share and assert that the resulting quorum key
// has the right public key. Decrypted dev shares are _basically_ master
// seeds. They're just have a "01" prefix because it's the one and only
Expand Down
134 changes: 111 additions & 23 deletions src/qos_client/src/cli/services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -921,7 +921,7 @@ where
// Check the namespace name
{
let prompt = format!(
"Is this the correct namespace name: {}? (yes/no)",
"Is this the correct namespace name: {}? (y/n)",
manifest.namespace.name
);
if !prompter.prompt_is_yes(&prompt) {
Expand All @@ -932,7 +932,7 @@ where
// Check the namespace nonce
{
let prompt = format!(
"Is this the correct namespace nonce: {}? (yes/no)",
"Is this the correct namespace nonce: {}? (y/n)",
manifest.namespace.nonce
);
if !prompter.prompt_is_yes(&prompt) {
Expand All @@ -943,7 +943,7 @@ where
// Check pivot restart policy
{
let prompt = format!(
"Is this the correct pivot restart policy: {:?}? (yes/no)",
"Is this the correct pivot restart policy: {:?}? (y/n)",
manifest.pivot.restart
);
if !prompter.prompt_is_yes(&prompt) {
Expand All @@ -954,7 +954,7 @@ where
// Check pivot arguments
{
let prompt = format!(
"Are these the correct pivot args:\n{:?}?\n(yes/no)",
"Are these the correct pivot args:\n{:?}?\n(y/n)",
manifest.pivot.args
);
if !prompter.prompt_is_yes(&prompt) {
Expand Down Expand Up @@ -1352,7 +1352,7 @@ where
// Check the namespace name
{
let prompt = format!(
"Is this the correct namespace name: {}? (yes/no)",
"Is this the correct namespace name: {}? (y/n)",
manifest_envelope.manifest.namespace.name
);
if !prompter.prompt_is_yes(&prompt) {
Expand All @@ -1363,7 +1363,7 @@ where
// Check the namespace nonce
{
let prompt = format!(
"Is this the correct namespace nonce: {}? (yes/no)",
"Is this the correct namespace nonce: {}? (y/n)",
manifest_envelope.manifest.namespace.nonce
);
if !prompter.prompt_is_yes(&prompt) {
Expand All @@ -1374,7 +1374,7 @@ where
// Check that the IAM role is correct
{
let prompt = format!(
"Does this AWS IAM role belong to the intended organization: {pcr3_preimage}? (yes/no)"
"Does this AWS IAM role belong to the intended organization: {pcr3_preimage}? (y/n)"
);
if !prompter.prompt_is_yes(&prompt) {
return false;
Expand All @@ -1393,7 +1393,7 @@ where
let approvers = approvers.join("\n");

let prompt = format!(
"The following manifest set members approved:\n{approvers}\nIs this ok? (yes/no)"
"The following manifest set members approved:\n{approvers}\nIs this ok? (y/n)"
);

if !prompter.prompt_is_yes(&prompt) {
Expand Down Expand Up @@ -1738,9 +1738,8 @@ pub(crate) fn shamir_reconstruct(
})
.collect::<Result<Vec<Vec<u8>>, Error>>()?;

let secret = Zeroizing::new(
qos_crypto::shamir::shares_reconstruct(shares).unwrap(),
);
let secret =
Zeroizing::new(qos_crypto::shamir::shares_reconstruct(shares).unwrap());

write_with_msg(output_path.as_ref(), &secret, "Reconstructed secret");

Expand Down Expand Up @@ -2129,7 +2128,26 @@ where
}

fn prompt_is_yes(&mut self, question: &str) -> bool {
self.prompt(question) == "yes"
// Fixed amount of attempts to avoid a "while true" loop
let mut attempts = 3;
// First prompt is the question. Subsequent prompts (if any) are the reminder to answer either yes or no.
let mut prompt = question;

while attempts > 0 {
let answer = self.prompt(prompt);

if ["yes", "Yes", "YES", "Y", "y"].contains(&answer.as_ref()) {
return true;
}

if ["no", "No", "NO", "N", "n"].contains(&answer.as_ref()) {
return false;
}

attempts -= 1;
prompt = "Please answer with either \"yes\" (y) or \"no\" (n)";
}
false
}
}

Expand Down Expand Up @@ -2557,7 +2575,7 @@ mod tests {
let Setup { manifest, .. } = setup();

let mut vec_out: Vec<u8> = vec![];
let vec_in = "ye\n".as_bytes();
let vec_in = "No\n".as_bytes();

let mut prompter =
Prompter { reader: vec_in, writer: &mut vec_out };
Expand All @@ -2568,7 +2586,10 @@ mod tests {
));

let output = String::from_utf8(vec_out).unwrap();
assert_eq!(&output, "Is this the correct namespace name: test-namespace? (yes/no)\n");
assert_eq!(
&output,
"Is this the correct namespace name: test-namespace? (y/n)\n"
);
}

#[test]
Expand All @@ -2591,7 +2612,7 @@ mod tests {

assert_eq!(
output[1],
"Is this the correct namespace nonce: 2? (yes/no)"
"Is this the correct namespace nonce: 2? (y/n)"
);
}

Expand All @@ -2615,7 +2636,7 @@ mod tests {

assert_eq!(
output[2],
"Is this the correct pivot restart policy: RestartPolicy::Never? (yes/no)"
"Is this the correct pivot restart policy: RestartPolicy::Never? (y/n)"
);
}

Expand All @@ -2639,7 +2660,7 @@ mod tests {

assert_eq!(output[3], "Are these the correct pivot args:");
assert_eq!(output[4], "[\"--option1\", \"argument\"]?");
assert_eq!(output[5], "(yes/no)");
assert_eq!(output[5], "(y/n)");
}
}

Expand Down Expand Up @@ -2690,7 +2711,7 @@ mod tests {
.get_mut(0)
.unwrap()
.member
.alias = "yoloswag420blazeit".to_string();
.alias = "not-a-member".to_string();

let member = share_set.members[0].clone();
assert!(!proxy_re_encrypt_share_programmatic_verifications(
Expand Down Expand Up @@ -2755,6 +2776,41 @@ mod tests {
));
}

#[test]
fn accepts_with_some_typos_from_operator() {
let Setup { manifest_envelope, .. } = setup();

let mut vec_out: Vec<u8> = vec![];
// Try all the accepted yes variants: y, yes, Yes, and YES!
let vec_in = "y\nyes\nyea\ntes\nYes\nYES\n".as_bytes();

let mut prompter =
Prompter { reader: vec_in, writer: &mut vec_out };

assert!(proxy_re_encrypt_share_human_verifications(
&manifest_envelope,
"pr3",
&mut prompter
));

let output = String::from_utf8(vec_out).unwrap();
let output: Vec<_> = output.lines().collect();
assert_eq!(
output,
vec![
"Is this the correct namespace name: test-namespace? (y/n)",
"Is this the correct namespace nonce: 2? (y/n)",
"Does this AWS IAM role belong to the intended organization: pr3? (y/n)",
"Please answer with either \"yes\" (y) or \"no\" (n)",
"Please answer with either \"yes\" (y) or \"no\" (n)",
"The following manifest set members approved:",
"\talias: 0",
"\talias: 1",
"Is this ok? (y/n)",
]
);
}

#[test]
fn exits_early_bad_namespace_name() {
let Setup { manifest_envelope, .. } = setup();
Expand All @@ -2772,7 +2828,10 @@ mod tests {
));

let output = String::from_utf8(vec_out).unwrap();
assert_eq!(&output, "Is this the correct namespace name: test-namespace? (yes/no)\n");
assert_eq!(
&output,
"Is this the correct namespace name: test-namespace? (y/n)\n"
);
}

#[test]
Expand All @@ -2795,7 +2854,7 @@ mod tests {
let output: Vec<_> = output.lines().collect();
assert_eq!(
output.last().unwrap(),
&"Is this the correct namespace nonce: 2? (yes/no)"
&"Is this the correct namespace nonce: 2? (y/n)"
);
}

Expand All @@ -2819,7 +2878,7 @@ mod tests {
let output: Vec<_> = output.lines().collect();
assert_eq!(
output.last().unwrap(),
&"Does this AWS IAM role belong to the intended organization: pr3? (yes/no)"
&"Does this AWS IAM role belong to the intended organization: pr3? (y/n)"
);
}

Expand All @@ -2828,7 +2887,7 @@ mod tests {
let Setup { manifest_envelope, .. } = setup();

let mut vec_out: Vec<u8> = vec![];
let vec_in = "yes\nyes\nyes\ny".as_bytes();
let vec_in = "yes\nyes\nyes\nno".as_bytes();

let mut prompter =
Prompter { reader: vec_in, writer: &mut vec_out };
Expand All @@ -2848,8 +2907,37 @@ mod tests {
);
assert_eq!(output[4], "\talias: 0");
assert_eq!(output[5], "\talias: 1");
assert_eq!(output[6], "Is this ok? (yes/no)");
assert_eq!(output[6], "Is this ok? (y/n)");
assert_eq!(output.len(), 7);
}

#[test]
fn exits_after_three_hesitations() {
let Setup { manifest_envelope, .. } = setup();

let mut vec_out: Vec<u8> = vec![];
let vec_in = "maybe\ndunno\nunsure\n".as_bytes();

let mut prompter =
Prompter { reader: vec_in, writer: &mut vec_out };

assert!(!proxy_re_encrypt_share_human_verifications(
&manifest_envelope,
"pr3",
&mut prompter
));

let output = String::from_utf8(vec_out).unwrap();
let output: Vec<_> = output.lines().collect();

assert_eq!(
output,
vec![
"Is this the correct namespace name: test-namespace? (y/n)",
"Please answer with either \"yes\" (y) or \"no\" (n)",
"Please answer with either \"yes\" (y) or \"no\" (n)",
]
);
}
}
}
Loading