Skip to content

Commit

Permalink
Add invitation dance cli test for shared recovery.
Browse files Browse the repository at this point in the history
  • Loading branch information
AureliaDolo committed Jan 14, 2025
1 parent 89de38c commit 38f1758
Show file tree
Hide file tree
Showing 11 changed files with 299 additions and 79 deletions.
26 changes: 17 additions & 9 deletions cli/src/commands/invite/claim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,17 @@ pub async fn main(args: Args) -> anyhow::Result<()> {
match ctx {
ShamirRecoveryClaimMaybeFinalizeCtx::Offline(ctx) => {
let retry = Select::new()
.default(0)
.with_prompt("Unable to join server, do you want to retry ?")
.items(&["yes", "no"])
.interact()?;

if retry == 0 {
// yes
device_ctx = ctx;
continue;
} else {
// no
return Err(anyhow!("Server offline, try again later."));
}
}
Expand Down Expand Up @@ -142,7 +145,7 @@ async fn step0(

let ctx = claimer_retrieve_info(Arc::new(config.into()), addr, None).await?;

handle.stop_with_newline();
handle.stop_with_symbol(GREEN_CHECKMARK);

Ok(ctx)
}
Expand All @@ -151,9 +154,10 @@ async fn step0(
fn shamir_pick_recipient(
ctx: &ShamirRecoveryClaimPickRecipientCtx,
) -> anyhow::Result<ShamirRecoveryClaimInitialCtx> {
let recipients = ctx.yet_to_contact_recipients();
let human_recipients: Vec<_> = recipients.iter().map(|r| r.human_handle.clone()).collect();
let recipients = ctx.recipients_without_a_share();
let human_recipients: Vec<&_> = recipients.iter().map(|r| &r.human_handle).collect();
let selection = Select::new()
.default(0)
.with_prompt("Choose a person to contact now")
.items(&human_recipients)
.interact()?;
Expand Down Expand Up @@ -201,11 +205,14 @@ async fn step1_shamir(
ctx.greeter_human_handle()
);

let mut handle = start_spinner("Waiting the greeter to start the invitation procedure".into());
let mut handle = start_spinner(format!(
"Waiting the greeter {} to start the invitation procedure",
ctx.greeter_human_handle()
));

let ctx = ctx.do_wait_peer().await?;

handle.stop_with_newline();
handle.stop_with_symbol(GREEN_CHECKMARK);

Ok(ctx)
}
Expand Down Expand Up @@ -249,6 +256,7 @@ async fn step2_shamir(
let sas_codes = ctx.generate_greeter_sas_choices(3);

let selected_sas = Select::new()
.default(0)
.items(&sas_codes)
.with_prompt("Select code provided by greeter")
.interact()?;
Expand Down Expand Up @@ -288,7 +296,7 @@ async fn step3_shamir(

let ctx = ctx.do_wait_peer_trust().await?;

handle.stop_with_newline();
handle.stop_with_symbol(GREEN_CHECKMARK);

Ok(ctx)
}
Expand Down Expand Up @@ -346,7 +354,7 @@ async fn step4_shamir(

let ctx = ctx.do_recover_share().await?;

handle.stop_with_newline();
handle.stop_with_symbol(GREEN_CHECKMARK);

Ok(ctx)
}
Expand All @@ -357,11 +365,11 @@ async fn step5_shamir(
) -> anyhow::Result<ShamirRecoveryClaimMaybeFinalizeCtx> {
let device_label = Input::new().with_prompt("Enter device label").interact()?;

let mut handle = start_spinner("Waiting for greeter".into());
let mut handle = start_spinner("Recovering device".into());

let ctx = ctx.recover_device(device_label).await?;

handle.stop_with_newline();
handle.stop_with_symbol(GREEN_CHECKMARK);

Ok(ctx)
}
Expand Down
17 changes: 6 additions & 11 deletions cli/src/commands/invite/greet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,8 @@ pub async fn device_greet(args: Args, client: &StartedClient) -> anyhow::Result<
let ctx = step4_device(ctx).await?;
step5_device(ctx).await
}
InviteListItem::ShamirRecovery {
token,
claimer_user_id,
..
} => {
let ctx = client
.start_shamir_recovery_invitation_greet(token, claimer_user_id)
.await?;
InviteListItem::ShamirRecovery { token, .. } => {
let ctx = client.start_shamir_recovery_invitation_greet(token).await?;

let ctx = step1_shamir(ctx).await?;
let ctx = step2_shamir(ctx).await?;
Expand Down Expand Up @@ -97,7 +91,7 @@ async fn step0(
None => return Err(anyhow::anyhow!("Invitation not found")),
};

handle.stop_with_newline();
handle.stop_with_symbol(GREEN_CHECKMARK);

Ok(invitation)
}
Expand Down Expand Up @@ -132,7 +126,7 @@ async fn step1_shamir(

let ctx = ctx.do_wait_peer().await?;

handle.stop_with_newline();
handle.stop_with_symbol(GREEN_CHECKMARK);

Ok(ctx)
}
Expand Down Expand Up @@ -182,7 +176,7 @@ async fn step2_shamir(

let ctx = ctx.do_wait_peer_trust().await?;

handle.stop_with_newline();
handle.stop_with_symbol(GREEN_CHECKMARK);

Ok(ctx)
}
Expand Down Expand Up @@ -223,6 +217,7 @@ async fn step3_shamir(
) -> anyhow::Result<ShamirRecoveryGreetInProgress3Ctx> {
let sas_codes = ctx.generate_claimer_sas_choices(3);
let selected_sas = Select::new()
.default(0)
.items(&sas_codes)
.with_prompt("Select code provided by claimer")
.interact()?;
Expand Down
7 changes: 6 additions & 1 deletion cli/src/commands/invite/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ crate::build_main_with_client!(main, list_invite);

pub async fn list_invite(_args: Args, client: &StartedClient) -> anyhow::Result<()> {
log::trace!("Listing invitations");
{
let mut spinner = start_spinner("Poll server for new certificates".into());
client.poll_server_for_new_certificates().await?;
spinner.stop_with_symbol(GREEN_CHECKMARK);
}

let mut handle = start_spinner("Listing invitations".into());

Expand All @@ -31,7 +36,7 @@ pub async fn list_invite(_args: Args, client: &StartedClient) -> anyhow::Result<
status,
token,
..
} => (token, status, format!("user (email={claimer_email}")),
} => (token, status, format!("user (email={claimer_email})")),
InviteListItem::Device { status, token, .. } => (token, status, "device".into()),
InviteListItem::ShamirRecovery {
status,
Expand Down
2 changes: 1 addition & 1 deletion cli/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub const GREEN: &str = "\x1B[92m";
pub const RED: &str = "\x1B[91m";
pub const RESET: &str = "\x1B[39m";
pub const YELLOW: &str = "\x1B[33m";
pub const GREEN_CHECKMARK: &str = "\x1B[92m🗸\x1B[39m";
pub const GREEN_CHECKMARK: &str = "\x1B[92m\x1B[39m";
pub const BULLET_CHAR: &str = "•";

pub fn format_devices(
Expand Down
1 change: 1 addition & 0 deletions cli/tests/integration/invitations/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod cancel;
mod device;
mod list;
mod shared_recovery;
mod user;
191 changes: 191 additions & 0 deletions cli/tests/integration/invitations/shared_recovery.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
use std::sync::{Arc, Mutex};

use libparsec::{
authenticated_cmds::v4::invite_new_shamir_recovery, get_default_config_dir, tmp_path,
AuthenticatedCmds, InvitationType, ParsecInvitationAddr, ProxyConfig, TmpPath,
};
use rexpect::{session::PtySession, spawn};

use crate::{
integration_tests::{bootstrap_cli_test, shared_recovery_create},
testenv_utils::{TestOrganization, DEFAULT_DEVICE_PASSWORD},
};

macro_rules! match_sas_code {
($locked:ident, $sas_code:ident) => {
$locked.read_line().unwrap(); //empty line
let first = dbg!($locked.read_line().unwrap());
let second = dbg!($locked.read_line().unwrap());
let third = dbg!($locked.read_line().unwrap());

if $sas_code == first[first.len() - 4..] {
$locked.send_line("").unwrap();
} else if $sas_code == second[second.len() - 4..] {
$locked.send_line("j").unwrap();
} else if $sas_code == third[third.len() - 4..] {
$locked.send_line("jj").unwrap();
} else {
panic!("no corresponding sas code available")
}
};
}

#[rstest::rstest]
#[tokio::test]
async fn invite_shared_recovery_dance(tmp_path: TmpPath) {
let (_, TestOrganization { alice, bob, .. }, _) = bootstrap_cli_test(&tmp_path).await.unwrap();

shared_recovery_create(&alice, &bob, None);
let cmds = AuthenticatedCmds::new(
&get_default_config_dir(),
bob.clone(),
ProxyConfig::new_from_env().unwrap(),
)
.unwrap();

let rep = cmds
.send(invite_new_shamir_recovery::Req {
send_email: false,
claimer_user_id: alice.user_id,
})
.await
.unwrap();

let invitation_addr = match rep {
invite_new_shamir_recovery::InviteNewShamirRecoveryRep::Ok { token, .. } => {
ParsecInvitationAddr::new(
alice.organization_addr.clone(),
alice.organization_id().clone(),
InvitationType::ShamirRecovery,
token,
)
}
rep => {
panic!("Server refused to create user invitation: {rep:?}");
}
};

let token = invitation_addr.token();

// spawn greeter thread
let mut cmd_greeter = assert_cmd::Command::cargo_bin("parsec-cli").unwrap();
cmd_greeter.args([
"invite",
"greet",
"--device",
&bob.device_id.hex(),
&token.hex().to_string(),
]);

let program_greeter = cmd_greeter.get_program().to_str().unwrap().to_string();
let program_greeter = cmd_greeter
.get_args()
.fold(program_greeter, |acc, s| format!("{acc} {s:?}"));

let p_greeter = Arc::new(Mutex::new(
spawn(&dbg!(program_greeter), Some(1000)).unwrap(),
));

// spawn claimer thread

let mut cmd_claimer = assert_cmd::Command::cargo_bin("parsec-cli").unwrap();
cmd_claimer.args(["invite", "claim", invitation_addr.to_url().as_ref()]);

let program_claimer = cmd_claimer.get_program().to_str().unwrap().to_string();
let program_claimer = cmd_claimer
.get_args()
.fold(program_claimer, |acc, s| format!("{acc} {s:?}"));

let p_claimer = Arc::new(Mutex::new(
spawn(&dbg!(program_claimer), Some(10_000)).unwrap(),
));

// retrieve greeter code
let greeter_cloned = p_greeter.clone();
let greeter = tokio::task::spawn(async move {
let mut locked = greeter_cloned.lock().unwrap();

locked.exp_string("Enter password for the device:").unwrap();
locked.send_line(DEFAULT_DEVICE_PASSWORD).unwrap();
locked.exp_string("Waiting for claimer").unwrap();
});
let claimer_cloned = p_claimer.clone();

let claimer = tokio::task::spawn(async move {
let mut locked = claimer_cloned.lock().unwrap();
locked
.exp_string("Choose a person to contact now:")
.unwrap();
// down to choose bob
locked.send_line("j").unwrap();

locked
.exp_string("Select code provided by greeter:")
.unwrap();
});
greeter.await.unwrap();
p_greeter
.lock()
.unwrap()
.exp_string("Code to provide to claimer:")
.unwrap();
let (_, matched) = p_greeter.lock().unwrap().exp_regex("[A-Z0-9]{4}").unwrap();
let sas_code = dbg!(matched[matched.len() - 4..].to_string()); // last 4 chars are the sas code

// code selection

claimer.await.unwrap();
let cloned_claimer = p_claimer.clone();
{
let mut locked = cloned_claimer.lock().unwrap();

match_sas_code!(locked, sas_code);
}

// retrieve claimer code
let greeter_cloned = p_greeter.clone();
let greeter = tokio::task::spawn(async move {
let mut locked = greeter_cloned.lock().unwrap();
locked.exp_string("Waiting for claimer").unwrap();
locked
.exp_string("Select code provided by claimer:")
.unwrap();
});

let sas_code = {
let mut locked = p_claimer.lock().unwrap();

locked.exp_string("Code to provide to greeter:").unwrap();
let (_, matched) = locked.exp_regex("[A-Z0-9]{4}").unwrap();
dbg!(matched[matched.len() - 4..].to_string()) // last 4 chars are the sas code
};
greeter.await.unwrap();

{
let mut locked = p_greeter.lock().unwrap();

match_sas_code!(locked, sas_code);
}
let mut greeter = Arc::<Mutex<PtySession>>::try_unwrap(p_greeter)
.ok()
.unwrap()
.into_inner()
.unwrap();
greeter.exp_eof().unwrap();
greeter.process.exit().unwrap();
drop(greeter);
// device creation
let mut locked = p_claimer.lock().unwrap();
locked.exp_string("Enter device label:").unwrap();
locked.send_line("label").unwrap();
locked.exp_string("Recovering device").unwrap();

locked
.exp_string("Enter password for the new device:")
.unwrap();
locked.send_line(DEFAULT_DEVICE_PASSWORD).unwrap();
locked.exp_string("Confirm password:").unwrap();

locked.send_line(DEFAULT_DEVICE_PASSWORD).unwrap();
locked.exp_eof().unwrap();
}
Loading

0 comments on commit 38f1758

Please sign in to comment.