Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DBotAverageScore] Migrate to Python, and fix '0' average causing a null value #28030

Merged
merged 16 commits into from Jul 11, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 9 additions & 0 deletions Packs/CommonScripts/ReleaseNotes/1_11_97.md
@@ -0,0 +1,9 @@

#### Scripts

##### DBotAverageScore

- Migrated the script from JavaScript to Python.
- Fixed an issue where if all scores of an indicator are '0', a null value would be returned.
- Updated the script to ignore '0' values (which indicate an 'unknown' value) from average calculations.
- Updated the Docker image to: *demisto/python3:3.10.12.63474*.
16 changes: 0 additions & 16 deletions Packs/CommonScripts/Scripts/DBotAverageScore/DBotAverageScore.js

This file was deleted.

69 changes: 69 additions & 0 deletions Packs/CommonScripts/Scripts/DBotAverageScore/DBotAverageScore.py
@@ -0,0 +1,69 @@
import demistomock as demisto
from CommonServerPython import *

from collections import defaultdict


def calculate_all_average_scores(context_data: list[dict[str, Any]]) -> CommandResults:
"""
Calculates the average score for each indicator in the context, and returns the results.

Args:
context_data (dict): 'DBotScore' context data, containing DBotScore entries to calculate the average for.

Returns:
CommandResults: A CommandResults object containing command's outputs.
"""
scores = defaultdict(list) # Format is 'indicator: [collected scores]'

for dbot_score_item in context_data:
indicator = dbot_score_item['Indicator']

scores[indicator].append(dbot_score_item['Score'])

context_output = []

for indicator, scores_list in scores.items():
context_output.append(calculate_average_score(indicator=indicator, scores_list=scores_list))

return CommandResults(
outputs_prefix='DBotAvgScore',
outputs_key_field='Indicator',
outputs=context_output,
readable_output=tableToMarkdown('DBot Average Scores', t=context_output),
)


def calculate_average_score(indicator: str, scores_list: list[int]) -> dict:
MichaelYochpaz marked this conversation as resolved.
Show resolved Hide resolved
"""
Calculates the average score of a list of scores, and return a context entry with the average.
'0' values are ignored (since they indicate "unknown" score).
If no scores are provided, the average is 0.

Args:
indicator (str): The indicator for which the average is calculated.
scores_list (list[int]): A list of scores.

Returns:
dict: A context entry for a single indicator, containing the average score.
"""
scores_list = [score for score in scores_list if score != 0] # Remove '0' values

if not scores_list: # If all values were '0', we have an empty list
return {'Indicator': indicator, 'Score': 0}

return {'Indicator': indicator, 'Score': sum(scores_list) / len(scores_list)}


def main(): # pragma: no cover
dbot_score_context_data: list | dict = demisto.context().get('DBotScore', [])

# If there is only a single DBotScore entry, the server returns it as a single dict, and not inside a list.
if isinstance(dbot_score_context_data, dict):
dbot_score_context_data = [dbot_score_context_data]

return_results(calculate_all_average_scores(dbot_score_context_data))


if __name__ in ("__main__", "builtin", "builtins"): # pragma: no cover
main()
Expand Up @@ -3,17 +3,18 @@ commonfields:
version: -1
name: DBotAverageScore
script: ''
type: javascript
comment: Calculates average score for each indicator from context
type: python
comment: The script calculates the average DBot score for each indicator in the context.
tags:
- Utility
outputs:
- contextPath: DBotAvgScore.Indicator
description: The indicator we calculate average score for
description: The indicator the average score is for
MichaelYochpaz marked this conversation as resolved.
Show resolved Hide resolved
- contextPath: DBotAvgScore.Score
description: The average score per indicator
description: The average score for the indicator
MichaelYochpaz marked this conversation as resolved.
Show resolved Hide resolved
enabled: true
scripttarget: 0
subtype: python3
dockerimage: demisto/python3:3.10.12.63474
fromversion: 5.0.0
tests:
- DbotAverageScore-Test
@@ -0,0 +1,56 @@
from CommonServerPython import *

from pathlib import Path

import pytest

from DBotAverageScore import *


def load_test_data(json_file_name: str) -> list | dict:
"""
Loads test data from a JSON file.
"""
with open(Path('test_data', json_file_name)) as json_file:
return json.load(json_file)


SAMPLE_DATA = load_test_data('sample_data.json')


@pytest.mark.parametrize('indicator, scores_list, expected_average', [
('192.0.2.0', [1, 2, 3], 2), # General check
('192.0.2.1', [0, 0, 0], 0), # Assure '0' is returned when all scores are '0'
('192.0.2.2', [1, 1, 0, 0], 1), # Assure '0' values are ignored
('192.0.2.3', [1, 2, 3, 4], 2.5), # Assure float value is returned
])
def test_calculate_average_score(indicator: str, scores_list: list[int], expected_average: float):
"""
Given:
An indicator and a list of scores.
When:
Creating a context entry with the average score.
Then:
Ensure the average and context entry are valid and correct.
"""
assert calculate_average_score(indicator, scores_list) == {'Indicator': indicator, 'Score': expected_average}


