async def test_authenticated_handshake_no_longer_supported( backend, server_factory, alice): async with server_factory(backend.handle_client) as server: stream = await server.connection_factory() transport = await Transport.init_for_client(stream, "127.0.0.1") challenge_req = await transport.recv() challenge = unpackb(challenge_req)["challenge"] answer = alice.signing_key.sign(challenge) answer_req = { "handshake": "answer", "client_api_version": ApiVersion(1, 3), "type": "AUTHENTICATED", "organization_id": str(alice.organization_id), "device_id": str(alice.device_id), "rvk": alice.root_verify_key.encode(), "answer": answer, } await transport.send(packb(answer_req)) result_req = await transport.recv() assert unpackb(result_req) == { "handshake": "result", "result": "bad_protocol", "help": "{'type': ['Unsupported value: AUTHENTICATED']}", }
async def test_authenticated_handshake_bad_versions( backend, server_factory, alice, mock_api_versions ): ch = APIV1_AuthenticatedClientHandshake( alice.organization_id, alice.device_id, alice.signing_key, alice.root_verify_key ) async with server_factory(backend.handle_client) as server: stream = server.connection_factory() transport = await Transport.init_for_client(stream, server.addr.hostname) challenge_req = await transport.recv() answer_req = ch.process_challenge_req(challenge_req) # Alter answer answer_dict = unpackb(answer_req) answer_dict["client_api_version"] = ApiVersion(3, 22) answer_req = packb(answer_dict) await transport.send(answer_req) result_req = await transport.recv() with pytest.raises(InvalidMessageError) as context: ch.process_result_req(result_req) assert "bad_protocol" in str(context.value) assert "{1.22}" in str(context.value)
async def test_handshake_incompatible_version(backend, server_factory): async with server_factory(backend.handle_client) as server: stream = server.connection_factory() transport = await Transport.init_for_client(stream, server.addr.hostname) incompatible_version = ApiVersion(API_VERSION.version + 1, 0) await transport.recv() # Get challenge req = { "handshake": "answer", "type": "anonymous", "client_api_version": incompatible_version, "organization_id": OrganizationID("Org"), "token": "whatever", } await transport.send(packb(req)) result_req = await transport.recv() assert unpackb(result_req) == { "handshake": "result", "result": "bad_protocol", "help": "No overlap between client API versions {3.0} and backend API versions {" + str(API_VERSION) + ", 1.3}", }
async def _handle_client_loop(self, transport, client_ctx): raw_req = None while True: # raw_req can be already defined if we received a new request # while processing a command raw_req = raw_req or await transport.recv() req = unpackb(raw_req) if get_log_level() <= LOG_LEVEL_DEBUG: client_ctx.logger.debug("Request", req=_filter_binary_fields(req)) try: cmd = req.get("cmd", "<missing>") if not isinstance(cmd, str): raise KeyError() if isinstance(client_ctx, AdministrationClientContext): cmd_func = self.administration_cmds[cmd] elif isinstance(client_ctx, LoggedClientContext): cmd_func = self.logged_cmds[cmd] else: cmd_func = self.anonymous_cmds[cmd] except KeyError: rep = { "status": "unknown_command", "reason": "Unknown command" } else: try: rep = await cmd_func(client_ctx, req) except InvalidMessageError as exc: rep = { "status": "bad_message", "errors": exc.errors, "reason": "Invalid message.", } except ProtocolError as exc: rep = {"status": "bad_message", "reason": str(exc)} except CancelledByNewRequest as exc: # Long command handling such as message_get can be cancelled # when the peer send a new request raw_req = exc.new_raw_req continue if get_log_level() <= LOG_LEVEL_DEBUG: client_ctx.logger.debug("Response", rep=_filter_binary_fields(req)) else: client_ctx.logger.info("Request", cmd=cmd, status=rep["status"]) raw_rep = packb(rep) await transport.send(raw_rep) raw_req = None
async def test_bad_cmd(alice_backend_sock): await alice_backend_sock.send(packb({"cmd": "dummy"})) rep = await alice_backend_sock.recv() assert unpackb(rep) == { "status": "unknown_command", "reason": "Unknown command" }
async def check_allowed_cmds(backend_sock, cmds): for cmd in cmds: if cmd == "events_listen": # Must pass wait option otherwise backend will hang forever await backend_sock.send(packb({"cmd": cmd, "wait": False})) else: await backend_sock.send(packb({"cmd": cmd})) rep = await backend_sock.recv() assert unpackb(rep)["status"] != "unknown_command"
async def test_handshake_invalid_format(backend, server_factory): async with server_factory(backend.handle_client) as server: stream = await server.connection_factory() transport = await Transport.init_for_client(stream, "127.0.0.1") await transport.recv() # Get challenge req = {"handshake": "dummy", "client_api_version": API_VERSION} await transport.send(packb(req)) result_req = await transport.recv() assert unpackb(result_req) == { "handshake": "result", "result": "bad_protocol", "help": "{'handshake': ['Invalid value, should be `answer`']}", }
def test_build_bad_outcomes(alice, method, expected_result): sh = ServerHandshake() sh.build_challenge_req() answer = { "handshake": "answer", "type": HandshakeType.AUTHENTICATED.value, "client_api_version": API_V2_VERSION, "organization_id": alice.organization_id, "device_id": alice.device_id, "rvk": alice.root_verify_key.encode(), "answer": alice.signing_key.sign(sh.challenge), } sh.process_answer_req(packb(answer)) req = getattr(sh, method)() assert unpackb(req) == {"handshake": "result", "result": expected_result, "help": ANY}
async def _handle_client_websocket_loop(self, transport: Transport, client_ctx) -> NoReturn: # Retrieve the allowed commands according to api version and auth type api_cmds = self.apis[client_ctx.TYPE] raw_req = None while True: # raw_req can be already defined if we received a new request # while processing a command raw_req = raw_req or await transport.recv() rep: dict req = unpackb(raw_req) try: cmd = req.get("cmd", "<missing>") if not isinstance(cmd, str): raise KeyError() cmd_func = api_cmds[cmd] except KeyError: rep = { "status": "unknown_command", "reason": "Unknown command" } else: try: rep = await cmd_func(client_ctx, req) except InvalidMessageError as exc: rep = { "status": "bad_message", "errors": exc.errors, "reason": "Invalid message.", } except ProtocolError as exc: rep = {"status": "bad_message", "reason": str(exc)} except CancelledByNewRequest as exc: # Long command handling such as message_get can be cancelled # when the peer send a new request raw_req = exc.new_raw_req continue client_ctx.logger.info("Request", cmd=cmd, status=rep["status"]) raw_rep = packb(rep) await transport.send(raw_rep) raw_req = None
async def test_anonymous_handshake_invalid_format(backend, server_factory): async with server_factory(backend.handle_client) as server: stream = server.connection_factory() transport = await Transport.init_for_client(stream, server.addr.hostname) await transport.recv() # Get challenge req = { "handshake": "foo", "type": "anonymous", "client_api_version": ApiVersion(1, 1), "organization_id": "zob", } await transport.send(packb(req)) result_req = await transport.recv() assert unpackb(result_req) == { "handshake": "result", "result": "bad_protocol", "help": "{'handshake': ['Invalid value, should be `answer`']}", }
async def test_administration_handshake_no_longer_supported( backend, server_factory): async with server_factory(backend.handle_client) as server: stream = await server.connection_factory() transport = await Transport.init_for_client(stream, "127.0.0.1") await transport.recv() answer_req = { "handshake": "answer", "client_api_version": ApiVersion(1, 3), "type": "ADMINISTRATION", "token": backend.config.administration_token, } await transport.send(packb(answer_req)) result_req = await transport.recv() assert unpackb(result_req) == { "handshake": "result", "result": "bad_protocol", "help": "{'type': ['Unsupported value: ADMINISTRATION']}", }
async def test_handshake_incompatible_version(backend, server_factory): async with server_factory(backend.handle_client) as server: stream = await server.connection_factory() transport = await Transport.init_for_client(stream, "127.0.0.1") incompatible_version = ApiVersion(API_VERSION.version + 1, 0) await transport.recv() # Get challenge req = { "handshake": "answer", "type": "anonymous", "client_api_version": incompatible_version, "organization_id": "Org", "token": "whatever", } await transport.send(packb(req)) result_req = await transport.recv() assert unpackb(result_req) == { "handshake": "result", "result": "bad_protocol", "help": f"No overlap between client API versions {{{incompatible_version}}} and backend API versions {{{', '.join(map(str, ServerHandshake.SUPPORTED_API_VERSIONS))}}}", }
async def test_connection(alice_backend_sock): await alice_backend_sock.send(packb({"cmd": "ping", "ping": "42"})) rep = await alice_backend_sock.recv() assert unpackb(rep) == {"status": "ok", "pong": "42"}