From 9cbc0ddf68d2f823c38eab0487a444b08a1cc639 Mon Sep 17 00:00:00 2001 From: pankjsingh Date: Thu, 27 Aug 2026 08:59:56 +0000 Subject: [PATCH] [TIPCommon] Fix OData filter URL encoding for special characters When constructing 1Platform OData $filter query parameters in OnePlatformSoarApi (such as in get_traking_list_records_filtered and get_traking_list_record), parameters containing reserved characters (such as '&' in environment or category names) caused the web server to split the query string, resulting in 400 Bad Request or missing custom list records. This change applies urllib.parse.quote() to the OData $filter string so that reserved characters and special symbols are safely percent-encoded and preserved in the backend API request. Bug: b/548190866 --- .../one_platform_soar_api.py | 8 ++- .../test_one_platform_soar_api.py | 65 +++++++++++++++++++ 2 files changed, 70 insertions(+), 3 deletions(-) diff --git a/packages/tipcommon/TIPCommon/src/TIPCommon/rest/soar_platform_clients/one_platform_soar_api.py b/packages/tipcommon/TIPCommon/src/TIPCommon/rest/soar_platform_clients/one_platform_soar_api.py index a6a0d56b11..816c0afc28 100644 --- a/packages/tipcommon/TIPCommon/src/TIPCommon/rest/soar_platform_clients/one_platform_soar_api.py +++ b/packages/tipcommon/TIPCommon/src/TIPCommon/rest/soar_platform_clients/one_platform_soar_api.py @@ -16,6 +16,8 @@ from __future__ import annotations +import urllib.parse + from typing import TYPE_CHECKING from TIPCommon.consts import DATAPLANE_1P_HEADER, DEFAULT_1P_PAGE_SIZE @@ -390,7 +392,7 @@ def get_traking_list_record(self) -> SingleJson: base_endpoint = "/system/settings/customLists" if filter_string: - initial_endpoint = f"{base_endpoint}?$filter={filter_string}&pageSize={_PAGE_SIZE}" + initial_endpoint = f"{base_endpoint}?$filter={urllib.parse.quote(filter_string)}&pageSize={_PAGE_SIZE}" else: initial_endpoint = f"{base_endpoint}?pageSize={_PAGE_SIZE}" @@ -411,7 +413,7 @@ def get_traking_list_records_filtered(self) -> SingleJson: base_endpoint = "/system/settings/customLists" if filter_string: - initial_endpoint = f"{base_endpoint}?$filter={filter_string}&pageSize={_PAGE_SIZE}" + initial_endpoint = f"{base_endpoint}?$filter={urllib.parse.quote(filter_string)}&pageSize={_PAGE_SIZE}" else: initial_endpoint = f"{base_endpoint}?pageSize={_PAGE_SIZE}" @@ -845,7 +847,7 @@ def get_cases_by_timestamp_filter(self) -> list[SingleJson]: base_endpoint = "/cases" initial_endpoint = ( - f"{base_endpoint}?$filter={filter_string}" + f"{base_endpoint}?$filter={urllib.parse.quote(filter_string)}" "&$select=id, updateTime" "&$expand=tags" f"&pageSize={_PAGE_SIZE}" diff --git a/packages/tipcommon/TIPCommon/tests/test_rest/test_soar_platform_clients/test_one_platform_soar_api.py b/packages/tipcommon/TIPCommon/tests/test_rest/test_soar_platform_clients/test_one_platform_soar_api.py index 8d3f853315..46e82cfecb 100644 --- a/packages/tipcommon/TIPCommon/tests/test_rest/test_soar_platform_clients/test_one_platform_soar_api.py +++ b/packages/tipcommon/TIPCommon/tests/test_rest/test_soar_platform_clients/test_one_platform_soar_api.py @@ -369,3 +369,68 @@ def test_save_or_update_job_invalid_resource_path( assert "Cannot parse resource path" in str(exc_info.value) assert 'Invalid parameter "name"' in str(exc_info.value) + + +def test_get_traking_list_records_filtered_url_encoded( + mocker: MockerFixture, mock_chronicle_soar: MagicMock, mock_get_sdk_api_uri: MagicMock +) -> None: + """Test get_traking_list_records_filtered URL-encodes special characters in OData filter.""" + import urllib.parse + + client = OnePlatformSoarApi(mock_chronicle_soar) + params: Any = client.params + params.environment = "Indevis GmbH & Co. KG" + params.category_name = "Indevis_Test_&_München" + params.entity_id = "192.168.1.100" + + mock_response = mocker.MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = {"customLists": [{"id": "list_1"}], "nextPageToken": None} + mock_chronicle_soar.session.request.return_value = mock_response + + records = client.get_traking_list_records_filtered() + + assert records == [{"id": "list_1"}] + expected_filter = client._build_tracking_list_filter_string( + params.category_name, params.entity_id, environment=params.environment + ) + expected_url = ( + f"https://mock-soar-api.com/system/settings/customLists?$filter={urllib.parse.quote(expected_filter)}&pageSize=1000" + ) + mock_chronicle_soar.session.request.assert_called_once_with( + "GET", + expected_url, + params=None, + json=None, + ) + + +def test_get_traking_list_record_url_encoded( + mocker: MockerFixture, mock_chronicle_soar: MagicMock, mock_get_sdk_api_uri: MagicMock +) -> None: + """Test get_traking_list_record URL-encodes special characters in OData filter.""" + import urllib.parse + + client = OnePlatformSoarApi(mock_chronicle_soar) + params: Any = client.params + params.category_name = "Indevis_Test_&_München" + params.entity_id = "192.168.1.100" + + mock_response = mocker.MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = {"customLists": [{"id": "list_2"}], "nextPageToken": None} + mock_chronicle_soar.session.request.return_value = mock_response + + records = client.get_traking_list_record() + + assert records == [{"id": "list_2"}] + expected_filter = client._build_tracking_list_filter_string(params.category_name, params.entity_id) + expected_url = ( + f"https://mock-soar-api.com/system/settings/customLists?$filter={urllib.parse.quote(expected_filter)}&pageSize=1000" + ) + mock_chronicle_soar.session.request.assert_called_once_with( + "GET", + expected_url, + params=None, + json=None, + )