예제 #1
0
 def _to_http_redirection_url(client_ctx, invitation):
     return BackendInvitationAddr.build(
         backend_addr=self._config.backend_addr,
         organization_id=client_ctx.organization_id,
         invitation_type=invitation.TYPE,
         token=invitation.token,
     ).to_http_redirection_url()
예제 #2
0
async def _invite_user(
    config: CoreConfig, device: LocalDevice, email: str, send_email: bool
) -> None:
    async with spinner("Creating user invitation"):
        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:
            rep = await cmds.invite_new(
                type=InvitationType.USER, claimer_email=email, send_email=send_email
            )
            if rep["status"] != "ok":
                raise RuntimeError(f"Backend refused to create user invitation: {rep}")
            if send_email and "email_sent" in rep:
                if rep["email_sent"] != InvitationEmailSentStatus.SUCCESS:
                    click.secho("Email could not be sent", fg="red")

    action_addr = BackendInvitationAddr.build(
        backend_addr=device.organization_addr.get_backend_addr(),
        organization_id=device.organization_id,
        invitation_type=InvitationType.USER,
        token=rep["token"],
    )
    action_addr_display = click.style(action_addr.to_url(), fg="yellow")
    click.echo(f"url: {action_addr_display}")
예제 #3
0
    def _on_list_success(self, job):
        assert job.is_finished()
        assert job.status == "ok"

        total, users, invitations = job.ret
        # Securing if page go to far
        if total == 0 and self._page > 1:
            self._page -= 1
            self.reset()
        self._flush_users_list()

        current_user = self.core.device.user_id
        for invitation in reversed(invitations):
            addr = BackendInvitationAddr.build(
                backend_addr=self.core.device.organization_addr,
                organization_id=self.core.device.organization_id,
                invitation_type=InvitationType.USER,
                token=invitation["token"],
            )
            self.add_user_invitation(invitation["claimer_email"], addr)
        for user_info in users:
            self.add_user(user_info=user_info,
                          is_current_user=current_user == user_info.user_id)
        self.spinner.hide()
        self.pagination(total=total, users_on_page=len(users))
        self.button_users_filter.setEnabled(True)
        self.line_edit_search.setEnabled(True)
예제 #4
0
async def test_claim_device_backend_desync(aqtbot, running_backend, backend,
                                           autoclose_dialog, alice, gui,
                                           monkeypatch):

    # Client is 5 minutes ahead
    def _timestamp(self):
        return pendulum_now().add(minutes=5)

    monkeypatch.setattr("parsec.api.protocol.BaseClientHandshake.timestamp",
                        _timestamp)

    invitation_addr = BackendInvitationAddr.build(
        backend_addr=alice.organization_addr.get_backend_addr(),
        organization_id=alice.organization_id,
        invitation_type=InvitationType.DEVICE,
        token=InvitationToken.new(),
    )

    gui.add_instance(invitation_addr.to_url())

    def _assert_dialogs():
        assert len(autoclose_dialog.dialogs) == 1
        assert autoclose_dialog.dialogs == [
            ("Error", translate("TEXT_BACKEND_STATE_DESYNC"))
        ]

    await aqtbot.wait_until(_assert_dialogs)
예제 #5
0
async def test_claim_device_already_deleted(aqtbot, running_backend, backend,
                                            autoclose_dialog, alice, gui):

    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,
    )
    await backend.invite.delete(
        organization_id=alice.organization_id,
        greeter=alice.user_id,
        token=invitation_addr.token,
        on=pendulum_now(),
        reason=InvitationDeletedReason.CANCELLED,
    )

    gui.add_instance(invitation_addr.to_url())

    def _assert_dialogs():
        assert len(autoclose_dialog.dialogs) == 1
        assert autoclose_dialog.dialogs == [
            ("Error", translate("TEXT_INVITATION_ALREADY_USED"))
        ]

    await aqtbot.wait_until(_assert_dialogs)
