예제 #1
0
async def test_handshake_unknown_device(running_backend, alice, mallory):
    with pytest.raises(BackendConnectionRefused) as exc:
        async with backend_authenticated_cmds_factory(
            alice.organization_addr, mallory.device_id, mallory.signing_key
        ) as cmds:
            await cmds.ping()
    assert str(exc.value) == "Invalid handshake information"
예제 #2
0
async def test_backend_disconnect_during_handshake(tcp_stream_spy, alice, backend_addr):
    client_answered = False

    async def poorly_serve_client(stream):
        nonlocal client_answered

        transport = await Transport.init_for_server(stream)
        handshake = ServerHandshake()
        await transport.send(handshake.build_challenge_req())
        await transport.recv()
        # Close connection during handshake
        await stream.aclose()

        client_answered = True

    async with trio.open_service_nursery() as nursery:

        async def connection_factory(*args, **kwargs):
            client_stream, server_stream = trio.testing.memory_stream_pair()
            nursery.start_soon(poorly_serve_client, server_stream)
            return client_stream

        with tcp_stream_spy.install_hook(backend_addr, connection_factory):
            with pytest.raises(BackendNotAvailable):
                async with backend_authenticated_cmds_factory(
                    alice.organization_addr, alice.device_id, alice.signing_key
                ) as cmds:
                    await cmds.ping()

        nursery.cancel_scope.cancel()

    assert client_answered
예제 #3
0
async def test_backend_closed_cmds(running_backend, alice):
    async with backend_authenticated_cmds_factory(
        alice.organization_addr, alice.device_id, alice.signing_key
    ) as cmds:
        pass
    with pytest.raises(trio.ClosedResourceError):
        await cmds.ping()
예제 #4
0
async def _list_invitations(config, device):
    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_list()
        if rep["status"] != "ok":
            raise RuntimeError(
                f"Backend error while listing invitations: {rep}")
        display_statuses = {
            InvitationStatus.READY: click.style("ready", fg="green"),
            InvitationStatus.IDLE: click.style("idle", fg="yellow"),
            InvitationStatus.DELETED: click.style("deleted", fg="red"),
        }
        for invitation in rep["invitations"]:
            display_status = display_statuses[invitation["status"]]
            display_token = invitation["token"].hex
            if invitation["type"] == InvitationType.USER:
                display_type = f"user (email={invitation['claimer_email']})"
            else:  # Device
                display_type = "device"
            click.echo(f"{display_token}\t{display_status}\t{display_type}")
        if not rep["invitations"]:
            click.echo("No invitations.")
예제 #5
0
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("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")
예제 #6
0
async def test_organization_expired(running_backend, alice, expiredorg):
    with pytest.raises(BackendConnectionRefused) as exc:
        async with backend_authenticated_cmds_factory(
            expiredorg.addr, alice.device_id, alice.signing_key
        ) as cmds:
            await cmds.ping()
    assert str(exc.value) == "Trial organization has expired"
예제 #7
0
async def test_backend_switch_offline(running_backend, alice):
    async with backend_authenticated_cmds_factory(
        alice.organization_addr, alice.device_id, alice.signing_key
    ) as cmds:
        await cmds.ping()
        with running_backend.offline():
            with pytest.raises(BackendNotAvailable):
                await cmds.ping()
예제 #8
0
async def test_authenticated_cmds_has_right_methods(running_backend, alice):
    async with backend_authenticated_cmds_factory(
        alice.organization_addr, alice.device_id, alice.signing_key
    ) as cmds:
        for method_name in AUTHENTICATED_CMDS:
            assert hasattr(cmds, method_name)
        for method_name in ALL_CMDS - AUTHENTICATED_CMDS:
            assert not hasattr(cmds, method_name)
예제 #9
0
async def test_handshake_rvk_mismatch(running_backend, alice, otherorg):
    bad_rvk_org_addr = BackendOrganizationAddr.build(
        backend_addr=alice.organization_addr,
        organization_id=alice.organization_id,
        root_verify_key=otherorg.root_verify_key,
    )
    with pytest.raises(BackendConnectionRefused) as exc:
        async with backend_authenticated_cmds_factory(
            bad_rvk_org_addr, alice.device_id, alice.signing_key
        ) as cmds:
            await cmds.ping()
    assert str(exc.value) == "Root verify key for organization differs between client and server"
예제 #10
0
async def test_handshake_unknown_organization(running_backend, alice):
    unknown_org_addr = BackendOrganizationAddr.build(
        backend_addr=alice.organization_addr,
        organization_id="dummy",
        root_verify_key=alice.organization_addr.root_verify_key,
    )
    with pytest.raises(BackendConnectionRefused) as exc:
        async with backend_authenticated_cmds_factory(
            unknown_org_addr, alice.device_id, alice.signing_key
        ) as cmds:
            await cmds.ping()
    assert str(exc.value) == "Invalid handshake information"
