From 0f97ecf762b0562e051d4cdc546686d525864ab8 Mon Sep 17 00:00:00 2001 From: rombernier Date: Wed, 11 Dec 2024 14:13:49 +0100 Subject: [PATCH] fix put json body --- Sekoia.io/CHANGELOG.md | 2 +- Sekoia.io/manifest.json | 2 +- .../synchronize_assets_with_ad.py | 18 +-- .../test_synchronize_assets.py | 109 ++++++++++++++++-- 4 files changed, 111 insertions(+), 20 deletions(-) diff --git a/Sekoia.io/CHANGELOG.md b/Sekoia.io/CHANGELOG.md index 7b0ed695a..d44cd6e17 100644 --- a/Sekoia.io/CHANGELOG.md +++ b/Sekoia.io/CHANGELOG.md @@ -6,7 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased -## 2024-12-10 - 2.65.10 +## 2024-12-10 - 2.65.11 ### Changed diff --git a/Sekoia.io/manifest.json b/Sekoia.io/manifest.json index 600f050ab..415436d03 100644 --- a/Sekoia.io/manifest.json +++ b/Sekoia.io/manifest.json @@ -12,7 +12,7 @@ "name": "Sekoia.io", "uuid": "92d8bb47-7c51-445d-81de-ae04edbb6f0a", "slug": "sekoia.io", - "version": "2.65.10", + "version": "2.65.11", "categories": [ "Generic" ] diff --git a/Sekoia.io/sekoiaio/operation_center/synchronize_assets_with_ad.py b/Sekoia.io/sekoiaio/operation_center/synchronize_assets_with_ad.py index 787f710f6..ee1549901 100644 --- a/Sekoia.io/sekoiaio/operation_center/synchronize_assets_with_ad.py +++ b/Sekoia.io/sekoiaio/operation_center/synchronize_assets_with_ad.py @@ -1,6 +1,7 @@ from urllib.parse import urljoin from typing import List, Dict, Any import requests +import json from pydantic import BaseModel from sekoia_automation.action import Action @@ -57,14 +58,14 @@ def get_assets(search_query: str, also_search_in_detection_properties: bool = Fa self.error(f"HTTP GET request failed: {response.url} with status code {response.status_code}") return response.json() - def post_request(endpoint: str, json_data: Dict[str, Any]) -> Dict[str, Any]: + def post_request(endpoint: str, json_data: str) -> Dict[str, Any]: api_path = urljoin(base_url + "/", endpoint) response = session.post(api_path, json=json_data) if not response.ok: self.error(f"HTTP POST request failed: {api_path} with status code {response.status_code}") return response.json() - def put_request(endpoint: str, json_data: Dict[str, Any]) -> None: + def put_request(endpoint: str, json_data: str) -> None: api_path = urljoin(base_url + "/", endpoint) response = session.put(api_path, json=json_data) if not response.ok: @@ -94,11 +95,11 @@ def merge_assets(destination: str, sources: List[str]) -> None: found_assets.add(asset["uuid"]) # Build asset payload - detection_properties = [] + detection_properties = {} for prop, keys in detection_properties_config.items(): values = [user_ad_data[key] for key in keys if key in user_ad_data and user_ad_data[key]] if values: - detection_properties.append({prop: values}) + detection_properties[prop] = values contextual_properties_config = asset_conf.get("contextual_properties", {}) custom_properties = {} @@ -117,12 +118,13 @@ def merge_assets(destination: str, sources: List[str]) -> None: "props": custom_properties, "atoms": detection_properties, } + json_payload_asset = json.dumps(payload_asset) created_asset = False destination_asset = "" if asset_name_json.get("total", 0) == 1: - self.log(f"asset name search response: {asset_name_json} and payload asset is {payload_asset}") + self.log(f"asset name search response: {asset_name_json} and payload asset is {json_payload_asset}") if asset_name_json["items"][0].get("name"): if str(asset_name_json["items"][0]["name"]).lower() == asset_name.lower(): asset_record = asset_name_json["items"][0] @@ -140,8 +142,8 @@ def merge_assets(destination: str, sources: List[str]) -> None: merge_assets(destination=destination_asset, sources=sources_to_merge) endpoint = f"v2/asset-management/assets/{destination_asset}" - self.log(f"put request: {endpoint} and payload asset is {payload_asset}") - put_request(endpoint=endpoint, json_data=payload_asset) + self.log(f"put request: {endpoint} and payload asset is {json_payload_asset}") + put_request(endpoint=endpoint, json_data=json_payload_asset) else: self.error(f"Unexpected asset name search response: {asset_name_json}") else: @@ -152,7 +154,7 @@ def merge_assets(destination: str, sources: List[str]) -> None: # Create the asset payload_asset["community_uuid"] = community_uuid - create_response = post_request(endpoint="v2/asset-management/assets", json_data=payload_asset) + create_response = post_request(endpoint="v2/asset-management/assets", json_data=json.dumps(payload_asset)) destination_asset = create_response["uuid"] if not destination_asset: self.error("Asset creation response does not contain 'uuid'.") diff --git a/Sekoia.io/tests/operation_center_action/test_synchronize_assets.py b/Sekoia.io/tests/operation_center_action/test_synchronize_assets.py index acee11e3a..41e050bdf 100644 --- a/Sekoia.io/tests/operation_center_action/test_synchronize_assets.py +++ b/Sekoia.io/tests/operation_center_action/test_synchronize_assets.py @@ -1,4 +1,5 @@ import pytest +import json from urllib.parse import urljoin from pydantic import BaseModel from typing import List, Dict, Any @@ -58,6 +59,100 @@ def arguments(self): community_uuid="community-1234", ) + def test_no_asset_found_and_create(self, requests_mock, action_instance, arguments): + """ + Test the SynchronizeAssetsWithAD action for the scenario where multiple assets are found, + and one of them is edited while merging the others. + """ + # Extract configuration from the mock module + base_url = action_instance.module.configuration["base_url"] + api_key = action_instance.module.configuration["api_key"] + + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {api_key}", + } + + # URLs + assets_url = urljoin(base_url + "/", "v2/asset-management/assets") + merge_url = urljoin(base_url + "/", "v2/asset-management/assets/merge") + update_url = urljoin(base_url + "/", "v2/asset-management/assets/asset-uuid-1") + create_url = urljoin(base_url + "/", "v2/asset-management/assets") + + # Helper functions to match specific GET requests + def match_asset_name(request): + search_query = request.qs.get("search", [None])[0] + also_search = "also_search_in_detection_properties" in request.qs + return search_query == "jdoe" and not also_search + + def match_detection_properties(request): + return request.qs.get("also_search_in_detection_properties", [None])[0] == "true" + + # Mock GET request for asset name search (should return one asset) + requests_mock.get( + assets_url, + additional_matcher=match_asset_name, + json={ + "total": 0, + "items": [], + }, + status_code=200, + ) + + # Mock GET request for detection properties (should return two additional assets) + requests_mock.get( + assets_url, + additional_matcher=match_detection_properties, + json={ + "total": 0, + "items": [], + }, + status_code=200, + ) + + # Mock PUT request to update the destination asset + requests_mock.post( + create_url, + json={"uuid": "asset_uuid_in_response"}, # Assume successful update with empty response + status_code=200, + ) + + # Execute the action + response = action_instance.run(arguments) + + # Assertions + assert response["created_asset"] is True, "Asset should not be created since it exists." + assert response["destination_asset"] == "asset_uuid_in_response", "Destination asset UUID mismatch." + assert set(response["found_assets"]) == set(), "Found assets mismatch." + + # Verify that the correct number of requests were made + # Expected Requests: + # 1. GET asset name search + # 2. 2 GET for all the detection properties search + # 3. PUT update asset + # 4. POST merge assets + assert ( + len(requests_mock.request_history) == 4 + ), f"Expected 4 HTTP requests, got {len(requests_mock.request_history)}." + + # Optionally, verify the payloads of PUT and POST requests + # Verify PUT request payload + post_requests = [req for req in requests_mock.request_history if req.method == "POST"] + assert len(post_requests) == 1, "Expected one POST request." + post_request = post_requests[0] + expected_post_payload = { + "name": "jdoe", + "description": "", + "type": "account", + "category": "user", + "reviewed": True, + "source": "manual", + "props": {"dept": "engineering"}, + "atoms": {"email": ["jdoe@example.com"], "department": ["engineering"]}, + "community_uuid": "community-1234", + } + assert post_request.json() == json.dumps(expected_post_payload), "POST request payload mismatch." + def test_on_asset_found_and_merge(self, requests_mock, action_instance, arguments): """ Test the SynchronizeAssetsWithAD action for the scenario where multiple assets are found, @@ -147,12 +242,9 @@ def match_detection_properties(request): "reviewed": True, "source": "manual", "props": {"dept": "engineering"}, - "atoms": [ - {"email": ["jdoe@example.com"]}, - {"department": ["engineering"]}, - ], + "atoms": {"email": ["jdoe@example.com"], "department": ["engineering"]}, } - assert put_request.json() == expected_put_payload, "PUT request payload mismatch." + assert put_request.json() == json.dumps(expected_put_payload), "PUT request payload mismatch." def test_multiple_assets_found_and_merge(self, requests_mock, action_instance, arguments): """ @@ -257,12 +349,9 @@ def match_detection_properties(request): "reviewed": True, "source": "manual", "props": {"dept": "engineering"}, - "atoms": [ - {"email": ["jdoe@example.com"]}, - {"department": ["engineering"]}, - ], + "atoms": {"email": ["jdoe@example.com"], "department": ["engineering"]}, } - assert put_request.json() == expected_put_payload, "PUT request payload mismatch." + assert put_request.json() == json.dumps(expected_put_payload), "PUT request payload mismatch." # Verify POST merge request payload post_merge_requests = [