예제 #6
0
    async def new_device_invitation(
        self, send_email: bool
    ) -> Tuple[BackendInvitationAddr, InvitationEmailSentStatus]:
        """
        Raises:
            BackendConnectionError
        """
        rep = await self._backend_conn.cmds.invite_new(
            type=InvitationType.DEVICE, send_email=send_email)
        if rep["status"] != "ok":
            raise BackendConnectionError(f"Backend error: {rep}")

        if not ("email_sent" in rep):
            email_sent = InvitationEmailSentStatus.SUCCESS
        else:
            email_sent = rep["email_sent"]

        return (
            BackendInvitationAddr.build(
                backend_addr=self.device.organization_addr.get_backend_addr(),
                organization_id=self.device.organization_id,
                invitation_type=InvitationType.DEVICE,
                token=rep["token"],
            ),
            email_sent,
        )
예제 #7
0
    async def new_user_invitation(
        self, email: str, send_email: bool
    ) -> Tuple[BackendInvitationAddr, InvitationEmailSentStatus]:
        """
        Raises:
            BackendConnectionError
        """
        rep = await self._backend_conn.cmds.invite_new(
            type=InvitationType.USER,
            claimer_email=email,
            send_email=send_email)
        if rep["status"] == "already_member":
            raise BackendInvitationOnExistingMember(
                "An user already exist with this email")
        elif rep["status"] != "ok":
            raise BackendConnectionError(f"Backend error: {rep}")

        if not ("email_sent" in rep):
            email_sent = InvitationEmailSentStatus.SUCCESS
        else:
            email_sent = rep["email_sent"]

        return (
            BackendInvitationAddr.build(
                backend_addr=self.device.organization_addr.get_backend_addr(),
                organization_id=self.device.organization_id,
                invitation_type=InvitationType.USER,
                token=rep["token"],
            ),
            email_sent,
        )
예제 #8
0
def _parse_invitation_token_or_url(raw: str) -> Union[BackendInvitationAddr, InvitationToken]:
    try:
        return InvitationToken.from_hex(raw)
    except ValueError:
        try:
            return BackendInvitationAddr.from_url(raw)
        except ValueError:
            raise ValueError("Must be an invitation URL or Token")
예제 #9
0
async def device_invitation_addr(backend, bob):
    invitation = await backend.invite.new_for_device(
        organization_id=bob.organization_id, greeter_user_id=bob.user_id)
    return BackendInvitationAddr.build(
        backend_addr=bob.organization_addr,
        organization_id=bob.organization_id,
        invitation_type=InvitationType.DEVICE,
        token=invitation.token,
    )
예제 #10
0
        async def bootstrap(self):
            author = logged_gui.test_get_central_widget().core.device
            claimer_email = self.requested_email

            # Create new invitation

            invitation = await backend.invite.new_for_user(
                organization_id=author.organization_id,
                greeter_user_id=author.user_id,
                claimer_email=claimer_email,
            )
            invitation_addr = BackendInvitationAddr.build(
                backend_addr=author.organization_addr.get_backend_addr(),
                organization_id=author.organization_id,
                invitation_type=InvitationType.USER,
                token=invitation.token,
            )

            # Switch to users page

            users_widget = await logged_gui.test_switch_to_users_widget()

            assert users_widget.layout_users.count() == 4

            invitation_widget = users_widget.layout_users.itemAt(0).widget()
            assert isinstance(invitation_widget, UserInvitationButton)
            assert invitation_widget.email == claimer_email

            # Click on the invitation button

            aqtbot.mouse_click(invitation_widget.button_greet,
                               QtCore.Qt.LeftButton)

            greet_user_widget = await catch_greet_user_widget()
            assert isinstance(greet_user_widget, GreetUserWidget)

            greet_user_information_widget = await catch_greet_user_widget()
            assert isinstance(greet_user_information_widget,
                              GreetUserInstructionsWidget)

            def _greet_user_displayed():
                assert greet_user_widget.dialog.isVisible()
                assert greet_user_widget.isVisible()
                assert greet_user_widget.dialog.label_title.text(
                ) == "Greet a new user"
                assert greet_user_information_widget.isVisible()

            await aqtbot.wait_until(_greet_user_displayed)

            self.author = author
            self.users_widget = users_widget
            self.invitation_widget = invitation_widget
            self.invitation_addr = invitation_addr
            self.greet_user_widget = greet_user_widget
            self.greet_user_information_widget = greet_user_information_widget

            self.assert_initial_state()  # Sanity check
