SOCRadarIncidentsV4
SOCRadar Incidents v4 API integration with advanced incident management, status reasons, and compliance tracking. Fetches security incidents from SOCRadar platform with proper deduplication and date handling. Supports filtering by alarm type IDs for granular control.
Analytics & SIEM · SOCRadar
Details
| ID | SOCRadarIncidentsV4 |
|---|---|
| Provider | SOCRadar |
| Category | Analytics & SIEM |
| From Version | 6.10.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM |
README
SOCRadar Incidents v4.0
Fetch and manage security incidents from SOCRadar’s Incident API v4 with advanced filtering, multi-status selection, and comprehensive incident enrichment.
Overview
SOCRadar is a digital risk protection platform that provides extended threat intelligence and brand protection capabilities. This integration enables XSOAR to ingest security incidents from SOCRadar’s Incident API v4, including:
- Brand Protection: Impersonating domains, phishing attacks, brand abuse
- Cyber Threat Intelligence: Stolen credentials, data leaks, malware infections
- Attack Surface Management: External exposure findings, misconfigurations
- Dark Web Intelligence: Compromised credentials, leaked data from dark web sources
- Supply Chain Security: Third-party risks and vendor security issues
What’s New in v4.0
Major Enhancements
- Multi-Status Filtering: Select multiple statuses (OPEN, CLOSED, ON_HOLD) simultaneously
- Epoch Time Precision: Second-level accuracy for incident fetching - zero duplicates
- Reverse Pagination: Fetches newest incidents first for better performance
- Dynamic Content Extraction: Automatically extracts alarm-specific fields regardless of type
- Parametric Company ID Display: Control whether company ID appears in incidents
- Enhanced Deduplication: Two-layer protection prevents duplicate incidents
- New Commands: Ask analyst, change severity, and more
Technical Improvements
- Interval-based fetching with overlap protection
- Configurable content and entity inclusion
- Integer company ID with validation
- Comprehensive debug logging
- Better error handling and recovery
Key Features
Incident Fetching
- Fetch incidents with configurable interval (default: 1 minute)
- Support for date ranges and epoch timestamps
- Automatic deduplication (tracks last 1000 alarm IDs)
- Reverse pagination for optimal performance
Flexible Filtering
- Multi-Status Selection: OPEN, CLOSED, ON_HOLD (multi-select)
- Severity Levels: LOW, MEDIUM, HIGH, CRITICAL
- Alarm Types: Filter by main type, sub-type, or type ID
- Custom Filters: Tags, assignees, date ranges
Rich Incident Data
- Dynamic CustomFields based on alarm type
- Configurable content extraction (varies by alarm type)
- Optional entity details inclusion
- Optional company ID visibility
Incident Management
- Change alarm status (11 status options)
- Add comments to alarms
- Change assignees
- Add/remove tags
- Request analyst assistance
- Modify severity levels
- Mark as false positive/resolved
Prerequisites
Required
- SOCRadar account with Incident API access
- API Key from SOCRadar platform
- Company ID
- XSOAR 6.x or later
API Access
To obtain your API credentials:
- Log in to SOCRadar Platform
- Navigate to Settings → API & Integrations
- Go to API Options page
- Copy your Company API Key (for Incident API)
- Note your Company ID
Configuration
Integration Settings
| Parameter | Required | Default | Description |
|---|---|---|---|
| Server URL | Yes | https://platform.socradar.com/api |
SOCRadar API base URL |
| API Key | Yes | - | Your Company API Key from SOCRadar |
| Company ID | Yes | - | Your Company ID (integer) |
| Fetch incidents | No | False | Enable automatic incident fetching |
| Incident type | No | - | XSOAR incident type to create |
| Max incidents per fetch | No | 10000 | Maximum incidents per fetch cycle |
| First fetch time | No | 3 days | Initial time range for first fetch |
| Fetch Interval (Minutes) | No | 1 | Time window for subsequent fetches |
Filtering Options
| Parameter | Type | Description |
|---|---|---|
| Status Filter | Multi-select | Select one or more: OPEN, CLOSED, ON_HOLD |
| Severity | Multi-select | Filter by: LOW, MEDIUM, HIGH, CRITICAL |
| Alarm Type IDs | Text | Comma-separated list of type IDs to include |
| Excluded Alarm Type IDs | Text | Comma-separated list of type IDs to exclude |
| Main Alarm Types | Text | Comma-separated main types (e.g., “Brand Protection”) |
| Alarm Sub Types | Text | Comma-separated sub types |
Content Options
| Parameter | Default | Description |
|---|---|---|
| Include Alarm Content | True | Extract content fields to CustomFields |
| Include Related Entities | True | Include entity details in CustomFields |
| Include Company ID | False | Show company ID in incidents (for multi-tenant) |
Installation
From XSOAR Marketplace
- Navigate to Settings → Marketplace
- Search for “SOCRadar”
- Click Install
- Configure integration instance
From GitHub (Manual)
# Clone the content repository
git clone https://github.com/demisto/content.git
cd content/Packs/SOCRadar
# Copy to your XSOAR instance
# Upload via Settings → Integrations → Upload
Setup Guide
Step 1: Create Integration Instance
- Navigate to Settings → Integrations → Servers & Services
- Search for “SOCRadar Incidents v4”
- Click Add instance
Step 2: Configure Basic Settings
Name: SOCRadar Production
Server URL: https://platform.socradar.com/api
API Key: [Your API Key from SOCRadar]
Company ID: [Your Company ID]
Step 3: Configure Fetching (Optional)
✓ Fetches incidents
Incident type: SOCRadar Incident
Max incidents per fetch: 10000
First fetch time: 3 days
Fetch Interval: 1 (minutes)
Step 4: Configure Filters (Optional)
Status Filter:
☑ OPEN
☐ CLOSED
☐ ON_HOLD
Content Options:
☑ Include Alarm Content in Custom Fields
☑ Include Related Entities Details
☐ Include Company ID in Incidents
Step 5: Test Connection
Click Test button to verify:
- API connectivity
- Authentication
- Company ID validity
Commands
Incident Management
socradar-change-alarm-status
Change the status of one or more alarms.
Arguments:
alarm_ids(Required): Comma-separated alarm IDs (e.g., “12345,67890”)status_reason(Required): New statusOPEN,INVESTIGATING,RESOLVED,PENDING_INFOLEGAL_REVIEW,VENDOR_ASSESSMENT,FALSE_POSITIVEDUPLICATE,PROCESSED_INTERNALLY,MITIGATED,NOT_APPLICABLE
comments(Optional): Status change commentscompany_id(Optional): Override default company ID
Example:
!socradar-change-alarm-status alarm_ids="81171696" status_reason="INVESTIGATING" comments="Under review"
From Incident Context:
!socradar-change-alarm-status alarm_ids=${incident.dbotMirrorId} status_reason="RESOLVED"
socradar-mark-false-positive
Mark alarm as false positive (shortcut command).
Arguments:
alarm_id(Required): Alarm IDcomments(Optional): Reason for false positivecompany_id(Optional): Override default company ID
Example:
!socradar-mark-false-positive alarm_id="81171696" comments="Not a real threat"
socradar-mark-resolved
Mark alarm as resolved (shortcut command).
Arguments:
alarm_id(Required): Alarm IDcomments(Optional): Resolution notescompany_id(Optional): Override default company ID
Example:
!socradar-mark-resolved alarm_id="81171696" comments="Issue fixed"
For Notes
socradar-add-comment
Add a comment to an alarm.
Arguments:
alarm_id(Required): Alarm IDuser_email(Required): Email of user posting commentcomment(Required): Comment textcompany_id(Optional): Override default company ID
Example:
!socradar-add-comment alarm_id="81171696" user_email="useremail" comment="Investigating with security team"
socradar-ask-analyst
Request assistance from SOCRadar analyst.
Arguments:
alarm_id(Required): Alarm IDcomment(Required): Message for analystcompany_id(Optional): Override default company ID
Example:
!socradar-ask-analyst alarm_id="81171696" comment="Need help analyzing this credential leak"
Assignment & Organization
socradar-add-assignee
Change alarm assignee(s).(User must be defined the same company)
Arguments:
alarm_id(Required): Alarm IDuser_emails(Required): Comma-separated email addressescompany_id(Optional): Override default company ID
Example:
!socradar-add-assignee alarm_id="81171696" user_emails="useremail"
socradar-add-tag
Add or remove a tag from alarm.
Arguments:
alarm_id(Required): Alarm IDtag(Required): Tag namecompany_id(Optional): Override default company ID
Example:
!socradar-add-tag alarm_id="81171696" tag="reviewed"
socradar-change-severity
Modify alarm severity level.
Arguments:
alarm_id(Required): Alarm IDseverity(Required): New severity (LOW, MEDIUM, HIGH, CRITICAL)company_id(Optional): Override default company ID
Example:
!socradar-change-severity alarm_id="81171696" severity="HIGH"
Testing
socradar-test-fetch
Test the fetch incidents functionality without creating incidents.
Arguments:
first_fetch(Optional): Time range to test (default: “3 days”)limit(Optional): Number of incidents to fetch (default: 5)
Example:
!socradar-test-fetch first_fetch="1 hour" limit="10"
Output:
Found 150 incident(s) from 2024-12-20!
Total available: 1000 records across 10 pages
Using REVERSE PAGINATION (page 10 → page 1)
Sample incidents:
- [81171696] HIGH | OPEN | example.com
Company: 789 | Type: Brand Protection / Impersonating Domain
Custom Fields
The integration creates these CustomFields in XSOAR incidents:
Standard Fields (Always Present)
socradaralarmid: Alarm IDsocradarstatus: Current statussocradarasset: Affected assetsocradaralarmtype: Main alarm typesocradaralarmsubtype: Alarm sub-typesocradaralarmtypeid: Type IDsocradartags: Comma-separated tagssocradarrisklevel: Risk levelsocradaralarmtext: Alarm description (truncated to 1000 chars)
Optional Fields
socradarcompanyid: Company ID (if “Include Company ID” enabled)socradarentities: Related entities (if “Include Entities” enabled)
Dynamic Content Fields (if “Include Content” enabled)
Content structure varies by alarm type. Examples:
Impersonating Domain:
socradarcontentdns_informationsocradarcontentwhois_informationsocradarcontentdomain_status
Stolen Credentials:
socradarcontentcredential_detailssocradarcontentlog_content_linksocradarcontentmalware_family
Data Leak:
socradarcontentsource_full_contentsocradarcontentcompromised_emailssocradarcontentcompromised_domains
Use Cases
1. Brand Protection
Scenario: Detect and respond to phishing domains impersonating your brand.
Configuration:
Main Alarm Types: Brand Protection
Status Filter: OPEN
Severity: HIGH, CRITICAL
Automation:
# Get incident
incident = demisto.incident()
alarm_id = incident.get('dbotMirrorId')
# Check domain details
domain_info = incident.get('CustomFields', {}).get('socradarcontentdns_information')
# Take action
demisto.executeCommand('socradar-add-assignee', {
'alarm_id': alarm_id,
'user_emails': 'useremail@usermail.com'
})
2. Multi-Tenant Operations (MSSP)
Scenario: Manage incidents for multiple customers.
Configuration:
Instance 1:
- Company ID: 789
- Include Company ID: ✓ YES
Instance 2:
- Company ID: 456
- Include Company ID: ✓ YES
Automation:
# Company ID is visible in CustomFields
company_id = incident.get('CustomFields', {}).get('socradarcompanyid')
# Route to appropriate team
if company_id == "789":
assign_to = "useremail"
else:
assign_to = "useremail"
demisto.executeCommand('socradar-add-assignee', {
'alarm_id': incident.get('dbotMirrorId'),
'user_emails': assign_to
})
Troubleshooting
No Incidents Fetched
Check:
- Test connection: Click “Test” button
- Date range: Increase “First fetch time” to “7 days”
- Filters: Remove status/severity filters temporarily
- Debug logs: Check XSOAR server logs
Debug Command:
!socradar-test-fetch first_fetch="7 days" limit="10"
Look for:
[SOCRadar V4.0] Total available: 0 records
If 0 records, no alarms match your filters.
Duplicate Incidents
This should NOT happen in v4.0! If you see duplicates:
- Check fetch interval: Should be ≥ 1 minute
-
Check logs for deduplication stats:
[SOCRadar V4.0] Page 1: Created 5 NEW incidents, skipped 95 duplicates -
Verify epoch time usage:
[SOCRadar V4.0] Time window (epoch seconds): 1734465600 to 1734465660
If duplicates persist, contact support with debug logs.
API Errors
401 Unauthorized:
- Verify API Key is correct
404 Not Found:
- Verify Company ID is correct (integer, not string)
- Check endpoint URLs in debug logs
Rate Limiting:
- Reduce fetch frequency
- Decrease “Max incidents per fetch”
Missing CustomFields
If dynamic content fields not appearing:
- Verify “Include Alarm Content” is enabled
- Create CustomFields manually in XSOAR:
- Settings → Advanced → Fields
- Add fields with type “Short Text”
- Prefix:
socradarcontent
If entities not appearing:
- Verify “Include Related Entities Details” is enabled
Performance Optimization
High-Volume Environments
Recommended Settings:
Max incidents per fetch: 10000
Fetch Interval: 5 (minutes)
Include Alarm Content: ✓ (enabled)
Include Related Entities: ✓ (enabled)
With Filters:
Status Filter: OPEN only
Severity: HIGH, CRITICAL only
This reduces data volume while capturing critical alerts.
Low-Volume Environments
Recommended Settings:
Max incidents per fetch: 1000
Fetch Interval: 1 (minutes)
Include Alarm Content: ✓ (enabled)
Include Related Entities: ✓ (enabled)
Faster response to new incidents.
Support
Official Support
- SOCRadar XSOAR Support: XSOAR@socradar.io
- SOCRadar Support: support@socradar.io
- Platform: platform.socradar.com
Feature Requests
Submit feature requests through:
- SOCRadar platform Request
- XSOAR content repository issues
Version History
v4.0.0 (December 2024)
- Initial release of SOCRadar Incidents v4.0
- Multi-status filtering support
- Epoch time precision for zero duplicates
- Reverse pagination implementation
- Dynamic content extraction
- Enhanced deduplication (2-layer)
- New commands: ask-analyst, change-severity
- Parametric company ID display
- Comprehensive debug logging
License
This integration is provided under the MIT License.
About SOCRadar
SOCRadar is a leading Extended Threat Intelligence (XTI) platform that helps organizations:
- Monitor and protect their digital assets
- Detect brand abuse and phishing attacks
- Identify stolen credentials and data leaks
- Track dark web activities
- Manage attack surface exposure
- Ensure supply chain security
Configuration parameters
apikey— (required)company_id— Company ID (required)isFetch— Fetch incidentsfirst_fetch— First Fetch Timefetch_interval_minutes— Fetch Interval (Minutes)max_fetch— Max incidents per fetchshow_content— Show Alarm Contentstatus— Status Filterseverities— Severity Levelsalarm_type_ids— Alarm Type IDs (Include)excluded_alarm_type_ids— Alarm Type IDs (Exclude)alarm_main_types— Alarm Main Typesexcluded_alarm_main_types— Alarm Main Types (Exclude)alarm_sub_types— Alarm Sub Typesexcluded_alarm_sub_types— Alarm Sub Types (Exclude)include_company_id— Include Company IDinclude_incident_link— Include Incident Linkinclude_mitigation— Include Mitigationinclude_response— Include Responseinclude_detection_and_analysis— Include Detection And Analysisinclude_post_incident_analysis— Include Post Incident Analysisinclude_related_assets— Include Related Assetsinclude_related_entities— Include Related Entitiesinsecure— Trust any certificate (not secure)proxy— Use system proxy settingsincidentType— Incident typeincidentFetchInterval— Incidents Fetch Interval
Commands (8)
-
socradar-add-assigneeadd the assignee(s) of an alarm.
-
socradar-add-commentAdd a comment to an alarm.
-
socradar-add-tagAdd or remove a tag from an alarm.
-
socradar-change-alarm-statusChange the status of one or more alarms.
-
socradar-change-assigneeDeprecatedadd the assignee(s) of an alarm.
-
socradar-mark-false-positiveMark an alarm as false positive.
-
socradar-mark-resolvedMark an alarm as resolved.
-
socradar-test-fetchTest incident fetching to verify alarms are available and date parsing works correctly.
import pytest from datetime import datetime, timedelta # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- def _make_alarm( alarm_id=123, risk="HIGH", asset="TESTSOCRadar", status="OPEN", main_type="Test Type", sub_type="Test Sub", alarm_text="Test alarm", date="2024-01-15T10:30:00", tags=None, related_entities=None, company_id="456", ): return { "alarm_id": alarm_id, "company_id": company_id, "alarm_risk_level": risk, "alarm_asset": asset, "status": status, "date": date, "alarm_type_details": { "alarm_main_type": main_type, "alarm_sub_type": sub_type, }, "alarm_text": alarm_text, "tags": tags or [], "alarm_related_entities": related_entities or [], } def _mock_response(alarms, total_pages=1, total_records=None): return { "is_success": True, "data": alarms, "total_records": total_records if total_records is not None else len(alarms), "total_pages": total_pages, "current_page": 1, } # --------------------------------------------------------------------------- # Fixture # --------------------------------------------------------------------------- @pytest.fixture def mock_client(): from SOCRadarIncidentsV4 import Client client = Client( base_url="https://platform.socradar.com/api", api_key="test-api-key", company_id="123", verify=False, proxy=False, ) return client # --------------------------------------------------------------------------- # convert_to_demisto_severity # --------------------------------------------------------------------------- @pytest.mark.parametrize( "severity,expected_attr", [ ("low", "LOW"), ("medium", "MEDIUM"), ("high", "HIGH"), ("critical", "CRITICAL"), ("info", "INFO"), ("unknown", "UNKNOWN"), ], ) def test_convert_to_demisto_severity(severity, expected_attr): from SOCRadarIncidentsV4 import convert_to_demisto_severity from CommonServerPython import IncidentSeverity result = convert_to_demisto_severity(severity) assert result == getattr(IncidentSeverity, expected_attr) # --------------------------------------------------------------------------- # parse_alarm_date # --------------------------------------------------------------------------- @pytest.mark.parametrize( "date_str,description", [ ("2024-01-15T10:30:00.123456", "with_microseconds"), ("2024-01-15T10:30:00", "without_microseconds"), ("2024-01-15", "date_only"), ], ) def test_parse_alarm_date_valid(date_str, description): from SOCRadarIncidentsV4 import parse_alarm_date result = parse_alarm_date(date_str) assert result is not None assert isinstance(result, datetime) @pytest.mark.parametrize( "date_str,description", [ (None, "none_date"), ("not-a-date", "invalid_format"), ], ) def test_parse_alarm_date_invalid(date_str, description): from SOCRadarIncidentsV4 import parse_alarm_date result = parse_alarm_date(date_str) assert result is None # --------------------------------------------------------------------------- # alarm_to_incident # --------------------------------------------------------------------------- def test_alarm_to_incident_basic(): from SOCRadarIncidentsV4 import alarm_to_incident alarm = _make_alarm() incident = alarm_to_incident(alarm) assert "SOCRadar Alarm 123" in incident["name"] assert "Test Type" in incident["name"] assert "Test Sub" in incident["name"] assert "TESTSOCRadar" in incident["name"] # asset field value assert incident["CustomFields"]["socradaralarmid"] == "123" assert incident["CustomFields"]["socradarasset"] == "TESTSOCRadar" assert incident["CustomFields"]["socradarstatus"] == "OPEN" assert incident["CustomFields"]["socradaralarmtype"] == "Test Type" def test_alarm_to_incident_with_tags_and_entities(): from SOCRadarIncidentsV4 import alarm_to_incident alarm = _make_alarm( tags=["tag1", "tag2"], related_entities=[{"key": "IP", "value": "1.2.3.4"}], ) incident = alarm_to_incident(alarm) assert "tag1,tag2" in incident["CustomFields"]["socradartags"] assert "IP: 1.2.3.4" in incident["details"] def test_alarm_to_incident_missing_fields(): from SOCRadarIncidentsV4 import alarm_to_incident alarm = {"alarm_id": 999} incident = alarm_to_incident(alarm) assert incident["name"] == "SOCRadar Alarm 999: Unknown [N/A]" assert incident["CustomFields"]["socradaralarmid"] == "999" def test_alarm_to_incident_raw_json(): import json from SOCRadarIncidentsV4 import alarm_to_incident alarm = _make_alarm() incident = alarm_to_incident(alarm) raw = json.loads(incident["rawJSON"]) assert raw["alarm_id"] == 123 # --------------------------------------------------------------------------- # Client # --------------------------------------------------------------------------- class TestClient: def test_client_initialization(self, mock_client): assert mock_client.api_key == "test-api-key" assert mock_client.company_id == "123" def test_get_headers(self, mock_client): headers = mock_client._get_headers() assert headers == {"API-Key": "test-api-key"} def test_search_incidents_params(self, mock_client, mocker): mock_http = mocker.patch.object( mock_client, "_http_request", return_value={ "is_success": True, "data": {"alarms": [], "total_pages": 1, "total_records": 0}, }, ) mock_client.search_incidents(limit=10, page=2, start_date="2024-01-01") call_kwargs = mock_http.call_args params = call_kwargs[1]["params"] assert params["limit"] == 10 assert params["page"] == 2 assert params["start_date"] == "2024-01-01" def test_change_alarm_status_invalid_status(self, mock_client): with pytest.raises(ValueError, match="Invalid status reason"): mock_client.change_alarm_status([1], "INVALID_STATUS") def test_change_alarm_status_success(self, mock_client, mocker): mocker.patch.object( mock_client, "_http_request", return_value={"is_success": True}, ) result = mock_client.change_alarm_status([1, 2], "RESOLVED") assert result["is_success"] is True def test_add_alarm_comment(self, mock_client, mocker): mocker.patch.object( mock_client, "_http_request", return_value={"is_success": True}, ) result = mock_client.add_alarm_comment(1, "user@test.com", "test comment") assert result["is_success"] is True def test_add_alarm_assignee(self, mock_client, mocker): mocker.patch.object( mock_client, "_http_request", return_value={"is_success": True}, ) result = mock_client.add_alarm_assignee(1, user_emails=["user@test.com"]) assert result["is_success"] is True def test_add_remove_tag(self, mock_client, mocker): mocker.patch.object( mock_client, "_http_request", return_value={"is_success": True}, ) result = mock_client.add_remove_tag(1, "critical-tag") assert result["is_success"] is True # --------------------------------------------------------------------------- # test_module # --------------------------------------------------------------------------- def test_test_module_success(mock_client, mocker): from SOCRadarIncidentsV4 import test_module mocker.patch.object(mock_client, "search_incidents", return_value={"is_success": True, "data": []}) result = test_module(mock_client) assert result == "ok" def test_test_module_failure(mock_client, mocker, capfd): from SOCRadarIncidentsV4 import test_module mocker.patch.object( mock_client, "search_incidents", return_value={"is_success": False, "message": "Authentication failed"}, ) with capfd.disabled(): result = test_module(mock_client) assert "Test failed" in result def test_test_module_unauthorized(mock_client, mocker, capfd): from SOCRadarIncidentsV4 import test_module from CommonServerPython import DemistoException mocker.patch.object( mock_client, "search_incidents", side_effect=DemistoException("401 Unauthorized"), ) with capfd.disabled(): result = test_module(mock_client) assert "Authorization Error" in result def test_test_module_forbidden(mock_client, mocker, capfd): from SOCRadarIncidentsV4 import test_module from CommonServerPython import DemistoException mocker.patch.object(mock_client, "search_incidents", side_effect=DemistoException("403 Forbidden")) with capfd.disabled(): result = test_module(mock_client) assert "Access Denied" in result def test_test_module_not_found(mock_client, mocker, capfd): from SOCRadarIncidentsV4 import test_module from CommonServerPython import DemistoException mocker.patch.object(mock_client, "search_incidents", side_effect=DemistoException("404 Not Found")) with capfd.disabled(): result = test_module(mock_client) assert "Not Found" in result # --------------------------------------------------------------------------- # status_reason_map completeness # --------------------------------------------------------------------------- def test_status_reason_map_completeness(): from SOCRadarIncidentsV4 import STATUS_REASON_MAP expected_statuses = [ "OPEN", "INVESTIGATING", "RESOLVED", "PENDING_INFO", "LEGAL_REVIEW", "VENDOR_ASSESSMENT", "FALSE_POSITIVE", "DUPLICATE", "PROCESSED_INTERNALLY", "MITIGATED", "NOT_APPLICABLE", ] for status in expected_statuses: assert status in STATUS_REASON_MAP # --------------------------------------------------------------------------- # fetch_incidents # --------------------------------------------------------------------------- def test_fetch_incidents_first_fetch(mock_client, mocker): """Test fetch incidents on first run""" from SOCRadarIncidentsV4 import fetch_incidents mocker.patch.object(mock_client, "search_incidents", return_value=_mock_response([_make_alarm()])) next_run, incidents = fetch_incidents( client=mock_client, max_results=100, last_run={}, first_fetch_time="3 days", fetch_interval_minutes=1, ) assert len(incidents) == 1 assert "last_fetch" in next_run assert "last_alarm_ids" in next_run assert 123 in next_run["last_alarm_ids"] def test_fetch_incidents_subsequent_fetch(mock_client, mocker): """Test fetch incidents on subsequent runs uses interval""" from SOCRadarIncidentsV4 import fetch_incidents last_fetch_time = (datetime.now() - timedelta(minutes=5)).isoformat() + "Z" mocker.patch.object( mock_client, "search_incidents", return_value=_mock_response([_make_alarm(alarm_id=200)]), ) next_run, incidents = fetch_incidents( client=mock_client, max_results=100, last_run={"last_fetch": last_fetch_time, "last_alarm_ids": []}, first_fetch_time="3 days", fetch_interval_minutes=1, ) assert len(incidents) == 1 assert 200 in next_run["last_alarm_ids"] def test_fetch_incidents_with_duplicates(mock_client, mocker): """Test that duplicate alarm IDs are skipped""" from SOCRadarIncidentsV4 import fetch_incidents last_fetch_time = (datetime.now() - timedelta(minutes=5)).isoformat() + "Z" mocker.patch.object( mock_client, "search_incidents", return_value=_mock_response([_make_alarm(alarm_id=123)]), ) next_run, incidents = fetch_incidents( client=mock_client, max_results=100, last_run={"last_fetch": last_fetch_time, "last_alarm_ids": [123]}, first_fetch_time="3 days", fetch_interval_minutes=1, ) assert len(incidents) == 0 def test_fetch_incidents_no_alarms(mock_client, mocker): """Test fetch when no alarms returned""" from SOCRadarIncidentsV4 import fetch_incidents mocker.patch.object(mock_client, "search_incidents", return_value=_mock_response([])) next_run, incidents = fetch_incidents( client=mock_client, max_results=100, last_run={}, first_fetch_time="3 days", fetch_interval_minutes=1, ) assert len(incidents) == 0 assert "last_fetch" in next_run assert "last_alarm_ids" in next_run def test_fetch_incidents_error_handling(mock_client, mocker, capfd): """Test fetch gracefully handles API errors""" from SOCRadarIncidentsV4 import fetch_incidents mocker.patch.object(mock_client, "search_incidents", side_effect=Exception("API Error")) with capfd.disabled(): next_run, incidents = fetch_incidents( client=mock_client, max_results=100, last_run={}, first_fetch_time="3 days", fetch_interval_minutes=1, ) assert incidents == [] assert "last_fetch" in next_run def test_fetch_incidents_with_filters(mock_client, mocker): """Test fetch passes filters correctly to search_incidents""" from SOCRadarIncidentsV4 import fetch_incidents mock_search = mocker.patch.object(mock_client, "search_incidents", return_value=_mock_response([_make_alarm()])) fetch_incidents( client=mock_client, max_results=100, last_run={}, first_fetch_time="3 days", fetch_interval_minutes=1, status=["OPEN"], severities=["HIGH"], alarm_main_types=["Phishing"], ) call_kwargs = mock_search.call_args[1] assert call_kwargs["status"] == ["OPEN"] assert call_kwargs["severities"] == ["HIGH"] assert call_kwargs["alarm_main_types"] == ["Phishing"] # --------------------------------------------------------------------------- # change_status_command # --------------------------------------------------------------------------- def test_change_status_command_success(mock_client, mocker): from SOCRadarIncidentsV4 import change_status_command mocker.patch.object(mock_client, "change_alarm_status", return_value={"is_success": True}) result = change_status_command(mock_client, {"alarm_ids": "1,2", "status_reason": "RESOLVED"}) assert "Status changed" in result.readable_output def test_change_status_command_missing_params(mock_client): from SOCRadarIncidentsV4 import change_status_command with pytest.raises(ValueError, match="required"): change_status_command(mock_client, {"alarm_ids": "", "status_reason": ""}) # --------------------------------------------------------------------------- # mark commands # --------------------------------------------------------------------------- def test_mark_false_positive_success(mock_client, mocker): from SOCRadarIncidentsV4 import mark_as_false_positive_command mocker.patch.object(mock_client, "change_alarm_status", return_value={"is_success": True}) result = mark_as_false_positive_command(mock_client, {"alarm_id": "1"}) assert "false positive" in result.readable_output def test_mark_false_positive_missing_alarm_id(mock_client): from SOCRadarIncidentsV4 import mark_as_false_positive_command with pytest.raises(ValueError, match="alarm_id is required"): mark_as_false_positive_command(mock_client, {}) def test_mark_resolved_success(mock_client, mocker): from SOCRadarIncidentsV4 import mark_as_resolved_command mocker.patch.object(mock_client, "change_alarm_status", return_value={"is_success": True}) result = mark_as_resolved_command(mock_client, {"alarm_id": "1"}) assert "resolved" in result.readable_output # --------------------------------------------------------------------------- # add_comment / add_assignee / add_tag commands # --------------------------------------------------------------------------- def test_add_comment_success(mock_client, mocker): from SOCRadarIncidentsV4 import add_comment_command mocker.patch.object(mock_client, "add_alarm_comment", return_value={"is_success": True}) result = add_comment_command( mock_client, {"alarm_id": "1", "user_email": "user@example.com", "comment": "test"}, ) assert "Comment added" in result.readable_output def test_add_comment_missing_params(mock_client): from SOCRadarIncidentsV4 import add_comment_command with pytest.raises(ValueError): add_comment_command(mock_client, {"alarm_id": "1", "user_email": "", "comment": ""}) def test_add_assignee_success(mock_client, mocker): from SOCRadarIncidentsV4 import add_assignee_command mocker.patch.object(mock_client, "add_alarm_assignee", return_value={"is_success": True}) result = add_assignee_command(mock_client, {"alarm_id": "1", "user_emails": "user@example.com"}) assert "Assignee added" in result.readable_output def test_add_assignee_missing_emails(mock_client): from SOCRadarIncidentsV4 import add_assignee_command with pytest.raises(ValueError, match="user_emails is required"): add_assignee_command(mock_client, {"alarm_id": "1", "user_emails": ""}) def test_add_tag_success(mock_client, mocker): from SOCRadarIncidentsV4 import add_tag_command mocker.patch.object(mock_client, "add_remove_tag", return_value={"is_success": True}) result = add_tag_command(mock_client, {"alarm_id": "1", "tag": "critical"}) assert "critical" in result.readable_output def test_add_tag_missing_tag(mock_client): from SOCRadarIncidentsV4 import add_tag_command with pytest.raises(ValueError, match="tag is required"): add_tag_command(mock_client, {"alarm_id": "1", "tag": ""}) # --------------------------------------------------------------------------- # test_fetch_command # --------------------------------------------------------------------------- def test_test_fetch_success(mock_client, mocker): from SOCRadarIncidentsV4 import test_fetch_command mocker.patch.object( mock_client, "search_incidents", return_value=_mock_response([_make_alarm()], total_records=1, total_pages=1), ) result = test_fetch_command(mock_client, {"first_fetch": "3 days", "limit": "5"}) assert "Found" in result.readable_output def test_test_fetch_no_incidents(mock_client, mocker): from SOCRadarIncidentsV4 import test_fetch_command mocker.patch.object(mock_client, "search_incidents", return_value=_mock_response([])) result = test_fetch_command(mock_client, {"first_fetch": "3 days", "limit": "5"}) assert "No incidents found" in result.readable_output def test_client_search_incidents_error_response(mock_client, mocker, capfd): """Test that API error response raises DemistoException""" from CommonServerPython import DemistoException mocker.patch.object( mock_client, "_http_request", return_value={ "is_success": False, "message": "Invalid request", }, ) with capfd.disabled(), pytest.raises(DemistoException, match="API Error"): mock_client.search_incidents(limit=1, page=1) # --------------------------------------------------------------------------- # format_value # --------------------------------------------------------------------------- def test_format_value_dict(): from SOCRadarIncidentsV4 import format_value result = format_value({"key1": "val1", "key2": "val2"}) assert "key1: val1" in result assert "key2: val2" in result def test_format_value_nested_dict(): from SOCRadarIncidentsV4 import format_value result = format_value({"parent": {"child": "value"}}) assert "parent:" in result assert " child: value" in result def test_format_value_list(): from SOCRadarIncidentsV4 import format_value result = format_value(["item1", "item2"]) assert "- item1" in result assert "- item2" in result def test_format_value_list_of_dicts(): from SOCRadarIncidentsV4 import format_value result = format_value([{"k": "v"}]) assert "-" in result[0] assert "k: v" in result[1] def test_format_value_scalar(): from SOCRadarIncidentsV4 import format_value result = format_value("simple_string") assert result == ["simple_string"] def test_format_value_dict_with_list_value(): from SOCRadarIncidentsV4 import format_value result = format_value({"items": ["a", "b"]}) assert "items:" in result assert any("- a" in line for line in result) # --------------------------------------------------------------------------- # alarm_to_incident include_* parameters # --------------------------------------------------------------------------- def test_alarm_to_incident_include_mitigation(): import json from SOCRadarIncidentsV4 import alarm_to_incident alarm = _make_alarm() alarm["alarm_type_details"]["alarm_default_mitigation_plan"] = "Do X and Y" incident = alarm_to_incident(alarm, include_mitigation=True) assert incident["CustomFields"]["socradarmitigation"] == "Do X and Y" raw = json.loads(incident["rawJSON"]) assert raw.get("alarm_mitigation") == "Do X and Y" def test_alarm_to_incident_include_response(): import json from SOCRadarIncidentsV4 import alarm_to_incident alarm = _make_alarm() alarm["alarm_response"] = "Response steps here" incident = alarm_to_incident(alarm, include_response=True) assert incident["CustomFields"]["socradarresponse"] == "Response steps here" raw = json.loads(incident["rawJSON"]) assert raw.get("alarm_response_plan") == "Response steps here" def test_alarm_to_incident_include_detection_and_analysis(): import json from SOCRadarIncidentsV4 import alarm_to_incident alarm = _make_alarm() alarm["alarm_type_details"]["alarm_detection_and_analysis"] = "Detection info" incident = alarm_to_incident(alarm, include_detection_and_analysis=True) assert incident["CustomFields"]["socradardetectionandanalysis"] == "Detection info" raw = json.loads(incident["rawJSON"]) assert raw.get("alarm_detection_and_analysis") == "Detection info" def test_alarm_to_incident_include_post_incident_analysis(): import json from SOCRadarIncidentsV4 import alarm_to_incident alarm = _make_alarm() alarm["alarm_type_details"]["alarm_post_incident_analysis"] = "Post analysis" incident = alarm_to_incident(alarm, include_post_incident_analysis=True) assert incident["CustomFields"]["socradarpostincidentanalysis"] == "Post analysis" raw = json.loads(incident["rawJSON"]) assert raw.get("alarm_post_incident_analysis") == "Post analysis" def test_alarm_to_incident_include_related_assets(): from SOCRadarIncidentsV4 import alarm_to_incident alarm = _make_alarm() alarm["alarm_related_assets"] = [{"key": "domain", "value": ["example.com", "test.com"]}] incident = alarm_to_incident(alarm, include_related_assets=True) assert "domain: example.com || test.com" in incident["CustomFields"]["socradarrelatedassets"] def test_alarm_to_incident_include_related_entities(): from SOCRadarIncidentsV4 import alarm_to_incident alarm = _make_alarm() alarm["alarm_related_entities"] = [{"key": "IP", "value": "1.2.3.4"}] incident = alarm_to_incident(alarm, include_related_entities=True) assert "IP: 1.2.3.4" in incident["CustomFields"]["socradarrelatedentities"] def test_alarm_to_incident_include_company_id(): import json from SOCRadarIncidentsV4 import alarm_to_incident alarm = _make_alarm() # Remove company_id from alarm to test configured_company_id fallback del alarm["company_id"] incident = alarm_to_incident(alarm, include_company_id=True, configured_company_id="789") assert incident["CustomFields"]["socradarcompanyid"] == "789" raw = json.loads(incident["rawJSON"]) assert raw.get("company_id") == "789" def test_alarm_to_incident_include_company_id_from_alarm(): from SOCRadarIncidentsV4 import alarm_to_incident alarm = _make_alarm(company_id="456") incident = alarm_to_incident(alarm, include_company_id=True) assert incident["CustomFields"]["socradarcompanyid"] == "456" def test_alarm_to_incident_show_content_false(): from SOCRadarIncidentsV4 import alarm_to_incident alarm = _make_alarm() alarm["content"] = {"key1": "value1", "key2": "value2"} incident = alarm_to_incident(alarm, show_content=False, include_incident_link=True) assert incident["CustomFields"]["socradarincidentcontent"] == "" def test_alarm_to_incident_show_content_true(): from SOCRadarIncidentsV4 import alarm_to_incident alarm = _make_alarm() alarm["content"] = {"key1": "value1"} incident = alarm_to_incident(alarm, show_content=True, include_incident_link=True) assert "key1: value1" in incident["CustomFields"]["socradarincidentcontent"] def test_alarm_to_incident_non_dict_alarm_type_details(): from SOCRadarIncidentsV4 import alarm_to_incident alarm = _make_alarm() alarm["alarm_type_details"] = "not-a-dict" incident = alarm_to_incident(alarm) assert "Unknown" in incident["name"] def test_alarm_to_incident_non_dict_content(): from SOCRadarIncidentsV4 import alarm_to_incident alarm = _make_alarm() alarm["content"] = "not-a-dict" incident = alarm_to_incident(alarm, show_content=True, include_incident_link=True) assert incident["CustomFields"]["socradarincidentcontent"] == "" def test_alarm_to_incident_non_list_tags(): from SOCRadarIncidentsV4 import alarm_to_incident alarm = _make_alarm() alarm["tags"] = "not-a-list" incident = alarm_to_incident(alarm) assert incident["CustomFields"]["socradartags"] == "" def test_alarm_to_incident_non_list_related_entities(): from SOCRadarIncidentsV4 import alarm_to_incident alarm = _make_alarm() alarm["alarm_related_entities"] = "not-a-list" incident = alarm_to_incident(alarm) # Should not crash assert incident["name"] is not None def test_alarm_to_incident_non_list_related_assets(): from SOCRadarIncidentsV4 import alarm_to_incident alarm = _make_alarm() alarm["alarm_related_assets"] = "not-a-list" incident = alarm_to_incident(alarm) assert incident["name"] is not None def test_alarm_to_incident_no_date(): from SOCRadarIncidentsV4 import alarm_to_incident alarm = _make_alarm() alarm["date"] = None incident = alarm_to_incident(alarm) assert incident["occurred"].endswith("Z") # --------------------------------------------------------------------------- # search_incidents - data as list path # --------------------------------------------------------------------------- def test_search_incidents_data_as_list(mock_client, mocker): """Test that when API returns data as a list (no total_records), it is handled.""" mocker.patch.object( mock_client, "_http_request", return_value={ "is_success": True, "data": [{"alarm_id": 1}, {"alarm_id": 2}], }, ) result = mock_client.search_incidents(limit=10, page=1, include_total_records=False) assert result["total_records"] == 2 assert result["total_pages"] == 1 assert len(result["data"]) == 2 def test_search_incidents_include_company_id(mock_client, mocker): """Test that include_company_id passes the param to the API.""" mock_http = mocker.patch.object( mock_client, "_http_request", return_value={ "is_success": True, "data": {"alarms": [], "total_pages": 1, "total_records": 0}, }, ) mock_client.search_incidents(limit=10, page=1, include_company_id=True) call_kwargs = mock_http.call_args[1] assert call_kwargs["params"]["include_company_id"] == "true" def test_search_incidents_no_include_company_id(mock_client, mocker): """Test that include_company_id=False does not add the param.""" mock_http = mocker.patch.object( mock_client, "_http_request", return_value={ "is_success": True, "data": {"alarms": [], "total_pages": 1, "total_records": 0}, }, ) mock_client.search_incidents(limit=10, page=1, include_company_id=False) call_kwargs = mock_http.call_args[1] assert "include_company_id" not in call_kwargs["params"] # --------------------------------------------------------------------------- # change_alarm_status - update_related_finding_status and email # --------------------------------------------------------------------------- def test_change_alarm_status_with_update_related_and_email(mock_client, mocker): """Test change_alarm_status passes update_related_finding_status and email.""" mock_http = mocker.patch.object( mock_client, "_http_request", return_value={"is_success": True}, ) mock_client.change_alarm_status( [1], "RESOLVED", comments="test", update_related_finding_status=True, email="user@example.com", ) call_kwargs = mock_http.call_args[1] assert call_kwargs["json_data"]["update_related_finding_status"] is True assert call_kwargs["json_data"]["email"] == "user@example.com" def test_change_alarm_status_with_company_id(mock_client, mocker): """Test change_alarm_status uses provided company_id.""" mock_http = mocker.patch.object( mock_client, "_http_request", return_value={"is_success": True}, ) mock_client.change_alarm_status([1], "OPEN", company_id="999") call_kwargs = mock_http.call_args[1] assert "/company/999/" in call_kwargs["url_suffix"] def test_change_alarm_status_no_company_id(mocker): """Test change_alarm_status raises when no company_id available.""" from SOCRadarIncidentsV4 import Client client = Client( base_url="https://platform.socradar.com/api", api_key="test", company_id="", verify=False, proxy=False, ) with pytest.raises(ValueError, match="company_id must be provided"): client.change_alarm_status([1], "OPEN") def test_change_alarm_status_api_error(mock_client, mocker): """Test change_alarm_status raises on API error.""" from CommonServerPython import DemistoException mocker.patch.object( mock_client, "_http_request", return_value={"is_success": False, "message": "Server error"}, ) with pytest.raises(DemistoException, match="API Error"): mock_client.change_alarm_status([1], "RESOLVED") # --------------------------------------------------------------------------- # change_status_command - update_related_finding_status validation # --------------------------------------------------------------------------- def test_change_status_command_requires_email_for_update_related(mock_client, mocker): """Test that email is required when update_related_finding_status is true.""" from SOCRadarIncidentsV4 import change_status_command with pytest.raises(ValueError, match="email"): change_status_command( mock_client, { "alarm_ids": "1", "status_reason": "RESOLVED", "update_related_finding_status": "true", }, ) def test_change_status_command_with_update_related_and_email(mock_client, mocker): """Test change_status_command passes update_related and email correctly.""" from SOCRadarIncidentsV4 import change_status_command mocker.patch.object(mock_client, "change_alarm_status", return_value={"is_success": True}) result = change_status_command( mock_client, { "alarm_ids": "1,2", "status_reason": "RESOLVED", "update_related_finding_status": "true", "email": "user@test.com", }, ) assert "Status changed" in result.readable_output # --------------------------------------------------------------------------- # fetch_incidents include parameters pass-through # --------------------------------------------------------------------------- def test_fetch_incidents_passes_include_params(mock_client, mocker): """Test that include_* params are passed to alarm_to_incident.""" from SOCRadarIncidentsV4 import fetch_incidents alarm = _make_alarm() alarm["alarm_type_details"]["alarm_default_mitigation_plan"] = "Mitigation plan" alarm["alarm_response"] = "Response plan" mocker.patch.object(mock_client, "search_incidents", return_value=_mock_response([alarm])) next_run, incidents = fetch_incidents( client=mock_client, max_results=100, last_run={}, first_fetch_time="3 days", include_mitigation=True, include_response=True, include_company_id=True, configured_company_id="999", ) assert len(incidents) == 1 cf = incidents[0]["CustomFields"] assert cf.get("socradarmitigation") == "Mitigation plan" assert cf.get("socradarresponse") == "Response plan" assert cf.get("socradarcompanyid") is not None # --------------------------------------------------------------------------- # parse_alarm_date - additional format # --------------------------------------------------------------------------- def test_parse_alarm_date_space_format(): from SOCRadarIncidentsV4 import parse_alarm_date result = parse_alarm_date("2024-01-15 10:30:00") assert result is not None assert result.year == 2024 assert result.month == 1 assert result.day == 15 # --------------------------------------------------------------------------- # test_module edge cases # --------------------------------------------------------------------------- def test_test_module_unexpected_exception(mock_client, mocker, capfd): from SOCRadarIncidentsV4 import test_module mocker.patch.object(mock_client, "search_incidents", side_effect=RuntimeError("unexpected")) with capfd.disabled(): result = test_module(mock_client) assert "Unexpected error" in result # --------------------------------------------------------------------------- # --------------------------------------------------------------------------- # Client methods - missing company_id # --------------------------------------------------------------------------- def test_add_alarm_comment_no_company_id(mocker): from SOCRadarIncidentsV4 import Client client = Client( base_url="https://test.com", api_key="k", company_id="", verify=False, proxy=False, ) with pytest.raises(ValueError, match="company_id"): client.add_alarm_comment(1, "user@test.com", "comment") def test_add_alarm_assignee_no_company_id(mocker): from SOCRadarIncidentsV4 import Client client = Client( base_url="https://test.com", api_key="k", company_id="", verify=False, proxy=False, ) with pytest.raises(ValueError, match="company_id"): client.add_alarm_assignee(1, user_emails=["user@test.com"]) def test_add_remove_tag_no_company_id(mocker): from SOCRadarIncidentsV4 import Client client = Client( base_url="https://test.com", api_key="k", company_id="", verify=False, proxy=False, ) with pytest.raises(ValueError, match="company_id"): client.add_remove_tag(1, "tag")