예제 #11
0
 async def _user_fs_factory(device, event_bus=None, initialize_in_v0: bool = False):
     event_bus = event_bus or event_bus_factory()
     async with backend_authenticated_cmds_factory(
         device.organization_addr, device.device_id, device.signing_key
     ) as cmds:
         path = local_storage_path(device)
         rdm = RemoteDevicesManager(cmds, device.root_verify_key)
         async with UserFS.run(
             device, path, cmds, rdm, event_bus, get_pattern_filter()
         ) as user_fs:
             if not initialize_in_v0:
                 await initialize_userfs_storage_v1(user_fs.storage)
             yield user_fs
예제 #12
0
async def _cancel_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:
        rep = await cmds.invite_delete(
            token=token, reason=InvitationDeletedReason.CANCELLED)
        if rep["status"] != "ok":
            raise RuntimeError(
                f"Backend error while cancelling invitation: {rep}")
        click.echo("Invitation deleted.")
예제 #13
0
async def test_handshake_revoked_device(running_backend, alice, bob):
    revoked_user_certificate = RevokedUserCertificateContent(
        author=alice.device_id, timestamp=pendulum.now(), user_id=bob.user_id
    ).dump_and_sign(alice.signing_key)
    await running_backend.backend.user.revoke_user(
        organization_id=alice.organization_id,
        user_id=bob.user_id,
        revoked_user_certificate=revoked_user_certificate,
        revoked_user_certifier=alice.device_id,
    )

    with pytest.raises(BackendConnectionRefused) as exc:
        async with backend_authenticated_cmds_factory(
            bob.organization_addr, bob.device_id, bob.signing_key
        ) as cmds:
            await cmds.ping()
    assert str(exc.value) == "Device has been revoked"
예제 #14
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}")
예제 #15
0
async def initialize_test_organization(
    config_dir: Path,
    backend_address: BackendAddr,
    password: str,
    administration_token: str,
    force: bool,
    additional_users_number: int,
    additional_devices_number: int,
) -> Tuple[LocalDevice, LocalDevice, LocalDevice]:
    configure_logging("WARNING")
    organization_id = OrganizationID("Org")

    # Create organization
    async with apiv1_backend_administration_cmds_factory(
        backend_address, administration_token) as administration_cmds:

        rep = await administration_cmds.organization_create(organization_id)
        assert rep["status"] == "ok"
        bootstrap_token = rep["bootstrap_token"]

        organization_bootstrap_addr = BackendOrganizationBootstrapAddr.build(
            backend_address, organization_id, bootstrap_token)
    # Bootstrap organization and Alice user and create device "laptop" for Alice

    async with apiv1_backend_anonymous_cmds_factory(
            organization_bootstrap_addr) as anonymous_cmds:
        alice_device = await bootstrap_organization(
            cmds=anonymous_cmds,
            human_handle=HumanHandle(label="Alice", email="*****@*****.**"),
            device_label="laptop",
        )
        save_device_with_password(config_dir,
                                  alice_device,
                                  password,
                                  force=force)

    config = load_config(config_dir, debug="DEBUG" in os.environ)
    # Create context manager, alice_client will be needed for the rest of the script
    async with logged_client_factory(config, alice_device) as alice_client:
        async with backend_authenticated_cmds_factory(
                addr=alice_device.organization_addr,
                device_id=alice_device.device_id,
                signing_key=alice_device.signing_key,
        ) as alice_cmds:

            # Create new device "pc" for Alice
            other_alice_device = await _register_new_device(
                cmds=alice_cmds, author=alice_device, device_label="pc")
            save_device_with_password(config_dir,
                                      other_alice_device,
                                      password,
                                      force=force)
            # Add additional random device for alice
            if additional_devices_number > 0:
                print(
                    f"Adding {additional_devices_number} devices in the test group"
                )
                print(" ... please wait ...")
                await _add_random_device(
                    cmds=alice_cmds,
                    config_dir=config_dir,
                    force=force,
                    password=password,
                    device=alice_device,
                    additional_devices_number=additional_devices_number,
                )
                print(" Done ")
            # Invite Bob in organization
            bob_device = await _register_new_user(
                cmds=alice_cmds,
                author=alice_device,
                device_label="laptop",
                human_handle=HumanHandle(email="*****@*****.**", label="Bob"),
                profile=UserProfile.STANDARD,
            )
            save_device_with_password(config_dir,
                                      bob_device,
                                      password,
                                      force=force)
            # Create Alice workspace
            alice_ws_id = await alice_client.user_fs.workspace_create(
                "alice_workspace")
            # Create context manager
            async with logged_client_factory(config, bob_device) as bob_client:
                # Create Bob workspace
                bob_ws_id = await bob_client.user_fs.workspace_create(
                    "bob_workspace")
                # Bob share workspace with Alice
                await bob_client.user_fs.workspace_share(
                    bob_ws_id, alice_device.user_id, WorkspaceRole.MANAGER)
                # Alice share workspace with Bob
                await alice_client.user_fs.workspace_share(
                    alice_ws_id, bob_device.user_id, WorkspaceRole.MANAGER)
                # Add additional random users
                if additional_users_number > 0:
                    print(
                        f"Adding {additional_users_number} users in the test group"
                    )
                    print(" ... please wait ...")
                    await _add_random_users(
                        cmds=alice_cmds,
                        author=alice_device,
                        alice_client=alice_client,
                        bob_client=bob_client,
                        alice_ws_id=alice_ws_id,
                        bob_ws_id=bob_ws_id,
                        additional_users_number=additional_users_number,
                    )
                    print(" Done ")

    # Synchronize every device
    for device in (alice_device, other_alice_device, bob_device):
        async with logged_client_factory(config, device) as client:
            await client.user_fs.process_last_messages()
            await client.user_fs.sync()
    return (alice_device, other_alice_device, bob_device)