예제 #11
0
async def invitation_addr(backend, alice):
    invitation = await backend.invite.new_for_device(
        organization_id=alice.organization_id, greeter_user_id=alice.user_id)
    return BackendInvitationAddr.build(
        backend_addr=alice.organization_addr.get_backend_addr(),
        organization_id=alice.organization_id,
        invitation_type=InvitationType.DEVICE,
        token=invitation.token,
    )
예제 #12
0
async def test_user_claim_but_active_users_limit_reached(
        backend, running_backend, alice):
    # Organization has reached active user limit
    await backend.organization.update(alice.organization_id,
                                      active_users_limit=1)

    # Invitation is still ok...
    invitation = await backend.invite.new_for_user(
        organization_id=alice.organization_id,
        greeter_user_id=alice.user_id,
        claimer_email="*****@*****.**",
    )
    invitation_addr = BackendInvitationAddr.build(
        backend_addr=alice.organization_addr.get_backend_addr(),
        organization_id=alice.organization_id,
        invitation_type=InvitationType.USER,
        token=invitation.token,
    )

    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 _run_claimer():
        async with backend_invited_cmds_factory(addr=invitation_addr) as cmds:
            initial_ctx = await claimer_retrieve_info(cmds)
            in_progress_ctx = await initial_ctx.do_wait_peer()
            in_progress_ctx = await in_progress_ctx.do_signify_trust()
            in_progress_ctx = await in_progress_ctx.do_wait_peer_trust()

            await in_progress_ctx.do_claim_user(requested_device_label=None,
                                                requested_human_handle=None)

    async with real_clock_timeout():
        async with trio.open_nursery() as nursery:
            nursery.start_soon(_run_claimer)
            await _run_greeter()
            # Claimer is not notified that the greeter has failed so we
            # must explicitly cancel it
            nursery.cancel_scope.cancel()
예제 #13
0
async def test_handshake_unknown_organization(running_backend, coolorg):
    invitation_addr = BackendInvitationAddr.build(
        backend_addr=running_backend.addr,
        organization_id=coolorg.organization_id,
        invitation_type=InvitationType.DEVICE,
        token=uuid4(),
    )
    with pytest.raises(BackendInvitationNotFound) as exc:
        async with backend_invited_cmds_factory(invitation_addr) as cmds:
            await cmds.ping()
    assert str(exc.value) == "Invalid handshake: Invitation not found"
예제 #14
0
async def user_invitation_addr(backend, bob):
    invitation = await backend.invite.new_for_user(
        organization_id=bob.organization_id,
        greeter_user_id=bob.user_id,
        claimer_email="*****@*****.**",
    )
    return BackendInvitationAddr.build(
        backend_addr=bob.organization_addr.get_backend_addr(),
        organization_id=bob.organization_id,
        invitation_type=InvitationType.USER,
        token=invitation.token,
    )
예제 #15
0
async def test_proxy_with_websocket(monkeypatch, connection_type, proxy_type):
    signing_key = SigningKey.generate()
    device_id = DeviceID("zack@pc1")
    proxy_events = []

    def _event_hook(event):
        proxy_events.append(event)

    async with trio.open_nursery() as nursery:
        target_port = await start_port_watchdog(nursery, _event_hook)
        proxy_port = await start_proxy_for_websocket(nursery, target_port,
                                                     _event_hook)

        if proxy_type == "http_proxy":
            proxy_url = f"http://127.0.0.1:{proxy_port}"
            monkeypatch.setitem(os.environ, "http_proxy", proxy_url)
        else:
            assert proxy_type == "http_proxy_pac"
            pac_server_port = await start_pac_server(
                nursery=nursery,
                pac_rule=f"PROXY 127.0.0.1:{proxy_port}",
                event_hook=_event_hook)
            pac_server_url = f"http://127.0.0.1:{pac_server_port}"
            monkeypatch.setitem(os.environ, "http_proxy_pac", pac_server_url)
            # HTTP_PROXY_PAC has priority over HTTP_PROXY
            monkeypatch.setitem(os.environ, "http_proxy",
                                f"http://127.0.0.1:{target_port}")

        async with real_clock_timeout():
            with pytest.raises(BackendNotAvailable):
                if connection_type == "authenticated":
                    await connect_as_authenticated(
                        addr=BackendOrganizationAddr.from_url(
                            f"parsec://127.0.0.1:{target_port}/CoolOrg?no_ssl=true&rvk=7NFDS4VQLP3XPCMTSEN34ZOXKGGIMTY2W2JI2SPIHB2P3M6K4YWAssss"
                        ),
                        device_id=device_id,
                        signing_key=signing_key,
                    )

                else:
                    assert connection_type == "invited"
                    await connect_as_invited(addr=BackendInvitationAddr.from_url(
                        f"parsec://127.0.0.1:{target_port}/CoolOrg?no_ssl=true&action=claim_user&token=3a50b191122b480ebb113b10216ef343"
                    ))

        assert proxy_events == [
            *(["PAC file retreived from server"]
              if proxy_type == "http_proxy_pac" else []),
            "Connected to proxy",
            "Reaching target through proxy",
        ]

        nursery.cancel_scope.cancel()