@pytest.mark.parametrize('context_data, expected_context_output, expected_readable_output', [
(SAMPLE_DATA['context_data'], SAMPLE_DATA['expected_context_output'], SAMPLE_DATA['expected_readable_output']),
])
def test_calculate_all_average_scores(context_data: list[dict[str, Any]],
expected_context_output: dict, expected_readable_output: str):
"""
Given:
A list of DBotScore context entries.
When:
Calculating the average score for each indicator using 'calculate_all_average_scores' function.
Then:
Ensure the context and readable outputs are valid and correct.
"""
results = calculate_all_average_scores(context_data)
assert results.outputs_prefix == 'DBotAvgScore'
assert results.outputs_key_field == 'Indicator'
assert results.outputs == expected_context_output
assert results.readable_output == expected_readable_output
17 changes: 12 additions & 5 deletions Packs/CommonScripts/Scripts/DBotAverageScore/README.md
@@ -1,22 +1,29 @@
Calculates the average score for each indicator from context.
The script calculates the average DBot score for each indicator in the context.
The script will ignore '0' scores (which are for an 'unknown' reputation).
If all scores for an indicator are '0', the indicator will receive a score of '0'.

For more information regarding DBot Scores, refer to the official ["Reputation and DBot Score" documentation](https://xsoar.pan.dev/docs/integrations/dbot).

## Script Data

---

| **Name** | **Description** |
| --- | --- |
| Script Type | javascript |
| Script Type | python3 |
| Tags | Utility |

| Cortex XSOAR Version | 5.0.0 |

## Inputs

---
There are no inputs for this script.

## Outputs

---

| **Path** | **Description** | **Type** |
| --- | --- | --- |
| DBotAvgScore.Indicator | The indicator that the average score was calculated for. | Unknown |
| DBotAvgScore.Score | The average score per indicator. | Unknown |
| DBotAvgScore.Indicator | The indicator the average score is for | Unknown |
| DBotAvgScore.Score | The average score for the indicator | Unknown |
MichaelYochpaz marked this conversation as resolved.
Show resolved Hide resolved
@@ -0,0 +1,40 @@
{
"context_data": [
{"Indicator": "192.0.2.0", "Reliability": "A - Completely reliable", "Score": 1, "Vendor": "Vendor1"},
{"Indicator": "192.0.2.0", "Reliability": "B - Usually reliable", "Score": 2, "Vendor": "Vendor2"},
{"Indicator": "192.0.2.0", "Reliability": "C - Fairly reliable", "Score": 3, "Vendor": "Vendor3"},

{"Indicator": "192.0.2.1", "Reliability": "A - Completely reliable", "Score": 0, "Vendor": "Vendor1"},
{"Indicator": "192.0.2.1", "Reliability": "B - Usually reliable", "Score": 0, "Vendor": "Vendor2"},
{"Indicator": "192.0.2.1", "Reliability": "C - Fairly reliable", "Score": 0, "Vendor": "Vendor3"},

{"Indicator": "192.0.2.2", "Reliability": "A - Completely reliable", "Score": 1, "Vendor": "Vendor1"},
{"Indicator": "192.0.2.2", "Reliability": "B - Usually reliable", "Score": 1, "Vendor": "Vendor2"},
{"Indicator": "192.0.2.2", "Reliability": "C - Fairly reliable", "Score": 0, "Vendor": "Vendor3"},
{"Indicator": "192.0.2.2", "Reliability": "D - Not usually reliable", "Score": 0, "Vendor": "Vendor4"},

{"Indicator": "192.0.2.3", "Reliability": "A - Completely reliable", "Score": 1, "Vendor": "Vendor1"},
{"Indicator": "192.0.2.3", "Reliability": "B - Usually reliable", "Score": 2, "Vendor": "Vendor2"},
{"Indicator": "192.0.2.3", "Reliability": "C - Fairly reliable", "Score": 3, "Vendor": "Vendor3"},
{"Indicator": "192.0.2.3", "Reliability": "D - Not usually reliable", "Score": 4, "Vendor": "Vendor4"}
],
"expected_context_output": [
{
"Indicator": "192.0.2.0",
"Score": 2.0
},
{
"Indicator": "192.0.2.1",
"Score": 0
},
{
"Indicator": "192.0.2.2",
"Score": 1.0
},
{
"Indicator": "192.0.2.3",
"Score": 2.5
}
],
"expected_readable_output": "### DBot Average Scores\n|Indicator|Score|\n|---|---|\n| 192.0.2.0 | 2.0 |\n| 192.0.2.1 | 0 |\n| 192.0.2.2 | 1.0 |\n| 192.0.2.3 | 2.5 |\n"
}
2 changes: 1 addition & 1 deletion Packs/CommonScripts/pack_metadata.json
Expand Up @@ -2,7 +2,7 @@
"name": "Common Scripts",
"description": "Frequently used scripts pack.",
"support": "xsoar",
"currentVersion": "1.11.96",
"currentVersion": "1.11.97",
"author": "Cortex XSOAR",
"url": "https://www.paloaltonetworks.com/cortex",
"email": "",
Expand Down
3 changes: 2 additions & 1 deletion Tests/known_words.txt
Expand Up @@ -239,4 +239,5 @@ GCP
Qradar
XSIAM
fromversion
xpanse
xpanse
JavaScript