Example #1
0
async def test_handshake_unknown_device(running_backend, alice, mallory):
    with pytest.raises(BackendConnectionRefused) as exc:
        async with apiv1_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"
Example #2
0
async def test_backend_closed_cmds(running_backend, alice):
    async with apiv1_backend_authenticated_cmds_factory(
            alice.organization_addr, alice.device_id,
            alice.signing_key) as cmds:
        pass
    with pytest.raises(trio.ClosedResourceError):
        await cmds.ping()
Example #3
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 apiv1_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
Example #4
0
async def test_backend_switch_offline(running_backend, alice):
    async with apiv1_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()
Example #5
0
async def test_authenticated_cmds_has_right_methods(running_backend, alice):
    async with apiv1_backend_authenticated_cmds_factory(
            alice.organization_addr, alice.device_id,
            alice.signing_key) as cmds:
        for method_name in APIV1_AUTHENTICATED_CMDS:
            assert hasattr(cmds, method_name)
        for method_name in ALL_CMDS - APIV1_AUTHENTICATED_CMDS:
            assert not hasattr(cmds, method_name)
Example #6
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 apiv1_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"
Example #7
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 apiv1_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"
Example #8
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 apiv1_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"
Example #9
0
async def apiv1_alice_backend_cmds(running_backend, alice):
    async with apiv1_backend_authenticated_cmds_factory(
        alice.organization_addr, alice.device_id, alice.signing_key
    ) as cmds:
        yield cmds
Example #10
0
async def invite_and_create_user(
    device: LocalDevice,
    user_id: UserID,
    token: str,
    is_admin: bool,
    keepalive: Optional[int] = None,
) -> DeviceID:
    """
    Raises:
        InviteClaimError
        InviteClaimBackendOfflineError
        InviteClaimValidationError
        InviteClaimPackingError
        InviteClaimCryptoError
        InviteClaimInvalidTokenError
    """
    try:
        async with apiv1_backend_authenticated_cmds_factory(
                device.organization_addr,
                device.device_id,
                device.signing_key,
                keepalive=keepalive) as cmds:
            try:
                rep = await cmds.user_invite(user_id)
                if rep["status"] == "timeout":
                    raise InviteClaimTimeoutError()
                elif rep["status"] != "ok":
                    raise InviteClaimError(f"Cannot invite user: {rep}")

                try:
                    claim = APIV1_UserClaimContent.decrypt_and_load_for(
                        rep["encrypted_claim"],
                        recipient_privkey=device.private_key)

                except DataError as exc:
                    raise InviteClaimCryptoError(
                        f"Cannot decrypt user claim info: {exc}") from exc

                if claim.token != token:
                    raise InviteClaimInvalidTokenError(
                        f"Invalid claim token provided by peer: `{claim.token}`"
                        f" (was expecting `{token}`)")

                device_id = claim.device_id
                now = pendulum.now()
                try:

                    user_certificate = UserCertificateContent(
                        author=device.device_id,
                        timestamp=now,
                        user_id=device_id.user_id,
                        public_key=claim.public_key,
                        profile=UserProfile.ADMIN
                        if is_admin else UserProfile.STANDARD,
                    ).dump_and_sign(device.signing_key)
                    device_certificate = DeviceCertificateContent(
                        author=device.device_id,
                        timestamp=now,
                        device_id=device_id,
                        verify_key=claim.verify_key,
                    ).dump_and_sign(device.signing_key)

                except DataError as exc:
                    raise InviteClaimError(
                        f"Cannot generate user&first device certificates: {exc}"
                    ) from exc

            except:
                # Cancel the invitation to prevent the claiming peer from
                # waiting us until timeout
                deadline = trio.current_time() + CANCEL_INVITATION_MAX_WAIT
                with trio.CancelScope(shield=True, deadline=deadline):
                    try:
                        await cmds.user_cancel_invitation(user_id)
                    except BackendConnectionError:
                        pass
                raise

            rep = await cmds.user_create(user_certificate, device_certificate)
            if rep["status"] != "ok":
                raise InviteClaimError(f"Cannot create user: {rep}")

    except BackendNotAvailable as exc:
        raise InviteClaimBackendOfflineError(str(exc)) from exc

    except BackendConnectionError as exc:
        raise InviteClaimError(f"Cannot create user: {exc}") from exc

    return device_id