예제 #16
0
def test_build_addrs():
    backend_addr = BackendAddr.from_url(BackendAddrTestbed.url)
    assert backend_addr.hostname == "parsec.cloud.com"
    assert backend_addr.port == 443
    assert backend_addr.use_ssl is True

    organization_id = OrganizationID("MyOrg")
    root_verify_key = SigningKey.generate().verify_key

    organization_addr = BackendOrganizationAddr.build(
        backend_addr=backend_addr, organization_id=organization_id, root_verify_key=root_verify_key
    )
    assert organization_addr.organization_id == organization_id
    assert organization_addr.root_verify_key == root_verify_key

    organization_bootstrap_addr = BackendOrganizationBootstrapAddr.build(
        backend_addr=backend_addr,
        organization_id=organization_id,
        token="a0000000000000000000000000000001",
    )
    assert organization_bootstrap_addr.token == "a0000000000000000000000000000001"
    assert organization_bootstrap_addr.organization_id == organization_id

    organization_bootstrap_addr2 = BackendOrganizationBootstrapAddr.build(
        backend_addr=backend_addr, organization_id=organization_id, token=None
    )
    assert organization_bootstrap_addr2.organization_id == organization_id
    assert organization_bootstrap_addr2.token == ""

    organization_file_link_addr = BackendOrganizationFileLinkAddr.build(
        organization_addr=organization_addr,
        workspace_id=EntryID.from_hex("2d4ded12-7406-4608-833b-7f57f01156e2"),
        encrypted_path=b"<encrypted_payload>",
    )
    assert organization_file_link_addr.workspace_id == EntryID.from_hex(
        "2d4ded12-7406-4608-833b-7f57f01156e2"
    )
    assert organization_file_link_addr.encrypted_path == b"<encrypted_payload>"

    invitation_addr = BackendInvitationAddr.build(
        backend_addr=backend_addr,
        organization_id=organization_id,
        invitation_type=InvitationType.USER,
        token=InvitationToken.from_hex("a0000000000000000000000000000001"),
    )
    assert invitation_addr.organization_id == organization_id
    assert invitation_addr.token == InvitationToken.from_hex("a0000000000000000000000000000001")
    assert invitation_addr.invitation_type == InvitationType.USER
예제 #17
0
 async def new_device_invitation(self, send_email: bool) -> BackendInvitationAddr:
     """
     Raises:
         BackendConnectionError
     """
     rep = await self._backend_conn.cmds.invite_new(
         type=InvitationType.DEVICE, send_email=send_email
     )
     if rep["status"] != "ok":
         raise BackendConnectionError(f"Backend error: {rep}")
     return BackendInvitationAddr.build(
         backend_addr=self.device.organization_addr,
         organization_id=self.device.organization_id,
         invitation_type=InvitationType.DEVICE,
         token=rep["token"],
     )
