Skip to content

Commit

Permalink
Merge pull request #1223 from SEKOIA-IO/fix/synchronize-asset-format
Browse files Browse the repository at this point in the history
Fix/synchronize asset format
  • Loading branch information
rombernier authored Dec 11, 2024
2 parents 7ac4783 + 0f97ecf commit ec5c024
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 20 deletions.
2 changes: 1 addition & 1 deletion Sekoia.io/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion Sekoia.io/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
]
Expand Down
18 changes: 10 additions & 8 deletions Sekoia.io/sekoiaio/operation_center/synchronize_assets_with_ad.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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 = {}
Expand All @@ -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]
Expand All @@ -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:
Expand All @@ -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'.")
Expand Down
109 changes: 99 additions & 10 deletions Sekoia.io/tests/operation_center_action/test_synchronize_assets.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import pytest
import json
from urllib.parse import urljoin
from pydantic import BaseModel
from typing import List, Dict, Any
Expand Down Expand Up @@ -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": ["[email protected]"], "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,
Expand Down Expand Up @@ -147,12 +242,9 @@ def match_detection_properties(request):
"reviewed": True,
"source": "manual",
"props": {"dept": "engineering"},
"atoms": [
{"email": ["[email protected]"]},
{"department": ["engineering"]},
],
"atoms": {"email": ["[email protected]"], "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):
"""
Expand Down Expand Up @@ -257,12 +349,9 @@ def match_detection_properties(request):
"reviewed": True,
"source": "manual",
"props": {"dept": "engineering"},
"atoms": [
{"email": ["[email protected]"]},
{"department": ["engineering"]},
],
"atoms": {"email": ["[email protected]"], "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 = [
Expand Down

0 comments on commit ec5c024

Please sign in to comment.