async def _greet_invitation(config, device, token): async with backend_authenticated_cmds_factory( addr=device.organization_addr, device_id=device.device_id, signing_key=device.signing_key, keepalive=config.backend_connection_keepalive, ) as cmds: async with spinner("Retrieving invitation info"): rep = await cmds.invite_list() if rep["status"] != "ok": raise RuntimeError(f"Backend error: {rep}") for invitation in rep["invitations"]: if invitation["token"] == token: break else: raise RuntimeError(f"Invitation not found") if invitation["type"] == InvitationType.USER: initial_ctx = UserGreetInitialCtx(cmds=cmds, token=token) do_greet = partial(_do_greet_user, device, initial_ctx) else: assert invitation["type"] == InvitationType.DEVICE initial_ctx = DeviceGreetInitialCtx(cmds=cmds, token=token) do_greet = partial(_do_greet_device, device, initial_ctx) while True: try: greet_done = await do_greet() if greet_done: break except InviteError as exc: click.secho(str(exc), fg="red") click.secho("Restarting the invitation process", fg="red")
async def _run_greeter(): initial_ctx = UserGreetInitialCtx(cmds=alice_backend_cmds, token=invitation_addr.token) in_progress_ctx = await initial_ctx.do_wait_peer() await oob_send.send(in_progress_ctx.greeter_sas) in_progress_ctx = await in_progress_ctx.do_wait_peer_trust() choices = in_progress_ctx.generate_claimer_sas_choices(size=5) assert len(choices) == 5 assert in_progress_ctx.claimer_sas in choices claimer_sas = await oob_recv.receive() assert claimer_sas == in_progress_ctx.claimer_sas in_progress_ctx = await in_progress_ctx.do_signify_trust() in_progress_ctx = await in_progress_ctx.do_get_claim_requests() assert in_progress_ctx.requested_device_label == requested_device_label assert in_progress_ctx.requested_human_handle == requested_human_handle await in_progress_ctx.do_create_new_user( author=alice, device_label=granted_device_label, human_handle=granted_human_handle, profile=granted_profile, )
async def _reset_greeter(self): async with trio.open_nursery() as nursery: greeter_initial_ctx = UserGreetInitialCtx( cmds=alice2_backend_cmds, token=self.invitation_addr.token) nursery.start_soon(greeter_initial_ctx.do_wait_peer) yield nursery.cancel_scope.cancel()
async def _init_ctx_create(cmds, token): initial_ctx = UserGreetInitialCtx(cmds=cmds, token=token) in_progress_ctx = await initial_ctx.do_wait_peer() in_progress_ctx = await in_progress_ctx.do_wait_peer_trust() in_progress_ctx = await in_progress_ctx.do_signify_trust() in_progress_ctx = await in_progress_ctx.do_get_claim_requests() return in_progress_ctx
async def start_greeting_user(self, token: UUID) -> UserGreetInProgress1Ctx: """ Raises: BackendConnectionError InviteError """ initial_ctx = UserGreetInitialCtx(cmds=self._backend_conn.cmds, token=token) return await initial_ctx.do_wait_peer()
async def invite_task(): initial_ctx = UserGreetInitialCtx(cmds=alice_cmds, token=invitation_addr.token) in_progress_ctx = await initial_ctx.do_wait_peer() in_progress_ctx = await in_progress_ctx.do_wait_peer_trust() in_progress_ctx = await in_progress_ctx.do_signify_trust() in_progress_ctx = await in_progress_ctx.do_get_claim_requests() await in_progress_ctx.do_create_new_user( author=alice_device, human_handle=in_progress_ctx.requested_human_handle, device_label=in_progress_ctx.requested_device_label, profile=UserProfile.STANDARD, )
async def _run_greeter(): async with backend_authenticated_cmds_factory( alice.organization_addr, alice.device_id, alice.signing_key) as alice_backend_cmds: initial_ctx = UserGreetInitialCtx(cmds=alice_backend_cmds, token=invitation_addr.token) in_progress_ctx = await initial_ctx.do_wait_peer() in_progress_ctx = await in_progress_ctx.do_wait_peer_trust() in_progress_ctx = await in_progress_ctx.do_signify_trust() in_progress_ctx = await in_progress_ctx.do_get_claim_requests() # ...this is where the limit should be enforced with pytest.raises(InviteActiveUsersLimitReachedError): await in_progress_ctx.do_create_new_user( author=alice, device_label=in_progress_ctx.requested_device_label, human_handle=in_progress_ctx.requested_human_handle, profile=UserProfile.STANDARD, )
async def step_2_start_greeter(self): cui_w = self.claim_user_instructions_widget self.greeter_initial_ctx = UserGreetInitialCtx( cmds=self.cmds, token=self.invitation_addr.token) self.greeter_in_progress_ctx = await self.greeter_initial_ctx.do_wait_peer( ) cuce_w = await catch_claim_user_widget() assert isinstance(cuce_w, ClaimUserCodeExchangeWidget) def _greeter_sas_code_choices_displayed(): assert not cui_w.isVisible() assert cuce_w.isVisible() assert cuce_w.widget_greeter_code.isVisible() assert cuce_w.code_input_widget.isVisible() assert cuce_w.code_input_widget.code_layout.count() == 4 # TODO: better check on codes await aqtbot.wait_until(_greeter_sas_code_choices_displayed) self.claim_user_code_exchange_widget = cuce_w return "step_3_exchange_greeter_sas"
async def test_claimer_handle_command_failure(backend, running_backend, alice, alice_backend_cmds, monkeypatch, fail_on_step): invitation = await backend.invite.new_for_device( organization_id=alice.organization_id, greeter_user_id=alice.user_id) invitation_addr = BackendInvitationAddr.build( backend_addr=alice.organization_addr.get_backend_addr(), organization_id=alice.organization_id, invitation_type=InvitationType.DEVICE, token=invitation.token, ) async def _cancel_invitation(): await backend.invite.delete( organization_id=alice.organization_id, greeter=alice.user_id, token=invitation_addr.token, on=pendulum_now(), reason=InvitationDeletedReason.CANCELLED, ) async with backend_invited_cmds_factory( addr=invitation_addr) as claimer_cmds: greeter_initial_ctx = UserGreetInitialCtx(cmds=alice_backend_cmds, token=invitation_addr.token) claimer_initial_ctx = await claimer_retrieve_info(claimer_cmds) claimer_in_progress_ctx = None greeter_in_progress_ctx = None async def _do_claimer(): nonlocal claimer_in_progress_ctx if fail_on_step == "wait_peer": return claimer_in_progress_ctx = await claimer_initial_ctx.do_wait_peer() if fail_on_step == "signify_trust": return claimer_in_progress_ctx = await claimer_in_progress_ctx.do_signify_trust( ) if fail_on_step == "wait_peer_trust": return claimer_in_progress_ctx = await claimer_in_progress_ctx.do_wait_peer_trust( ) async def _do_greeter(): nonlocal greeter_in_progress_ctx if fail_on_step == "wait_peer": return greeter_in_progress_ctx = await greeter_initial_ctx.do_wait_peer() if fail_on_step == "signify_trust": return greeter_in_progress_ctx = await greeter_in_progress_ctx.do_wait_peer_trust( ) if fail_on_step == "wait_peer_trust": return greeter_in_progress_ctx = await greeter_in_progress_ctx.do_signify_trust( ) async with real_clock_timeout(): async with trio.open_nursery() as nursery: nursery.start_soon(_do_claimer) nursery.start_soon(_do_greeter) deleted_event = trio.Event() async def _send_event(*args, **kwargs): if BackendEvent.INVITE_STATUS_CHANGED in args and ( kwargs.get("status") == InvitationStatus.DELETED): deleted_event.set() await trio.sleep(0) backend.invite._send_event = _send_event monkeypatch.setattr("parsec.backend.postgresql.invite.send_signal", _send_event) async with real_clock_timeout(): await _cancel_invitation() await deleted_event.wait() with pytest.raises(BackendInvitationAlreadyUsed) as exc_info: if fail_on_step == "wait_peer": await claimer_initial_ctx.do_wait_peer() elif fail_on_step == "signify_trust": await claimer_in_progress_ctx.do_signify_trust() elif fail_on_step == "wait_peer_trust": await claimer_in_progress_ctx.do_wait_peer_trust() elif fail_on_step == "claim_device": await claimer_in_progress_ctx.do_claim_device( requested_device_label=DeviceLabel("TheSecretDevice")) else: raise AssertionError(f"Unknown step {fail_on_step}") assert str(exc_info.value ) == "Invalid handshake: Invitation already deleted"
async def test_claimer_handle_cancel_event(backend, running_backend, alice, alice_backend_cmds, fail_on_step): invitation = await backend.invite.new_for_device( organization_id=alice.organization_id, greeter_user_id=alice.user_id) invitation_addr = BackendInvitationAddr.build( backend_addr=alice.organization_addr.get_backend_addr(), organization_id=alice.organization_id, invitation_type=InvitationType.DEVICE, token=invitation.token, ) async def _cancel_invitation(): await backend.invite.delete( organization_id=alice.organization_id, greeter=alice.user_id, token=invitation_addr.token, on=pendulum_now(), reason=InvitationDeletedReason.CANCELLED, ) async with backend_invited_cmds_factory( addr=invitation_addr) as claimer_cmds: greeter_initial_ctx = UserGreetInitialCtx(cmds=alice_backend_cmds, token=invitation_addr.token) claimer_initial_ctx = await claimer_retrieve_info(claimer_cmds) claimer_in_progress_ctx = None greeter_in_progress_ctx = None async def _do_claimer(): nonlocal claimer_in_progress_ctx if fail_on_step == "wait_peer": return claimer_in_progress_ctx = await claimer_initial_ctx.do_wait_peer() if fail_on_step == "signify_trust": return claimer_in_progress_ctx = await claimer_in_progress_ctx.do_signify_trust( ) if fail_on_step == "wait_peer_trust": return claimer_in_progress_ctx = await claimer_in_progress_ctx.do_wait_peer_trust( ) async def _do_greeter(): nonlocal greeter_in_progress_ctx if fail_on_step == "wait_peer": return greeter_in_progress_ctx = await greeter_initial_ctx.do_wait_peer() if fail_on_step == "signify_trust": return greeter_in_progress_ctx = await greeter_in_progress_ctx.do_wait_peer_trust( ) if fail_on_step == "wait_peer_trust": return greeter_in_progress_ctx = await greeter_in_progress_ctx.do_signify_trust( ) async with real_clock_timeout(): async with trio.open_nursery() as nursery: nursery.start_soon(_do_claimer) nursery.start_soon(_do_greeter) async with real_clock_timeout(): async with trio.open_nursery() as nursery: async def _do_claimer_wait_peer(): with pytest.raises( BackendInvitationAlreadyUsed) as exc_info: await claimer_initial_ctx.do_wait_peer() assert str( exc_info.value ) == "Invalid handshake: Invitation already deleted" async def _do_claimer_signify_trust(): with pytest.raises( BackendInvitationAlreadyUsed) as exc_info: await claimer_in_progress_ctx.do_signify_trust() assert str( exc_info.value ) == "Invalid handshake: Invitation already deleted" async def _do_claimer_wait_peer_trust(): with pytest.raises( BackendInvitationAlreadyUsed) as exc_info: await claimer_in_progress_ctx.do_wait_peer_trust() assert str( exc_info.value ) == "Invalid handshake: Invitation already deleted" async def _do_claimer_claim_device(): with pytest.raises( BackendInvitationAlreadyUsed) as exc_info: await claimer_in_progress_ctx.do_claim_device( requested_device_label=DeviceLabel( "TheSecretDevice")) assert str( exc_info.value ) == "Invalid handshake: Invitation already deleted" steps = { "wait_peer": _do_claimer_wait_peer, "signify_trust": _do_claimer_signify_trust, "wait_peer_trust": _do_claimer_wait_peer_trust, "claim_device": _do_claimer_claim_device, } _do_claimer = steps[fail_on_step] with backend.event_bus.listen() as spy: nursery.start_soon(_do_claimer) # Be sure that _do_claimer got valid invitations before cancelation await spy.wait_with_timeout( BackendEvent.INVITE_CONDUIT_UPDATED) await _cancel_invitation() await spy.wait_with_timeout( BackendEvent.INVITE_STATUS_CHANGED)
async def test_claimer_handle_reset(backend, running_backend, alice, alice_backend_cmds): invitation = await backend.invite.new_for_device( organization_id=alice.organization_id, greeter_user_id=alice.user_id) invitation_addr = BackendInvitationAddr.build( backend_addr=alice.organization_addr.get_backend_addr(), organization_id=alice.organization_id, invitation_type=InvitationType.DEVICE, token=invitation.token, ) async with backend_invited_cmds_factory( addr=invitation_addr) as claimer_cmds: greeter_initial_ctx = UserGreetInitialCtx(cmds=alice_backend_cmds, token=invitation_addr.token) claimer_initial_ctx = await claimer_retrieve_info(claimer_cmds) claimer_in_progress_ctx = None greeter_in_progress_ctx = None # Step 1 async with real_clock_timeout(): async with trio.open_nursery() as nursery: async def _do_claimer(): nonlocal claimer_in_progress_ctx claimer_in_progress_ctx = await claimer_initial_ctx.do_wait_peer( ) async def _do_greeter(): nonlocal greeter_in_progress_ctx greeter_in_progress_ctx = await greeter_initial_ctx.do_wait_peer( ) nursery.start_soon(_do_claimer) nursery.start_soon(_do_greeter) # Claimer restart the conduit while greeter try to do step 2 async with real_clock_timeout(): async with trio.open_nursery() as nursery: async def _do_claimer(): nonlocal claimer_in_progress_ctx claimer_in_progress_ctx = await claimer_initial_ctx.do_wait_peer( ) nursery.start_soon(_do_claimer) with pytest.raises(InvitePeerResetError): await greeter_in_progress_ctx.do_wait_peer_trust() # Greeter redo step 1 greeter_in_progress_ctx = await greeter_initial_ctx.do_wait_peer( ) # Now do the other way around: greeter restart conduit while claimer try step 2 async with real_clock_timeout(): async with trio.open_nursery() as nursery: async def _do_greeter(): nonlocal greeter_in_progress_ctx greeter_in_progress_ctx = await greeter_initial_ctx.do_wait_peer( ) nursery.start_soon(_do_greeter) with pytest.raises(InvitePeerResetError): await claimer_in_progress_ctx.do_signify_trust() # Claimer redo step 1 claimer_in_progress_ctx = await claimer_initial_ctx.do_wait_peer( )