Example #11
0
async def invite_and_create_device(device: LocalDevice,
                                   new_device_name: DeviceName,
                                   token: str,
                                   keepalive: Optional[int] = None) -> None:
    """
    Raises:
        InviteClaimError
        InviteClaimBackendOfflineError
        InviteClaimValidationError
        InviteClaimPackingError
        InviteClaimCryptoError
        InviteClaimInvalidTokenError
    """
    try:
        async with apiv1_backend_authenticated_cmds_factory(
                device.organization_addr,
                device.device_id,
                device.signing_key,
                keepalive=keepalive) as cmds:
            try:

                rep = await cmds.device_invite(new_device_name)
                if rep["status"] != "ok":
                    raise InviteClaimError(f"Cannot invite device: {rep}")

                try:
                    claim = APIV1_DeviceClaimContent.decrypt_and_load_for(
                        rep["encrypted_claim"],
                        recipient_privkey=device.private_key)

                except DataError as exc:
                    raise InviteClaimCryptoError(
                        f"Cannot decrypt device claim info: {exc}") from exc

                if claim.token != token:
                    raise InviteClaimInvalidTokenError(
                        f"Invalid claim token provided by peer: `{claim.token}`"
                        f" (was expecting `{token}`)")

                try:
                    now = pendulum.now()
                    device_certificate = DeviceCertificateContent(
                        author=device.device_id,
                        timestamp=now,
                        device_id=claim.device_id,
                        verify_key=claim.verify_key,
                    ).dump_and_sign(device.signing_key)

                except DataError as exc:
                    raise InviteClaimError(
                        f"Cannot generate device certificate: {exc}") from exc

                try:
                    encrypted_answer = APIV1_DeviceClaimAnswerContent(
                        private_key=device.private_key,
                        user_manifest_id=device.user_manifest_id,
                        user_manifest_key=device.user_manifest_key,
                    ).dump_and_encrypt_for(
                        recipient_pubkey=claim.answer_public_key)

                except DataError as exc:
                    raise InviteClaimError(
                        f"Cannot generate user claim answer message: {exc}"
                    ) from exc

            except:
                # Cancel the invitation to prevent the claiming peer from
                # waiting us until timeout.
                deadline = trio.current_time() + CANCEL_INVITATION_MAX_WAIT
                with trio.CancelScope(shield=True, deadline=deadline):
                    try:
                        await cmds.device_cancel_invitation(new_device_name)
                    except BackendConnectionError:
                        pass
                raise

            rep = await cmds.device_create(device_certificate,
                                           encrypted_answer)
            if rep["status"] != "ok":
                raise InviteClaimError(f"Cannot create device: {rep}")

    except BackendNotAvailable as exc:
        raise InviteClaimBackendOfflineError(str(exc)) from exc

    except BackendConnectionError as exc:
        raise InviteClaimError(f"Cannot create device: {exc}") from exc
Example #12
0
 def _cmds_factory(keepalive):
     return apiv1_backend_authenticated_cmds_factory(
         alice.organization_addr, alice.device_id, alice.signing_key, keepalive=keepalive
     )
Example #13
0
async def test_ping(running_backend, alice):
    async with apiv1_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 !"}
Example #14
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[
        APIV1_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[APIV1_HandshakeType.AUTHENTICATED][
        "events_listen"] = _mocked_api_events_listen

    events_listen_rep = None
    async with apiv1_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": "pinged",
                "ping": "foo"
            })

    assert events_listen_rep == {
        "status": "ok",
        "event": "pinged",
        "ping": "foo"
    }
Example #15
0
async def test_organization_expired(running_backend, alice, expiredorg):
    with pytest.raises(BackendConnectionRefused) as exc:
        async with apiv1_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"