def test_process_challenge_req_good_api_version( alice, monkeypatch, client_version, backend_version, valid ): # Cast parameters client_version = ApiVersion(*client_version) backend_version = ApiVersion(*backend_version) ch = AuthenticatedClientHandshake( alice.organization_id, alice.device_id, alice.signing_key, alice.root_verify_key ) req = { "handshake": "challenge", "challenge": b"1234567890", "supported_api_versions": [backend_version], } monkeypatch.setattr(ch, "SUPPORTED_API_VERSIONS", [client_version]) if not valid: # Invalid versioning with pytest.raises(HandshakeAPIVersionError) as context: ch.process_challenge_req(packb(req)) assert context.value.client_versions == [client_version] assert context.value.backend_versions == [backend_version] else: # Valid versioning ch.process_challenge_req(packb(req)) assert ch.challenge_data["supported_api_versions"] == [backend_version] assert ch.backend_api_version == backend_version assert ch.client_api_version == client_version
def test_process_challenge_req_good_api_version(alice, monkeypatch, client_version, backend_version, valid): # Cast parameters client_version = ApiVersion(*client_version) backend_version = ApiVersion(*backend_version) ch = APIV1_AnonymousClientHandshake(alice.organization_id, alice.root_verify_key) req = { "handshake": "challenge", "challenge": b"1234567890", "supported_api_versions": [backend_version], "backend_timestamp": pendulum.now(), "ballpark_client_early_offset": BALLPARK_CLIENT_EARLY_OFFSET, "ballpark_client_late_offset": BALLPARK_CLIENT_LATE_OFFSET, } monkeypatch.setattr(ch, "SUPPORTED_API_VERSIONS", [client_version]) # Invalid versioning if not valid: with pytest.raises(HandshakeAPIVersionError) as context: ch.process_challenge_req(packb(req)) assert context.value.client_versions == [client_version] assert context.value.backend_versions == [backend_version] return # Valid versioning ch.process_challenge_req(packb(req)) assert ch.SUPPORTED_API_VERSIONS == [client_version] assert ch.challenge_data["supported_api_versions"] == [backend_version] assert ch.backend_api_version == backend_version assert ch.client_api_version == client_version
def test_handshake_challenge_schema_for_client_server_api_compatibility( mallory, alice, monkeypatch): ash = ServerHandshake() bch = AuthenticatedClientHandshake(mallory.organization_id, mallory.device_id, mallory.signing_key, mallory.root_verify_key) challenge = b"1234567890" # Backend API >= 2.5 and Client API < 2.5 client_version = ApiVersion(2, 4) backend_version = ApiVersion(2, 5) answer = { "handshake": "answer", "type": HandshakeType.AUTHENTICATED.value, "client_api_version": client_version, "organization_id": str(alice.organization_id), "device_id": str(alice.device_id), "rvk": alice.root_verify_key.encode(), "answer": alice.signing_key.sign(challenge), } ash.build_challenge_req() ash.challenge = challenge ash.process_answer_req(packb(answer)) result_req = ash.build_result_req(alice.verify_key) result = handshake_result_serializer.loads(result_req) assert result["result"] == "ok" # Backend API < 2.5 and Client API >= 2.5 client_version = ApiVersion(2, 5) backend_version = ApiVersion(2, 4) req = { "handshake": "challenge", "challenge": challenge, "supported_api_versions": [backend_version], "backend_timestamp": pendulum.now(), "ballpark_client_early_offset": BALLPARK_CLIENT_EARLY_OFFSET, "ballpark_client_late_offset": BALLPARK_CLIENT_LATE_OFFSET, } monkeypatch.setattr(bch, "SUPPORTED_API_VERSIONS", [client_version]) answer_req = bch.process_challenge_req(packb(req)) answer = handshake_answer_serializer.loads(answer_req) assert mallory.verify_key.verify(answer["answer"]) == challenge
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 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']}", }
def mock_api_versions(monkeypatch): default_client_version = ApiVersion(1, 11) default_backend_version = ApiVersion(1, 22) def _mock_api_versions(client_versions=None, backend_versions=None): if client_versions is not None: monkeypatch.setattr(APIV1_AnonymousClientHandshake, "SUPPORTED_API_VERSIONS", client_versions) if backend_versions is not None: monkeypatch.setattr(ServerHandshake, "SUPPORTED_API_VERSIONS", backend_versions) _mock_api_versions.default_client_version = default_client_version _mock_api_versions.default_backend_version = default_backend_version _mock_api_versions(client_versions=[default_client_version], backend_versions=[default_backend_version]) return _mock_api_versions
def test_process_challenge_req_good_multiple_api_version( alice, monkeypatch, client_versions, backend_versions, expected_client_version, expected_backend_version, ): # Cast parameters client_versions = [ApiVersion(*args) for args in client_versions] backend_versions = [ApiVersion(*args) for args in backend_versions] if expected_client_version: expected_client_version = ApiVersion(*expected_client_version) if expected_backend_version: expected_backend_version = ApiVersion(*expected_backend_version) ch = APIV1_AnonymousClientHandshake(alice.organization_id, alice.root_verify_key) req = { "handshake": "challenge", "challenge": b"1234567890", "supported_api_versions": list(backend_versions), "backend_timestamp": pendulum.now(), "ballpark_client_early_offset": BALLPARK_CLIENT_EARLY_OFFSET, "ballpark_client_late_offset": BALLPARK_CLIENT_LATE_OFFSET, } monkeypatch.setattr(ch, "SUPPORTED_API_VERSIONS", client_versions) # Invalid versioning if expected_client_version is None: with pytest.raises(HandshakeAPIVersionError) as context: ch.process_challenge_req(packb(req)) assert context.value.client_versions == client_versions assert context.value.backend_versions == backend_versions return # Valid versioning ch.process_challenge_req(packb(req)) assert ch.SUPPORTED_API_VERSIONS == client_versions assert ch.challenge_data["supported_api_versions"] == list( backend_versions) assert ch.backend_api_version == expected_backend_version assert ch.client_api_version == expected_client_version
def test_process_challenge_req_good_multiple_api_version( alice, monkeypatch, client_versions, backend_versions, expected_client_version, expected_backend_version, ): # Cast parameters client_versions = [ApiVersion(*args) for args in client_versions] backend_versions = [ApiVersion(*args) for args in backend_versions] if expected_client_version: expected_client_version = ApiVersion(*expected_client_version) if expected_backend_version: expected_backend_version = ApiVersion(*expected_backend_version) ch = APIV1_AuthenticatedClientHandshake(alice.organization_id, alice.device_id, alice.signing_key, alice.root_verify_key) req = { "handshake": "challenge", "challenge": b"1234567890", "supported_api_versions": list(backend_versions), } monkeypatch.setattr(ch, "SUPPORTED_API_VERSIONS", client_versions) # Invalid versioning if expected_client_version is None: with pytest.raises(HandshakeAPIVersionError) as context: ch.process_challenge_req(packb(req)) assert context.value.client_versions == client_versions assert context.value.backend_versions == backend_versions return # Valid versioning ch.process_challenge_req(packb(req)) assert ch.SUPPORTED_API_VERSIONS == client_versions assert ch.challenge_data["supported_api_versions"] == list( backend_versions) assert ch.backend_api_version == expected_backend_version assert ch.client_api_version == expected_client_version
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))}}}", }
def _deserialize(self, *args: object, **kwargs: object) -> ApiVersion: result = super()._deserialize(*args, **kwargs) return ApiVersion(*result)
def _deserialize(self, *args, **kwargs): result = super()._deserialize(*args, **kwargs) return ApiVersion(*result)