예제 #18
0
async def test_handshake_organization_expired(running_backend, expiredorg,
                                              expiredorgalice):
    invitation = await running_backend.backend.invite.new_for_device(
        organization_id=expiredorgalice.organization_id,
        greeter_user_id=expiredorgalice.user_id)
    invitation_addr = BackendInvitationAddr.build(
        backend_addr=running_backend.addr,
        organization_id=expiredorgalice.organization_id,
        invitation_type=InvitationType.DEVICE,
        token=invitation.token,
    )

    with pytest.raises(BackendConnectionRefused) as exc:
        async with backend_invited_cmds_factory(invitation_addr) as cmds:
            await cmds.ping()
    assert str(exc.value) == "Trial organization has expired"
예제 #19
0
async def test_invited_cmd_keepalive(mock_clock, monkeypatch, backend,
                                     running_backend, backend_addr, alice):
    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,
        organization_id=alice.organization_id,
        invitation_type=InvitationType.DEVICE,
        token=invitation.token,
    )

    def _cmds_factory(keepalive):
        return backend_invited_cmds_factory(invitation_addr,
                                            keepalive=keepalive)

    await _test_keepalive(mock_clock, monkeypatch, _cmds_factory)
예제 #20
0
 async def new_user_invitation(self, email: str, send_email: bool) -> BackendInvitationAddr:
     """
     Raises:
         BackendConnectionError
     """
     rep = await self._backend_conn.cmds.invite_new(
         type=InvitationType.USER, claimer_email=email, send_email=send_email
     )
     if rep["status"] == "already_member":
         raise InviteAlreadyMemberError()
     elif rep["status"] != "ok":
         raise BackendConnectionError(f"Backend error: {rep}")
     return BackendInvitationAddr.build(
         backend_addr=self.device.organization_addr,
         organization_id=self.device.organization_id,
         invitation_type=InvitationType.USER,
         token=rep["token"],
     )
예제 #21
0
        async def bootstrap(self):
            author = logged_gui.test_get_central_widget().core.device
            # Create new invitation

            invitation = await backend.invite.new_for_device(
                organization_id=author.organization_id,
                greeter_user_id=author.user_id)
            invitation_addr = BackendInvitationAddr.build(
                backend_addr=author.organization_addr.get_backend_addr(),
                organization_id=author.organization_id,
                invitation_type=InvitationType.DEVICE,
                token=invitation.token,
            )

            # Switch to devices page
            devices_widget = await logged_gui.test_switch_to_devices_widget()
            assert devices_widget.layout_devices.count() == 2

            # Click on the invitation button
            aqtbot.mouse_click(devices_widget.button_add_device,
                               QtCore.Qt.LeftButton)
            greet_device_widget = await catch_greet_device_widget()
            assert isinstance(greet_device_widget, GreetDeviceWidget)

            greet_device_information_widget = await catch_greet_device_widget()
            assert isinstance(greet_device_information_widget,
                              GreetDeviceInstructionsWidget)

            def _greet_device_displayed():
                assert greet_device_widget.dialog.isVisible()
                assert greet_device_widget.isVisible()
                assert greet_device_widget.dialog.label_title.text(
                ) == "Greet a new device"
                assert greet_device_information_widget.isVisible()

            await aqtbot.wait_until(_greet_device_displayed)

            self.author = author
            self.devices_widget = devices_widget
            self.invitation_addr = invitation_addr
            self.greet_device_widget = greet_device_widget
            self.greet_device_information_widget = greet_device_information_widget

            self.assert_initial_state()  # Sanity check
예제 #22
0
async def test_claim_user_unknown_invitation(aqtbot, running_backend, backend,
                                             autoclose_dialog, alice, gui):

    invitation_addr = BackendInvitationAddr.build(
        backend_addr=alice.organization_addr.get_backend_addr(),
        organization_id=alice.organization_id,
        invitation_type=InvitationType.USER,
        token=InvitationToken.new(),
    )

    gui.add_instance(invitation_addr.to_url())

    def _assert_dialogs():
        assert len(autoclose_dialog.dialogs) == 1
        assert autoclose_dialog.dialogs == [
            ("Error", translate("TEXT_CLAIM_USER_INVITATION_NOT_FOUND"))
        ]

    await aqtbot.wait_until(_assert_dialogs)