예제 #16
0
 def _cmds_factory(keepalive):
     return backend_authenticated_cmds_factory(alice.organization_addr,
                                               alice.device_id,
                                               alice.signing_key,
                                               keepalive=keepalive)
예제 #17
0
async def test_events_listen_wait_has_watchdog(monkeypatch, mock_clock, running_backend, alice):
    # Spy on the transport events to detect the Pings/Pongs
    # (Note we are talking about websocket ping, not our own higher-level ping api)
    transport_events_sender, transport_events_receiver = trio.open_memory_channel(100)
    _vanilla_next_ws_event = Transport._next_ws_event

    async def _mocked_next_ws_event(transport):
        event = await _vanilla_next_ws_event(transport)
        await transport_events_sender.send((transport, event))
        return event

    monkeypatch.setattr(Transport, "_next_ws_event", _mocked_next_ws_event)

    async def next_ping_related_event():
        while True:
            transport, event = await transport_events_receiver.receive()
            if isinstance(event, (Ping, Pong)):
                return (transport, event)

    # Highjack the backend api to control the wait time and the final
    # event that will be returned to the client
    backend_received_cmd = trio.Event()
    backend_client_ctx = None
    vanilla_api_events_listen = running_backend.backend.apis[HandshakeType.AUTHENTICATED][
        "events_listen"
    ]

    async def _mocked_api_events_listen(client_ctx, msg):
        nonlocal backend_client_ctx
        backend_client_ctx = client_ctx
        backend_received_cmd.set()
        return await vanilla_api_events_listen(client_ctx, msg)

    running_backend.backend.apis[HandshakeType.AUTHENTICATED][
        "events_listen"
    ] = _mocked_api_events_listen

    events_listen_rep = None
    async with backend_authenticated_cmds_factory(
        alice.organization_addr, alice.device_id, alice.signing_key, keepalive=2
    ) as cmds:
        mock_clock.rate = 1
        async with trio.open_service_nursery() as nursery:

            async def _cmd():
                nonlocal events_listen_rep
                events_listen_rep = await cmds.events_listen(wait=True)

            nursery.start_soon(_cmd)

            # Wait for the connection to be established with the backend
            with trio.fail_after(1):
                await backend_received_cmd.wait()

            # Now advance time until ping is requested
            await trio.testing.wait_all_tasks_blocked()
            mock_clock.jump(2)
            with trio.fail_after(2):
                backend_transport, event = await next_ping_related_event()
                assert isinstance(event, Ping)
                client_transport, event = await next_ping_related_event()
                assert isinstance(event, Pong)
                assert client_transport is not backend_transport

            # Wait for another ping, just to be sure...
            await trio.testing.wait_all_tasks_blocked()
            mock_clock.jump(2)
            with trio.fail_after(1):
                backend_transport2, event = await next_ping_related_event()
                assert isinstance(event, Ping)
                assert backend_transport is backend_transport2
                client_transport2, event = await next_ping_related_event()
                assert isinstance(event, Pong)
                assert client_transport is client_transport2

            await backend_client_ctx.send_events_channel.send(
                {"event": APIEvent.PINGED, "ping": "foo"}
            )

    assert events_listen_rep == {"status": "ok", "event": APIEvent.PINGED, "ping": "foo"}
예제 #18
0
async def test_ping(running_backend, alice):
    async with backend_authenticated_cmds_factory(
        alice.organization_addr, alice.device_id, alice.signing_key
    ) as cmds:
        rep = await cmds.ping("Hello World !")
        assert rep == {"status": "ok", "pong": "Hello World !"}
예제 #19
0
 async def _remote_devices_manager_factory(device):
     async with backend_authenticated_cmds_factory(
         device.organization_addr, device.device_id, device.signing_key
     ) as cmds:
         yield RemoteDevicesManager(cmds, device.root_verify_key)
예제 #20
0
async def bob_backend_cmds(running_backend, bob):
    async with backend_authenticated_cmds_factory(
        bob.organization_addr, bob.device_id, bob.signing_key
    ) as cmds:
        yield cmds
예제 #21
0
async def alice2_backend_cmds(running_backend, alice2):
    async with backend_authenticated_cmds_factory(
        alice2.organization_addr, alice2.device_id, alice2.signing_key
    ) as cmds:
        yield cmds