예제 #23
0
async def _invite_device(config, device):
    async with spinner("Creating device invitation"):
        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:
            rep = await cmds.invite_new(type=InvitationType.DEVICE)
            if rep["status"] != "ok":
                raise RuntimeError(f"Backend refused to create device invitation: {rep}")

    action_addr = BackendInvitationAddr.build(
        backend_addr=device.organization_addr,
        organization_id=device.organization_id,
        invitation_type=InvitationType.DEVICE,
        token=rep["token"],
    )
    action_addr_display = click.style(action_addr.to_url(), fg="yellow")
    click.echo(f"url: {action_addr_display}")
예제 #24
0
async def test_claim_device_unknown_invitation(aqtbot, running_backend,
                                               backend, autoclose_dialog,
                                               alice, gui):

    invitation_addr = BackendInvitationAddr.build(
        backend_addr=alice.organization_addr,
        organization_id=alice.organization_id,
        invitation_type=InvitationType.DEVICE,
        token=uuid4(),
    )

    await aqtbot.run(gui.add_instance, invitation_addr.to_url())

    def _assert_dialogs():
        assert len(autoclose_dialog.dialogs) == 1
        assert autoclose_dialog.dialogs == [
            ("Error", translate("TEXT_CLAIM_DEVICE_INVITATION_NOT_FOUND"))
        ]

    await aqtbot.wait_until(_assert_dialogs)
예제 #25
0
async def test_claim_device_offline_backend(aqtbot, running_backend, backend,
                                            autoclose_dialog, alice, gui):

    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,
    )
    with running_backend.offline():
        gui.add_instance(invitation_addr.to_url())

        def _assert_dialogs():
            assert len(autoclose_dialog.dialogs) == 1
            assert autoclose_dialog.dialogs == [
                ("Error", translate("TEXT_INVITATION_BACKEND_NOT_AVAILABLE"))
            ]

        await aqtbot.wait_until(_assert_dialogs)
예제 #26
0
        async def bootstrap(self):
            claimer_email = self.requested_human_handle.email

            # Create new invitation

            invitation = await backend.invite.new_for_user(
                organization_id=self.author.organization_id,
                greeter_user_id=self.author.user_id,
                claimer_email=claimer_email,
            )
            invitation_addr = BackendInvitationAddr.build(
                backend_addr=self.author.organization_addr.get_backend_addr(),
                organization_id=self.author.organization_id,
                invitation_type=InvitationType.USER,
                token=invitation.token,
            )

            # Switch to users claim page

            gui.add_instance(invitation_addr.to_url())

            cu_w = await catch_claim_user_widget()
            assert isinstance(cu_w, ClaimUserWidget)
            cui_w = await catch_claim_user_widget()
            assert isinstance(cui_w, ClaimUserInstructionsWidget)

            def _register_user_displayed():
                tab = gui.test_get_tab()
                assert tab and tab.isVisible()
                assert cu_w.isVisible()
                assert cu_w.dialog.label_title.text() == "Register a user"
                assert cui_w.isVisible()

            await aqtbot.wait_until(_register_user_displayed)

            self.invitation_addr = invitation_addr
            self.claim_user_widget = cu_w
            self.claim_user_instructions_widget = cui_w

            self.assert_initial_state()  # Sanity check
예제 #27
0
        async def bootstrap(self):
            # Create new invitation

            invitation = await backend.invite.new_for_device(
                organization_id=self.author.organization_id,
                greeter_user_id=self.author.user_id)
            invitation_addr = BackendInvitationAddr.build(
                backend_addr=self.author.organization_addr.get_backend_addr(),
                organization_id=self.author.organization_id,
                invitation_type=InvitationType.DEVICE,
                token=invitation.token,
            )

            # Switch to device claim page

            gui.add_instance(invitation_addr.to_url())

            cd_w = await catch_claim_device_widget()
            assert isinstance(cd_w, ClaimDeviceWidget)
            cdi_w = await catch_claim_device_widget()
            assert isinstance(cdi_w, ClaimDeviceInstructionsWidget)

            def _register_device_displayed():
                tab = gui.test_get_tab()
                assert tab and tab.isVisible()
                assert cd_w.isVisible()
                assert cd_w.dialog.label_title.text() == "Register a device"
                assert cdi_w.isVisible()

            await aqtbot.wait_until(_register_device_displayed)

            self.invitation_addr = invitation_addr
            self.claim_device_widget = cd_w
            self.claim_device_instructions_widget = cdi_w

            self.assert_initial_state()  # Sanity check
예제 #28
0
async def test_good_device_claim(backend, running_backend, alice, bob,
                                 alice_backend_cmds, user_fs_factory,
                                 with_labels):
    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,
    )

    if with_labels:
        requested_device_label = DeviceLabel("Foo's label")
        granted_device_label = DeviceLabel("Bar's label")
    else:
        requested_device_label = None
        granted_device_label = None
    new_device = None

    # Simulate out-of-bounds canal
    oob_send, oob_recv = trio.open_memory_channel(0)

    async def _run_claimer():
        async with backend_invited_cmds_factory(addr=invitation_addr) as cmds:
            initial_ctx = await claimer_retrieve_info(cmds)
            assert isinstance(initial_ctx, DeviceClaimInitialCtx)
            assert initial_ctx.greeter_user_id == alice.user_id
            assert initial_ctx.greeter_human_handle == alice.human_handle

            in_progress_ctx = await initial_ctx.do_wait_peer()

            choices = in_progress_ctx.generate_greeter_sas_choices(size=4)
            assert len(choices) == 4
            assert in_progress_ctx.greeter_sas in choices

            greeter_sas = await oob_recv.receive()
            assert greeter_sas == in_progress_ctx.greeter_sas

            in_progress_ctx = await in_progress_ctx.do_signify_trust()
            await oob_send.send(in_progress_ctx.claimer_sas)

            in_progress_ctx = await in_progress_ctx.do_wait_peer_trust()

            nonlocal new_device
            new_device = await in_progress_ctx.do_claim_device(
                requested_device_label=requested_device_label)
            assert isinstance(new_device, LocalDevice)

    async def _run_greeter():
        initial_ctx = DeviceGreetInitialCtx(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

        await in_progress_ctx.do_create_new_device(
            author=alice, device_label=granted_device_label)

    async with real_clock_timeout():
        async with trio.open_nursery() as nursery:
            nursery.start_soon(_run_claimer)
            nursery.start_soon(_run_greeter)

    assert new_device is not None
    assert new_device.user_id == alice.user_id
    assert new_device.device_name != alice.device_name
    assert new_device.device_label == granted_device_label
    assert new_device.human_handle == alice.human_handle
    assert new_device.private_key == alice.private_key
    assert new_device.signing_key != alice.signing_key
    assert new_device.profile == alice.profile
    assert new_device.user_manifest_id == alice.user_manifest_id
    assert new_device.user_manifest_key == alice.user_manifest_key
    # Make sure greeter&claimer data are not mixed
    assert new_device.local_symkey != alice.local_symkey

    # Now invitation should have been deleted
    rep = await alice_backend_cmds.invite_list()
    assert rep == {"status": "ok", "invitations": []}

    # Verify user&device data in backend
    _, device = await backend.user.get_user_with_device(
        new_device.organization_id, new_device.device_id)
    assert device.device_label == granted_device_label
    if with_labels:
        assert device.device_certificate != device.redacted_device_certificate
    else:
        assert device.device_certificate == device.redacted_device_certificate

    # Test the behavior of this new device
    async with user_fs_factory(bob) as bobfs:
        async with user_fs_factory(alice) as alicefs:
            async with user_fs_factory(new_device) as newfs:
                # New device should start with a speculative user manifest
                um = newfs.get_user_manifest()
                assert um.is_placeholder
                assert um.speculative

                # Old device modify user manifest
                await alicefs.workspace_create(EntryName("wa"))
                await alicefs.sync()

                # New sharing from other user
                wb_id = await bobfs.workspace_create(EntryName("wb"))
                await bobfs.workspace_share(wb_id, alice.user_id,
                                            WorkspaceRole.CONTRIBUTOR)

                # Test new device get access to both new workspaces
                await newfs.process_last_messages()
                await newfs.sync()
                newfs_um = newfs.get_user_manifest()

                # Make sure new and old device have the same view on data
                await alicefs.sync()
                alicefs_um = alicefs.get_user_manifest()
                assert newfs_um == alicefs_um
예제 #29
0
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)
예제 #30
0
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"