diff --git a/Packs/Active_Directory_Query/.pack-ignore b/Packs/Active_Directory_Query/.pack-ignore index d8b78692a304..7f52ba9141b7 100644 --- a/Packs/Active_Directory_Query/.pack-ignore +++ b/Packs/Active_Directory_Query/.pack-ignore @@ -18,4 +18,4 @@ ldap tls useraccountcontrol zipprotectwithpassword - +cn diff --git a/Packs/Active_Directory_Query/Integrations/Active_Directory_Query/Active_Directory_Query.py b/Packs/Active_Directory_Query/Integrations/Active_Directory_Query/Active_Directory_Query.py index e6ccbf6188f4..bc771d933cc7 100644 --- a/Packs/Active_Directory_Query/Integrations/Active_Directory_Query/Active_Directory_Query.py +++ b/Packs/Active_Directory_Query/Integrations/Active_Directory_Query/Active_Directory_Query.py @@ -1597,7 +1597,7 @@ def add_member_to_group(default_base_dn): if not success: raise Exception("Failed to add {} to group {}".format( args.get('username') or args.get('computer-name'), - args.get('group_name') + args.get('group-cn') )) demisto_entry = { @@ -1634,7 +1634,7 @@ def remove_member_from_group(default_base_dn): if not success: raise Exception("Failed to remove {} from group {}".format( args.get('username') or args.get('computer-name'), - args.get('group_name') + args.get('group-cn') )) demisto_entry = { diff --git a/Packs/Active_Directory_Query/ReleaseNotes/1_6_33.md b/Packs/Active_Directory_Query/ReleaseNotes/1_6_33.md new file mode 100644 index 000000000000..a5bc19a27f6b --- /dev/null +++ b/Packs/Active_Directory_Query/ReleaseNotes/1_6_33.md @@ -0,0 +1,3 @@ +#### Integrations +##### Active Directory Query v2 +- Fixed an issue where *group-cn* argument should have been used for error message in ***ad-add-to-group*** and ***ad-remove-from-group*** commands. diff --git a/Packs/Active_Directory_Query/TestPlaybooks/Active_Directory_-_manual_pagination_check.yml b/Packs/Active_Directory_Query/TestPlaybooks/Active_Directory_-_manual_pagination_check.yml index ae1ab22e6ecd..0736b2d0c89a 100644 --- a/Packs/Active_Directory_Query/TestPlaybooks/Active_Directory_-_manual_pagination_check.yml +++ b/Packs/Active_Directory_Query/TestPlaybooks/Active_Directory_-_manual_pagination_check.yml @@ -312,6 +312,9 @@ tasks: type: regular iscommand: false brand: "" + scriptarguments: + message: + simple: '"page-cookie is not set currently, or it returns no data" ' separatecontext: false view: |- { @@ -347,7 +350,7 @@ tasks: conditions: - label: "yes" condition: - - - operator: isEqualNumber + - - operator: greaterThanOrEqual left: value: complex: @@ -357,7 +360,7 @@ tasks: iscontext: true right: value: - simple: "2" + simple: "1" view: |- { "position": { @@ -965,7 +968,7 @@ tasks: conditions: - label: "yes" condition: - - - operator: isEqualNumber + - - operator: greaterThanOrEqual left: value: complex: @@ -976,7 +979,7 @@ tasks: iscontext: true right: value: - simple: "2" + simple: "1" view: |- { "position": { diff --git a/Packs/Active_Directory_Query/pack_metadata.json b/Packs/Active_Directory_Query/pack_metadata.json index c349bfdb8a03..a809b2db391c 100644 --- a/Packs/Active_Directory_Query/pack_metadata.json +++ b/Packs/Active_Directory_Query/pack_metadata.json @@ -2,7 +2,7 @@ "name": "Active Directory Query", "description": "Active Directory Query integration enables you to access and manage Active Directory objects (users, contacts, and computers).", "support": "xsoar", - "currentVersion": "1.6.32", + "currentVersion": "1.6.33", "author": "Cortex XSOAR", "url": "", "email": "", diff --git a/Packs/CortexXDR/Integrations/CortexXDRIR/CortexXDRIR.py b/Packs/CortexXDR/Integrations/CortexXDRIR/CortexXDRIR.py index a947eb47b383..0abd5f7093a8 100644 --- a/Packs/CortexXDR/Integrations/CortexXDRIR/CortexXDRIR.py +++ b/Packs/CortexXDR/Integrations/CortexXDRIR/CortexXDRIR.py @@ -432,8 +432,8 @@ def get_multiple_incidents_extra_data(self, incident_id_list=[], fields_to_exclu def get_headers(params: dict) -> dict: - api_key = params.get('apikey') or params.get('apikey_creds', {}).get('password', '') - api_key_id = params.get('apikey_id') or params.get('apikey_id_creds', {}).get('password', '') + api_key = params.get('apikey_creds', {}).get('password', '') or params.get('apikey', '') + api_key_id = params.get('apikey_id_creds', {}).get('password', '') or params.get('apikey_id') nonce: str = "".join([secrets.choice(string.ascii_letters + string.digits) for _ in range(64)]) timestamp: str = str(int(datetime.now(timezone.utc).timestamp()) * 1000) auth_key = f"{api_key}{nonce}{timestamp}" diff --git a/Packs/CortexXDR/Integrations/CortexXDRIR/CortexXDRIR.yml b/Packs/CortexXDR/Integrations/CortexXDRIR/CortexXDRIR.yml index 78ea0267d8ea..d29dd02dc786 100644 --- a/Packs/CortexXDR/Integrations/CortexXDRIR/CortexXDRIR.yml +++ b/Packs/CortexXDR/Integrations/CortexXDRIR/CortexXDRIR.yml @@ -3492,7 +3492,7 @@ script: isArray: true name: xdr-remove-user-role description: Remove one or more users from a role. - dockerimage: demisto/python3:3.10.14.90585 + dockerimage: demisto/python3:3.10.14.91134 isfetch: true isfetch:xpanse: false script: '' diff --git a/Packs/CortexXDR/ReleaseNotes/6_1_26.md b/Packs/CortexXDR/ReleaseNotes/6_1_26.md new file mode 100644 index 000000000000..5593024032af --- /dev/null +++ b/Packs/CortexXDR/ReleaseNotes/6_1_26.md @@ -0,0 +1,7 @@ + +#### Integrations + +##### Palo Alto Networks Cortex XDR - Investigation and Response + +- Fixed an issue where authentication failed due to deprecated authentication configuration. +- Updated the Docker image to: *demisto/python3.10.14.91134*. \ No newline at end of file diff --git a/Packs/CortexXDR/pack_metadata.json b/Packs/CortexXDR/pack_metadata.json index 5a8e7b76afdc..2107e134d18e 100644 --- a/Packs/CortexXDR/pack_metadata.json +++ b/Packs/CortexXDR/pack_metadata.json @@ -2,7 +2,7 @@ "name": "Cortex XDR by Palo Alto Networks", "description": "Automates Cortex XDR incident response, and includes custom Cortex XDR incident views and layouts to aid analyst investigations.", "support": "xsoar", - "currentVersion": "6.1.25", + "currentVersion": "6.1.26", "author": "Cortex XSOAR", "url": "https://www.paloaltonetworks.com/cortex", "email": "", diff --git a/Packs/GoogleDrive/Integrations/GoogleDrive/GoogleDrive_description.md b/Packs/GoogleDrive/Integrations/GoogleDrive/GoogleDrive_description.md index 115b5649c49d..b647f861691f 100644 --- a/Packs/GoogleDrive/Integrations/GoogleDrive/GoogleDrive_description.md +++ b/Packs/GoogleDrive/Integrations/GoogleDrive/GoogleDrive_description.md @@ -15,3 +15,50 @@ Provide at least one of the following scopes for each command. * https://www.googleapis.com/auth/drive.appdata * https://www.googleapis.com/auth/drive.metadata * https://www.googleapis.com/auth/drive.photos.readonly + + +* ***google-drive-drives-list*** +* ***google-drive-drive-get*** +* ***google-drive-drive-delete*** + * https://www.googleapis.com/auth/drive + * https://www.googleapis.com/auth/drive.readonly + +* ***google-drive-files-list*** +* ***google-drive-file-get*** +* ***google-drive-file-upload*** +* ***google-drive-file-copy*** +* ***google-drive-file-get-parents*** + * https://www.googleapis.com/auth/drive + * https://www.googleapis.com/auth/drive.file + * https://www.googleapis.com/auth/drive.readonly + * https://www.googleapis.com/auth/drive.metadata.readonly + * https://www.googleapis.com/auth/drive.appdata + * https://www.googleapis.com/auth/drive.metadata + * https://www.googleapis.com/auth/drive.photos.readonly + + +* ***google-drive-file-delete*** + * https://www.googleapis.com/auth/drive + * https://www.googleapis.com/auth/drive.file + * https://www.googleapis.com/auth/drive.appdata + +* ***google-drive-file-permissions-list*** + * https://www.googleapis.com/auth/drive + * https://www.googleapis.com/auth/drive.file + * https://www.googleapis.com/auth/drive.readonly + * https://www.googleapis.com/auth/drive.metadata.readonly + * https://www.googleapis.com/auth/drive.metadata + * https://www.googleapis.com/auth/drive.photos.readonly + +* ***google-drive-file-permission-create*** +* ***google-drive-file-permission-update*** +* ***google-drive-file-permission-delete*** + * https://www.googleapis.com/auth/drive + * https://www.googleapis.com/auth/drive.file + +* ***google-drive-file-modify-label*** +* ***google-drive-get-labels*** +* ***google-drive-get-file-labels*** + * https://www.googleapis.com/auth/drive + * https://www.googleapis.com/auth/drive.labels + diff --git a/Packs/GoogleDrive/ReleaseNotes/1_3_6.md b/Packs/GoogleDrive/ReleaseNotes/1_3_6.md new file mode 100644 index 000000000000..d3d2deb21864 --- /dev/null +++ b/Packs/GoogleDrive/ReleaseNotes/1_3_6.md @@ -0,0 +1,6 @@ + +#### Integrations + +##### Google Drive + +Documentation and metadata improvements. diff --git a/Packs/GoogleDrive/pack_metadata.json b/Packs/GoogleDrive/pack_metadata.json index 60407559c723..cd644c13d3f6 100644 --- a/Packs/GoogleDrive/pack_metadata.json +++ b/Packs/GoogleDrive/pack_metadata.json @@ -2,7 +2,7 @@ "name": "Google Drive", "description": "Google Drive allows users to store files on their servers, synchronize files across devices, and share files. This integration helps you to create a new drive, query past activity and view change logs performed by the users, as well as list drives and files, and manage their permissions.", "support": "xsoar", - "currentVersion": "1.3.5", + "currentVersion": "1.3.6", "author": "Cortex XSOAR", "url": "https://www.paloaltonetworks.com/cortex", "email": "", diff --git a/Packs/NetscoutAED/Author_image.png b/Packs/NetscoutAED/Author_image.png new file mode 100644 index 000000000000..7f12ca9d2fec Binary files /dev/null and b/Packs/NetscoutAED/Author_image.png differ diff --git a/Packs/NetscoutAED/README.md b/Packs/NetscoutAED/README.md index 9d85b4cb63ca..db332356f440 100644 --- a/Packs/NetscoutAED/README.md +++ b/Packs/NetscoutAED/README.md @@ -1,3 +1,5 @@ +Note: Support for this Pack was moved to Partner starting MArch 31st, 2023. In case of any issues arise, please contact the Partner directly at supportmanagement@netscout.com. + The Netscout Arbor Edge Defense (AED) integration enables you to block and allow outbound and inbound traffic. ## What does this pack do? diff --git a/Packs/NetscoutAED/ReleaseNotes/1_0_26.md b/Packs/NetscoutAED/ReleaseNotes/1_0_26.md new file mode 100644 index 000000000000..627b6ed27037 --- /dev/null +++ b/Packs/NetscoutAED/ReleaseNotes/1_0_26.md @@ -0,0 +1,5 @@ +#### Integrations + +##### Netscout Arbor Edge Defense +- Finished adoption process. + diff --git a/Packs/NetscoutAED/pack_metadata.json b/Packs/NetscoutAED/pack_metadata.json index 46dada37ee77..c026c49bb67b 100644 --- a/Packs/NetscoutAED/pack_metadata.json +++ b/Packs/NetscoutAED/pack_metadata.json @@ -1,11 +1,11 @@ { "name": "Netscout Arbor Edge Defense - AED", "description": "Use the Netscout Arbor Edge Defense integration to detect and stop both inbound threats and outbound malicious communication from compromised internal devices.", - "support": "xsoar", - "currentVersion": "1.0.25", - "author": "Cortex XSOAR", - "url": "https://www.paloaltonetworks.com/cortex", - "email": "", + "support": "partner", + "currentVersion": "1.0.26", + "author": "Netscout", + "url": "https://www.netscout.com/product/arbor-aed-aem", + "email": "supportmanagement@netscout.com", "categories": [ "Network Security" ], @@ -16,4 +16,4 @@ "xsoar", "marketplacev2" ] -} \ No newline at end of file +} diff --git a/Packs/NetscoutArborSightline/Author_image.png b/Packs/NetscoutArborSightline/Author_image.png new file mode 100644 index 000000000000..7f12ca9d2fec Binary files /dev/null and b/Packs/NetscoutArborSightline/Author_image.png differ diff --git a/Packs/NetscoutArborSightline/README.md b/Packs/NetscoutArborSightline/README.md index 1c148fe881b3..3bb0cf2dfdcb 100644 --- a/Packs/NetscoutArborSightline/README.md +++ b/Packs/NetscoutArborSightline/README.md @@ -1,3 +1,5 @@ +Note: Support for this Pack will be moved to Partner starting February 26, 2024. + Arbor Sightline provides network visibility and reporting capabilities to help you identify and manage the mitigation of threats to your network. diff --git a/Packs/NetscoutArborSightline/ReleaseNotes/1_0_18.md b/Packs/NetscoutArborSightline/ReleaseNotes/1_0_18.md new file mode 100644 index 000000000000..c83ed94aaa5a --- /dev/null +++ b/Packs/NetscoutArborSightline/ReleaseNotes/1_0_18.md @@ -0,0 +1,5 @@ +#### Integrations + +##### Netscout Arbor Sightline (Peakflow) +- Finished adoption process. + diff --git a/Packs/NetscoutArborSightline/pack_metadata.json b/Packs/NetscoutArborSightline/pack_metadata.json index f5f8750b2a14..3d1eb793fc7e 100644 --- a/Packs/NetscoutArborSightline/pack_metadata.json +++ b/Packs/NetscoutArborSightline/pack_metadata.json @@ -1,11 +1,11 @@ { "name": "Netscout Arbor Sightline", "description": "Identify Potential Network Outages & Gain Business Insights to Solve Your Problems", - "support": "xsoar", - "currentVersion": "1.0.17", - "author": "Cortex XSOAR", - "url": "https://www.paloaltonetworks.com/cortex", - "email": "", + "support": "partner", + "currentVersion": "1.0.18", + "author": "Netscout", + "url": "https://www.netscout.com/product/arbor-sightline", + "email": "supportmanagement@netscout.com", "created": "2021-01-13T15:10:51Z", "categories": [ "Network Security" @@ -17,4 +17,4 @@ "xsoar", "marketplacev2" ] -} \ No newline at end of file +} diff --git a/Packs/Netskope/Integrations/NetskopeAPIv2/NetskopeAPIv2.py b/Packs/Netskope/Integrations/NetskopeAPIv2/NetskopeAPIv2.py index 796a8f0bf51b..abb4da70f9dd 100644 --- a/Packs/Netskope/Integrations/NetskopeAPIv2/NetskopeAPIv2.py +++ b/Packs/Netskope/Integrations/NetskopeAPIv2/NetskopeAPIv2.py @@ -1,10 +1,10 @@ +import demistomock as demisto # noqa: F401 +from CommonServerPython import * # noqa: F401 import copy from http import HTTPStatus from typing import Any, NamedTuple from collections.abc import Callable -import demistomock as demisto # noqa: F401 -from CommonServerPython import * # noqa: F401 MIN_PAGE_NUM = 1 MAX_PAGE_SIZE = 50 @@ -253,6 +253,31 @@ def update_url_list( "PUT", f"api/v2/policy/urllist/{url_list_id}", json_data=data ) + def patch_url_list( + self, + url_list_id: str, + urls: list[str] = None, + list_type: str = None, + ) -> dict[str, Any]: + """Update the given URL list. + + Args: + url_list_id (str): URL list ID. + urls (List[str]): URL lists. + list_type (str): URL list type. + + Returns: + Dict[str, Any]: API response from Netskope. + """ + + data = { + "data": {"type": list_type, "urls": urls} + } + + return self._http_request( + "PATCH", f"api/v2/policy/urllist/{url_list_id}/append", json_data=data + ) + def create_url_list( self, name: str, @@ -547,6 +572,48 @@ def update_url_list_command( ) +def add_url_list_command( + client: Client, + args: dict[str, Any], +) -> CommandResults: + """Update URL List. + + Args: + client (Client): Netskope API client. + args (Dict[str, Any]): command arguments. + + Returns: + CommandResults: outputs, readable outputs and raw response for XSOAR. + """ + url_list_id = args["url_list_id"] + urls = argToList(args.get("urls")) + list_type = args.get("list_type", '').lower() or None + + response = client.patch_url_list( + url_list_id, + urls, + list_type + ) + + deploy_url_list_if_required(args, client.deploy_url_list) + output = get_updated_url_list(response) + + readable_output = tableToMarkdown( + name="URL list was updated successfully", + t=remove_empty_elements(output), + headers=URL_HEADER, + headerTransform=string_to_table_header, + ) + + return CommandResults( + readable_output=readable_output, + outputs_prefix="Netskope.URLList", + outputs_key_field="id", + outputs=output, + raw_response=response, + ) + + def create_url_list_command( client: Client, args: dict[str, Any], @@ -1182,7 +1249,7 @@ def optional_arg_to_boolean(arg: str | bool | None) -> bool | None: return argToBoolean(arg) if arg is not None else None -def main() -> None: +def main() -> None: # pragma: no cover params: dict[str, Any] = demisto.params() args: dict[str, Any] = demisto.args() @@ -1210,6 +1277,7 @@ def main() -> None: "netskope-url-lists-list": lists_url_list_command, "netskope-url-list-delete": delete_url_list_command, "netskope-client-list": list_client_command, + "netskope-url-list-add": add_url_list_command } if command == "test-module": diff --git a/Packs/Netskope/Integrations/NetskopeAPIv2/NetskopeAPIv2.yml b/Packs/Netskope/Integrations/NetskopeAPIv2/NetskopeAPIv2.yml index 9320a9fa6769..82e2c39c32f8 100644 --- a/Packs/Netskope/Integrations/NetskopeAPIv2/NetskopeAPIv2.yml +++ b/Packs/Netskope/Integrations/NetskopeAPIv2/NetskopeAPIv2.yml @@ -24,6 +24,7 @@ configuration: required: true hiddenusername: true type: 9 + display: '' - additionalinfo: First alert created date to fetch. e.g., "1 min ago","2 weeks ago","3 months ago". defaultvalue: 3 days display: First fetch timestamp @@ -38,7 +39,7 @@ configuration: required: false - display: Maximum events as incidents per fetch. Max value is 200. name: max_events_fetch - defaultvalue: 50 + defaultvalue: '50' required: false type: 0 - additionalinfo: Fetch events as incidents, in addition to the alerts. @@ -74,6 +75,12 @@ configuration: - display: Fetch incidents name: isFetch type: 8 + required: false +- defaultvalue: '1' + display: Incidents Fetch Interval + name: incidentFetchInterval + required: false + type: 19 description: Netskope API v2 provides a powerful interface for managing and monitoring Netskope deployments. It enables users to retrieve alerts and events, manage URL lists, and control clients. With Netskope API v2, organizations can proactively respond to security threats, enforce web access policies, and efficiently administer their Netskope environment. display: Netskope (API v2) name: netskope_api_v2 @@ -81,31 +88,17 @@ script: commands: - arguments: - description: "Restrict events to those that have dates greater than the provided date string (for example \"YYYY-MM-DDThh:mm\", \"1 min ago\", \"2 weeks ago\"). Note that if this argument is provided, you must also provide the ‘end_time’ argument. " - isArray: false name: start_time - required: false - description: "Restrict events to those that have dates less than or equal to the provided date string (for example \"YYYY-MM-DDThh:mm\", \"1 min ago\", \"2 weeks ago\"). Note that if this argument is provided, you must also provide the ‘start_time’ argument. If start_time argument is provided and this argument is not - the default value will be set for now." - isArray: false name: end_time - required: false - description: "Restrict events to those that were inserted to the system after the provided date string (for example \"YYYY-MM-DDThh:mm\", \"1 min ago\", \"2 weeks ago\"). Note that if this argument is provided, you must also provide the ‘insertion_end_time’ argument. " - isArray: false name: insertion_start_time - required: false - description: "Restrict events to those that were inserted to the system before the provided date string (for example \"YYYY-MM-DDThh:mm\", \"1 min ago\", \"2 weeks ago\"). Note that if this argument is provided, you must also provide the ‘insertion_start_time’ argument. If insertion_start_time argument is provided and this argument is not - the default value will be set for now." - isArray: false name: insertion_end_time - required: false - - default: false - description: 'Free query by which to filter the alerts. For example, "alert_name like test". For more information, visit Netskope documentation: https://docs.netskope.com/en/get-alerts-data.html.' - isArray: false + - description: 'Free query by which to filter the alerts. For example, "alert_name like test". For more information, visit Netskope documentation: https://docs.netskope.com/en/get-alerts-data.html.' name: query - required: false - secret: false - auto: PREDEFINED - default: false description: Select alerts by their type. - isArray: false name: alert_type predefined: - anomaly @@ -120,30 +113,18 @@ script: - quarantine - Remediation - uba - required: false - secret: false - auto: PREDEFINED - default: false description: Whether to retrieve acknowledged alerts or not. - isArray: false name: retrieve_acknowledged predefined: - 'True' - 'False' - required: false - secret: false - description: 'Page number of paginated results. Minimum value: 1.' - isArray: false name: page - required: false - defaultValue: '50' description: The maximum number of records to retrieve. - isArray: false name: limit - required: false - deprecated: false description: Get alerts generated by Netskope. You may choose what alerts to receive with the alert_type parameter. You must provide start_time and end_time, or insertion_start_time and insertion_end_time. (Note that if end_time or insertion_end_time aren't provided. the date time would be set as now.) You cannot provide a combination of the options mentioned above. - execution: false name: netskope-alert-list outputs: - contextPath: Netskope.Alert._appsession_start @@ -475,9 +456,7 @@ script: type: String - arguments: - auto: PREDEFINED - default: false description: Select events by their type. - isArray: false name: event_type predefined: - page @@ -486,41 +465,22 @@ script: - infrastructure - network required: true - secret: false - - default: false - description: 'Free query to filter the events. For example, "app eq Dropbox". For more information, visit Netskope documentation: https://docs.netskope.com/en/get-events-data.html' - isArray: false + - description: 'Free query to filter the events. For example, "app eq Dropbox". For more information, visit Netskope documentation: https://docs.netskope.com/en/get-events-data.html' name: query - required: false - secret: false - description: "Restrict events to those that have dates greater than the provided date string (for example \"YYYY-MM-DDThh:mm\", \"1 min ago\", \"2 weeks ago\"). Note that if this argument is provided, you must also provide the ‘end_time’ argument." - isArray: false name: start_time - required: false - description: "Restrict events to those that have dates less than or equal to the provided date string (for example \"YYYY-MM-DDThh:mm\", \"1 min ago\", \"2 weeks ago\"). Note that if this argument is provided, you must also provide the ‘start_time’ argument. If start_time argument is provided and this argument is not - the default value will be set for now." - isArray: false name: end_time - required: false - description: "Restrict events to those that were inserted to the system after the provided date string (for example \"YYYY-MM-DDThh:mm\", \"1 min ago\", \"2 weeks ago\"). Note that if this argument is provided, you must also provide the ‘insertion_end_time’ argument." - isArray: false name: insertion_start_time - required: false - description: "Restrict events to those that were inserted to the system before the provided date string (for example \"YYYY-MM-DDThh:mm\", \"1 min ago\", \"2 weeks ago\"). Note that if this argument is provided, you must also provide the ‘insertion_start_time’ argument. If insertion_start_time argument is provided and this argument is not - the default value will be set for now." - isArray: false name: insertion_end_time - required: false - description: 'Page number of paginated results. Minimum value: 1.' - isArray: false name: page - required: false - defaultValue: '50' description: The maximum number of records to retrieve. - isArray: false name: limit - required: false - deprecated: false description: Get events extracted from SaaS traffic. You may choose what events to receive with the event_type parameter. You must provide start_time and end_time, or insertion_start_time and insertion_end_time. (Note that if end_time or insertion_end_time aren't provided, the date time would be set as now.) You cannot provide a combination of the options mentioned above. - execution: false name: netskope-event-list outputs: - contextPath: Netskope.Event._appsession_start @@ -786,11 +746,9 @@ script: type: String - arguments: - description: The URL list ID to update (use netskope-url-list-list command to get URL list ID). - isArray: false name: url_list_id required: true - description: The updated URL list name. - isArray: false name: name required: true - description: The updated URL list items (For Exact - Enter URLs like *.example.com, or IP addresses, separated by new line. For Regex - Enter URLs like ^client[0-9]\\.google\\.com , ^app\\.slack\\.com/.*/netskope, or ^google.com, separated by new line). @@ -799,25 +757,19 @@ script: required: true - auto: PREDEFINED description: The updated URL list type. - isArray: false name: list_type predefined: - exact - regex required: true - auto: PREDEFINED - default: false - defaultValue: false + defaultValue: 'false' description: Whether to deploy URL list changes or not. - isArray: false name: deploy predefined: - 'True' - 'False' - required: false - deprecated: false description: Update the URL list with the values provided. Note that this command overrides the list. - execution: false name: netskope-url-list-update outputs: - contextPath: Netskope.URLList.id @@ -849,7 +801,6 @@ script: type: Number - arguments: - description: The unique name for the URL list. - isArray: false name: name required: true - description: The URL list items (For Exact - Enter URLs like *.example.com, or IP addresses, separated by new line. For Regex - Enter URLs like ^client[0-9]\\.google\\.com , ^app\\.slack\\.com/.*/netskope, or ^google.com, separated by new line). @@ -858,25 +809,19 @@ script: required: true - auto: PREDEFINED description: The URL list type. - isArray: false name: list_type predefined: - exact - regex required: true - auto: PREDEFINED - default: false - defaultValue: false + defaultValue: 'false' description: Whether to deploy URL list changes or not. - isArray: false name: deploy predefined: - 'True' - 'False' - required: false - deprecated: false description: Create a new URL list. - execution: false name: netskope-url-list-create outputs: - contextPath: Netskope.URLList.id @@ -908,38 +853,27 @@ script: type: Number - arguments: - description: The URL list ID to get. - isArray: false name: url_list_id - required: false - auto: PREDEFINED description: Get a list of only applied or pending URL lists. - isArray: false name: pending predefined: - applied - pending - required: false - description: 'Comma-separated data values to return in response call (for example: name, id, data, modify_by, modify_time, modify_type, pending). Defaults to all values.' isArray: true name: field - required: false - description: Whether to retrieve all results or not. - isArray: false name: all_results - required: false - defaultValue: false + defaultValue: 'false' auto: PREDEFINED predefined: - 'True' - 'False' - defaultValue: '50' description: The maximum number of records to retrieve. - isArray: false name: limit - required: false - deprecated: false description: Get all URL lists or a specific URL list by specifying the list ID. - execution: false name: netskope-url-lists-list outputs: - contextPath: Netskope.URLList.id @@ -965,22 +899,16 @@ script: type: String - arguments: - description: The URL list ID to delete (use netskope-url-list-list to get the URL list ID). - isArray: false name: url_list_id required: true - auto: PREDEFINED - default: false - defaultValue: false + defaultValue: 'false' description: Whether to deploy URL list changes or not. - isArray: false name: deploy predefined: - 'True' - 'False' - required: false - deprecated: false description: Delete a URL list by the list ID. - execution: false name: netskope-url-list-delete outputs: - contextPath: Netskope.URLList.id @@ -991,21 +919,13 @@ script: type: String - arguments: - description: 'Filter the Netskope user by ''key eq value'' template. For example: userName eq "someUserName" OR externalId eq "User-Ext_id".' - isArray: false name: filter - required: false - description: 'Page number of paginated results. Minimum value: 1.' - isArray: false name: page - required: false - defaultValue: '50' description: The maximum number of records to retrieve. - isArray: false name: limit - required: false - deprecated: false description: Get information about Netskope SCIM users. The command provides a list of users who have been imported into the Netskope tenant through SCIM integration. Users imported through other methods, such as manual CSV import or manual creation, will not be included in the returned results. - execution: false name: netskope-client-list outputs: - contextPath: Netskope.Client.id @@ -1026,13 +946,62 @@ script: - contextPath: Netskope.Client.emails description: Netskope client emails. type: String - dockerimage: demisto/python3:3.10.13.89873 - feed: false + - arguments: + - description: The URL list ID to update (use netskope-url-list-list command to get URL list ID). + name: url_list_id + required: true + - description: The updated URL list items (For Exact - Enter URLs like *.example.com, or IP addresses, separated by new line. For Regex - Enter URLs like ^client[0-9]\\.google\\.com , ^app\\.slack\\.com/.*/netskope, or ^google.com, separated by new line). + isArray: true + name: urls + required: true + - auto: PREDEFINED + description: The updated URL list type. + name: list_type + predefined: + - exact + - regex + required: true + - auto: PREDEFINED + defaultValue: 'false' + description: Whether to deploy URL list changes or not. + name: deploy + predefined: + - 'True' + - 'False' + description: Update the URL list with the values provided. Note that this command appends the list. + name: netskope-url-list-add + outputs: + - contextPath: Netskope.URLList.id + description: Netskope URL list ID. + type: Number + - contextPath: Netskope.URLList.name + description: Netskope URL list name. + type: String + - contextPath: Netskope.URLList.data.urls + description: Netskope URL list data URLs. + type: String + - contextPath: Netskope.URLList.data.type + description: Netskope URL list data type. + type: String + - contextPath: Netskope.URLList.data.json_version + description: Netskope URL list data JSON version. + type: Number + - contextPath: Netskope.URLList.modify_by + description: Netskope URL list modify by. + type: String + - contextPath: Netskope.URLList.modify_time + description: Netskope URL list modify time. + type: Date + - contextPath: Netskope.URLList.modify_type + description: Netskope URL list modify type. + type: String + - contextPath: Netskope.URLList.pending + description: Netskope URL list pending. + type: Number + dockerimage: demisto/python3:3.10.14.90585 isfetch: true - longRunning: false - longRunningPort: false runonce: false - script: '-' + script: '' subtype: python3 type: python fromversion: 6.9.0 diff --git a/Packs/Netskope/Integrations/NetskopeAPIv2/README.md b/Packs/Netskope/Integrations/NetskopeAPIv2/README.md index 2febbfdbc31f..46ef45a5dd05 100644 --- a/Packs/Netskope/Integrations/NetskopeAPIv2/README.md +++ b/Packs/Netskope/Integrations/NetskopeAPIv2/README.md @@ -12,19 +12,21 @@ This integration was integrated and tested with version 2 of the Netskope API. | Server URL | | True | | Use system proxy settings | | False | | Trust any certificate (not secure) | | False | - | API token | Netskope API access token \(make sure to generate token for the following endpoints: api/v2/events/data/application, api/v2/events/data/audit, api/v2/events/data/page, api/v2/events/data/network, api/v2/events/data/infrastructure, api/v2/events/data/alert, api/v2/policy/urllist \(read \+ write\), api/v2/policy/urllist/deploy \(read \+ write\), api/v2/scim/Users\). | True | - | First fetch timestamp | First alert created date to fetch. e.g., "1 min ago","2 weeks ago","3 months ago" | False | + | API token | Netskope API access token \(make sure to generate token for the required endpoints\). | True | + | First fetch timestamp | First alert created date to fetch. e.g., "1 min ago","2 weeks ago","3 months ago". | False | | Maximum incidents per fetch | Maximum number of incidents per fetch. Default is 50. The maximum is 100. | False | | Maximum events as incidents per fetch. Max value is 200. | | False | | Fetch Events | Fetch events as incidents, in addition to the alerts. | False | | Event types to fetch. | The event types to fetch as incidents. | False | - | Alerts Query | Free text query to filter the fetched alerts. | False | - | Events Query | Free text query to filter the fetched events \(if configured\). | False | + | Alerts Query | Free text query to filter the fetched alerts. For more information, visit Netskope documentation \(https://docs.netskope.com/en/get-alerts-data.html\). | False | + | Events Query | Free text query to filter the fetched events \(if configured\). For more information, visit Netskope documentation \(https://docs.netskope.com/en/get-alerts-data.html\). | False | | Incident type | | False | - | Fetch incidents | | | + | Fetch incidents | | False | + | Incidents Fetch Interval | | False | 4. Click **Test** to validate the URLs, token, and connection. + ## Commands You can execute these commands from the Cortex XSOAR CLI, as part of an automation, or in a playbook. @@ -938,3 +940,35 @@ Get information about Netskope SCIM users. The command provides a list of users >|---|---|---|---|---|---| >| 6a4dbb07-f465-4a6c-8af2-1c84ced65010 | upn1 | first_name | last_name | email1@netskope.local | true | >| f8d26597-e4a4-400d-a24b-40318a9e80e5 | upn2 | first_name1 | last_name1 | email11@netskope.local | true | +### netskope-url-list-add + +*** +Update the URL list with the values provided. Note that this command appends the list. + +#### Base Command + +`netskope-url-list-add` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| url_list_id | The URL list ID to update (use netskope-url-list-list command to get URL list ID). | Required | +| urls | The updated URL list items (For Exact - Enter URLs like *.example.com, or IP addresses, separated by new line. For Regex - Enter URLs like ^client[0-9]\\.google\\.com , ^app\\.slack\\.com/.*/netskope, or ^google.com, separated by new line). | Required | +| list_type | The updated URL list type. Possible values are: exact, regex. | Required | +| deploy | Whether to deploy URL list changes or not. Possible values are: True, False. Default is false. | Optional | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| Netskope.URLList.id | Number | Netskope URL list ID. | +| Netskope.URLList.name | String | Netskope URL list name. | +| Netskope.URLList.data.urls | String | Netskope URL list data URLs. | +| Netskope.URLList.data.type | String | Netskope URL list data type. | +| Netskope.URLList.data.json_version | Number | Netskope URL list data JSON version. | +| Netskope.URLList.modify_by | String | Netskope URL list modify by. | +| Netskope.URLList.modify_time | Date | Netskope URL list modify time. | +| Netskope.URLList.modify_type | String | Netskope URL list modify type. | +| Netskope.URLList.pending | Number | Netskope URL list pending. | + diff --git a/Packs/Netskope/ReleaseNotes/3_3_8.md b/Packs/Netskope/ReleaseNotes/3_3_8.md new file mode 100644 index 000000000000..59c2ae73344d --- /dev/null +++ b/Packs/Netskope/ReleaseNotes/3_3_8.md @@ -0,0 +1,6 @@ + +#### Integrations + +##### Netskope (API v2) + +- Added a new command **netskope-url-list-add** to update the URL List with the provided URLs. Note this command will append the values to the URL List. diff --git a/Packs/Netskope/pack_metadata.json b/Packs/Netskope/pack_metadata.json index 7ec12d034bd7..50bf57348104 100644 --- a/Packs/Netskope/pack_metadata.json +++ b/Packs/Netskope/pack_metadata.json @@ -2,7 +2,7 @@ "name": "Netskope", "description": "Cloud access security broker that enables to find, understand, and secure cloud apps.", "support": "xsoar", - "currentVersion": "3.3.7", + "currentVersion": "3.3.8", "author": "Cortex XSOAR", "url": "https://www.paloaltonetworks.com/cortex", "email": "", diff --git a/Packs/Redmine/.secrets-ignore b/Packs/Redmine/.secrets-ignore index e69de29bb2d1..cb3dc92fa017 100644 --- a/Packs/Redmine/.secrets-ignore +++ b/Packs/Redmine/.secrets-ignore @@ -0,0 +1 @@ +https://test \ No newline at end of file diff --git a/Packs/Redmine/Integrations/Redmine/Redmine.py b/Packs/Redmine/Integrations/Redmine/Redmine.py index 7921629c2364..ffdf44483845 100644 --- a/Packs/Redmine/Integrations/Redmine/Redmine.py +++ b/Packs/Redmine/Integrations/Redmine/Redmine.py @@ -187,9 +187,10 @@ def convert_args_to_request_format(args: Dict[str, Any]): if custom_fields := args.pop('custom_fields', None): custom_fields = argToList(custom_fields) try: - args['custom_fields'] = [{'id': field.split(':')[0], 'value': field.split(':')[1]} for field in custom_fields] + args['custom_fields'] = [{'id': field.split(":", 1)[0], 'value': field.split(":", 1)[1]} + for field in custom_fields if field] except Exception as e: - if 'list index out of range' in e.args[0]: + if 'list index out of range' in e.args[0] or 'substring not found' in e.args[0]: raise DemistoException("Custom fields not in format, please follow the instructions") raise @@ -339,10 +340,12 @@ def check_args_validity_and_convert_to_id(status_id: str, tracker_id: str, custo raise DemistoException("Invalid tracker ID, please use only predefined values.") if custom_field: try: - cf_in_format = argToList(custom_field, ':') + cf_in_format = custom_field.split(":", 1) args[f'cf_{cf_in_format[0]}'] = cf_in_format[1] except Exception as e: - raise DemistoException(f"Invalid custom field format, please follow the command description. Error: {e}.") + if 'list index out of range' in e.args[0] or 'substring not found' in e.args[0]: + raise DemistoException(f"Invalid custom field format, please follow the command description. Error: {e}.") + raise return status_id, tracker_id page_number = args.pop('page_number', None) diff --git a/Packs/Redmine/Integrations/Redmine/Redmine_test.py b/Packs/Redmine/Integrations/Redmine/Redmine_test.py index 0ab14ffe2b0d..8f19e64fc2db 100644 --- a/Packs/Redmine/Integrations/Redmine/Redmine_test.py +++ b/Packs/Redmine/Integrations/Redmine/Redmine_test.py @@ -22,14 +22,38 @@ def test_create_issue_command(mocker, redmine_client): from Redmine import create_issue_command http_request = mocker.patch.object(redmine_client, '_http_request') http_request.return_value = {"issue": {"id": "1"}} - args = {'project_id': '1', 'issue_id': '1', 'subject': 'changeFromCode', 'tracker_id': 'Bug', 'watcher_user_ids': '[1]'} + args = {'project_id': '1', 'issue_id': '1', 'subject': 'changeFromCode', 'tracker_id': 'Bug', 'watcher_user_ids': '[1]', + 'custom_fields': '1:https://test:appear'} create_issue_command(redmine_client, args=args) - http_request.assert_called_with('POST', '/issues.json', params={}, - json_data={'issue': {'issue_id': '1', 'subject': 'changeFromCode', - 'tracker_id': '1', 'watcher_user_ids': [1], 'project_id': '1'}}, + http_request.assert_called_with('POST', '/issues.json', params={}, json_data={'issue': + {'issue_id': '1', 'subject': 'changeFromCode', + 'tracker_id': '1', 'custom_fields': + [{'id': '1', 'value': 'https://test:appear'}], + 'project_id': '1', 'watcher_user_ids': [1]}}, headers={'Content-Type': 'application/json', 'X-Redmine-API-Key': True}) +def test_create_issue_command_not_url_cf(mocker, redmine_client): + """ + Given: + - All relevant arguments for the command that is executed without list id + When: + - redmine-issue-create command is executed + Then: + - The http request is called with the right arguments + """ + from Redmine import create_issue_command + http_request = mocker.patch.object(redmine_client, '_http_request') + http_request.return_value = {"issue": {"id": "1"}} + args = {'project_id': '1', 'issue_id': '1', 'subject': 'changeFromCode', 'tracker_id': 'Bug', 'watcher_user_ids': '[1]', + 'custom_fields': '1:hello'} + create_issue_command(redmine_client, args=args) + http_request.assert_called_with('POST', '/issues.json', params={}, json_data={'issue': { + 'issue_id': '1', 'subject': 'changeFromCode', 'tracker_id': '1', 'custom_fields': + [{'id': '1', 'value': 'hello'}], 'project_id': '1', 'watcher_user_ids': [1]}}, + headers={'Content-Type': 'application/json', 'X-Redmine-API-Key': True}) + + def test_create_issue_command_response(mocker, redmine_client): """ Given: @@ -44,16 +68,19 @@ def test_create_issue_command_response(mocker, redmine_client): 'project_id': '1', 'issue_id': '1', 'subject': 'testResponse', - 'tracker_id': 'Bug' + 'tracker_id': 'Bug', + 'custom_fields': '1:https://test:appear,,,,' } create_issue_request_mock = mocker.patch.object(redmine_client, 'create_issue_request') create_issue_request_mock.return_value = {'issue': {'id': '789', 'project': {'name': 'testing', 'id': '1'}, - 'subject': 'testResponse', 'tracker': {'name': 'Bug', 'id': '1'} + 'subject': 'testResponse', 'tracker': {'name': 'Bug', 'id': '1'}, + 'custom_fields': {'name': 'test', 'value': 'https://test:appear'} } } result = create_issue_command(redmine_client, args) - assert result.readable_output == ("### The issue you created:\n|Id|Project|Tracker|Subject|\n|---|---|---|---|\n" - "| 789 | testing | Bug | testResponse |\n") + assert result.readable_output == ('### The issue you created:\n|Id|Project|Tracker|Subject|Custom Fields|\n|---|---|---|---|' + '---|\n| 789 | testing | Bug | testResponse | ***name***: test
***value***: ' + 'https://test:appear |\n') def test_create_issue_command_invalid_custom_fields(redmine_client): @@ -67,8 +94,8 @@ def test_create_issue_command_invalid_custom_fields(redmine_client): """ from Redmine import create_issue_command from CommonServerPython import DemistoException - args = {'project_id': '1', 'custom_fields': 'jnlnj', 'issue_id': '1', 'subject': 'testSub', 'tracker_id': 'Bug', - 'watcher_user_ids': '[1]', 'status_id': 'New', 'priority_id': 'High'} + args = {'project_id': '1', 'custom_fields': '1:https://test:appear,111', 'issue_id': '1', 'subject': 'testSub', + 'tracker_id': 'Bug', 'watcher_user_ids': '[1]', 'status_id': 'New', 'priority_id': 'High'} with pytest.raises(DemistoException) as e: create_issue_command(redmine_client, args) assert e.value.message == "Custom fields not in format, please follow the instructions" @@ -207,7 +234,8 @@ def test_update_issue_command_response(mocker, redmine_client): """ from Redmine import update_issue_command update_issue_request_mock = mocker.patch.object(redmine_client, 'update_issue_request') - args = {'issue_id': '1', 'subject': 'changefortest', 'tracker_id': 'Bug', 'watcher_user_ids': '[1]'} + args = {'issue_id': '1', 'subject': 'changefortest', 'tracker_id': 'Bug', + 'watcher_user_ids': '[1]', 'custom_fields': '1:https://test:appear'} update_issue_request_mock.return_value = {} result = update_issue_command(redmine_client, args=args) assert result.readable_output == 'Issue with id 1 was successfully updated.' @@ -286,10 +314,13 @@ def test_get_issues_list_command(mocker, redmine_client): """ from Redmine import get_issues_list_command http_request = mocker.patch.object(redmine_client, '_http_request') - args = {'sort': 'priority:desc', 'limit': '1'} + args = {'sort': 'priority:desc', 'limit': '1', 'custom_field': '1:https://tests'} get_issues_list_command(redmine_client, args) - http_request.assert_called_with('GET', '/issues.json', params={'status_id': 'open', 'offset': 0, 'limit': 1, - 'sort': 'priority:desc'}, headers={'X-Redmine-API-Key': True}) + http_request.assert_called_with('GET', '/issues.json', params={'status_id': 'open', + 'offset': 0, 'limit': 1, + 'sort': 'priority:desc', + 'cf_1': 'https://tests'}, + headers={'X-Redmine-API-Key': True}) def test_get_issues_list_command_response(mocker, redmine_client): diff --git a/Packs/Redmine/ReleaseNotes/1_0_1.md b/Packs/Redmine/ReleaseNotes/1_0_1.md new file mode 100644 index 000000000000..f896be2dafdb --- /dev/null +++ b/Packs/Redmine/ReleaseNotes/1_0_1.md @@ -0,0 +1,6 @@ + +#### Integrations + +##### Redmine + +- Fixed an issue where the **custom fields** argument was unable to support URLs. diff --git a/Packs/Redmine/pack_metadata.json b/Packs/Redmine/pack_metadata.json index 14dbbf3ce504..a6304c041c15 100644 --- a/Packs/Redmine/pack_metadata.json +++ b/Packs/Redmine/pack_metadata.json @@ -2,7 +2,7 @@ "name": "Redmine", "description": "A project management and issue tracking system.", "support": "xsoar", - "currentVersion": "1.0.0", + "currentVersion": "1.0.1", "author": "Cortex XSOAR", "url": "https://www.paloaltonetworks.com/cortex", "email": "", diff --git a/Packs/SentinelOne/Integrations/SentinelOne-V2/README.md b/Packs/SentinelOne/Integrations/SentinelOne-V2/README.md index 845c8ad3b3c1..4f09d32cf125 100644 --- a/Packs/SentinelOne/Integrations/SentinelOne-V2/README.md +++ b/Packs/SentinelOne/Integrations/SentinelOne-V2/README.md @@ -150,7 +150,7 @@ Lists all exclusion items that match the specified input filter. ### sentinelone-get-hash *** -Gets the file reputation by a SHA1 hash. +Gets the file reputation verdict by a SHA1 hash. #### Base Command @@ -167,6 +167,7 @@ Gets the file reputation by a SHA1 hash. | **Path** | **Type** | **Description** | | --- | --- | --- | | SentinelOne.Hash.Rank | Number | The hash reputation \(1-10\). | +| SentinelOne.Hash.Verdict | String | The hash reputation verdict. | | SentinelOne.Hash.Hash | String | The content hash. | ### sentinelone-get-threats diff --git a/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2.py b/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2.py index 80852daba63a..53fc129d8b8f 100644 --- a/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2.py +++ b/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2.py @@ -518,10 +518,18 @@ def get_processes_request(self, query_id=None, limit=None): return response.get('data', {}) def get_hash_reputation_request(self, hash_): + """ + [DEPRECATED by S1] IN 2.1 + """ endpoint_url = f'hashes/{hash_}/reputation' response = self._http_request(method='GET', url_suffix=endpoint_url) return response + def get_hash_verdict_request(self, hash_): + endpoint_url = f'hashes/{hash_}/verdict' + response = self._http_request(method='GET', url_suffix=endpoint_url) + return response + def get_hash_classification_request(self, hash_): """ [DEPRECATED by S1] IN BOTH 2.0 and 2.1 @@ -1146,7 +1154,8 @@ def get_threats_command(client: Client, args: dict) -> CommandResults: def get_hash_command(client: Client, args: dict) -> CommandResults: """ - Get hash reputation. + Get hash verdict. + Removed hash reputation since SentinelOne has deprecated it - Breaking BC. Removed hash classification since SentinelOne has deprecated it - Breaking BC. """ hash_ = args.get('hash') @@ -1154,20 +1163,20 @@ def get_hash_command(client: Client, args: dict) -> CommandResults: if type_ == 'Unknown': raise DemistoException('Enter a valid hash format.') - hash_reputation = client.get_hash_reputation_request(hash_) - reputation = hash_reputation.get('data', {}) + hash_verdict = client.get_hash_verdict_request(hash_) + reputation = hash_verdict.get('data', {}) contents = { - 'Rank': reputation.get('rank'), + 'Verdict': reputation.get('verdict'), 'Hash': hash_, } return CommandResults( - readable_output=tableToMarkdown('Sentinel One - Hash Reputation\nProvides hash reputation (rank from 0 to 10):', + readable_output=tableToMarkdown('SentinelOne - Hash Reputation Verdict\nProvides hash reputation verdict:', contents, removeNull=True), outputs_prefix='SentinelOne.Hash', outputs_key_field='Hash', outputs=contents, - raw_response=hash_reputation) + raw_response=hash_verdict) def mark_as_threat_command(client: Client, args: dict) -> CommandResults: diff --git a/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2.yml b/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2.yml index 608476f1bbfa..26a99a424b86 100644 --- a/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2.yml +++ b/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2.yml @@ -337,6 +337,9 @@ script: - contextPath: SentinelOne.Hash.Rank description: The hash reputation (1-10). type: Number + - contextPath: SentinelOne.Hash.Verdict + description: The verdict of a hash. + type: String - contextPath: SentinelOne.Hash.Hash description: The content hash. type: String @@ -2385,7 +2388,7 @@ script: - contextPath: SentinelOne.Notes.UpdatedAt description: The note updated time. type: string - dockerimage: demisto/python3:3.10.13.87159 + dockerimage: demisto/python3:3.10.13.89009 isfetch: true ismappable: true isremotesyncin: true diff --git a/Packs/SentinelOne/ReleaseNotes/3_2_23.md b/Packs/SentinelOne/ReleaseNotes/3_2_23.md new file mode 100644 index 000000000000..d1d4f9cb4596 --- /dev/null +++ b/Packs/SentinelOne/ReleaseNotes/3_2_23.md @@ -0,0 +1,4 @@ +#### Integrations +##### SentinelOne v2 +- Updated the ***sentinelone-get-hash*** command to output the Hash Reputation Verdict, instead of the Hash Reputation Rank as SentinelOne has deprecated /rank API endpoint and recommend /verdict API which fetches Hash Reputation Verdict. +- Updated the Docker image to: *demisto/python3:3.10.13.89009*. diff --git a/Packs/SentinelOne/TestPlaybooks/playbook-SentinelOne_2_0-test.yml b/Packs/SentinelOne/TestPlaybooks/playbook-SentinelOne_2_0-test.yml index e4f158d03101..ede96f0dd524 100644 --- a/Packs/SentinelOne/TestPlaybooks/playbook-SentinelOne_2_0-test.yml +++ b/Packs/SentinelOne/TestPlaybooks/playbook-SentinelOne_2_0-test.yml @@ -1049,7 +1049,7 @@ tasks: - - operator: isExists left: value: - simple: SentinelOne.Hash.Rank + simple: SentinelOne.Hash.Verdict iscontext: true continueonerrortype: "" view: |- diff --git a/Packs/SentinelOne/TestPlaybooks/playbook-SentinelOne_2_1-test.yml b/Packs/SentinelOne/TestPlaybooks/playbook-SentinelOne_2_1-test.yml index 80669dadc379..9c155369d47b 100644 --- a/Packs/SentinelOne/TestPlaybooks/playbook-SentinelOne_2_1-test.yml +++ b/Packs/SentinelOne/TestPlaybooks/playbook-SentinelOne_2_1-test.yml @@ -1050,7 +1050,7 @@ tasks: - - operator: isExists left: value: - simple: SentinelOne.Hash.Rank + simple: SentinelOne.Hash.Verdict iscontext: true continueonerrortype: "" view: |- diff --git a/Packs/SentinelOne/pack_metadata.json b/Packs/SentinelOne/pack_metadata.json index 227b88c6e7ac..5e8ba780758f 100644 --- a/Packs/SentinelOne/pack_metadata.json +++ b/Packs/SentinelOne/pack_metadata.json @@ -2,7 +2,7 @@ "name": "SentinelOne", "description": "Endpoint protection", "support": "partner", - "currentVersion": "3.2.22", + "currentVersion": "3.2.23", "author": "SentinelOne", "url": "https://www.sentinelone.com/support/", "email": "support@sentinelone.com", diff --git a/Packs/ServiceNow/Integrations/ServiceNowv2/README.md b/Packs/ServiceNow/Integrations/ServiceNowv2/README.md index fb4c487ba63b..af14657f9d9c 100644 --- a/Packs/ServiceNow/Integrations/ServiceNowv2/README.md +++ b/Packs/ServiceNow/Integrations/ServiceNowv2/README.md @@ -97,7 +97,7 @@ If MFA is enabled for your user, follow the next steps: | Get incident attachments | | False | | Incident Mirroring Direction | Choose the direction to mirror the incident: Incoming \(from ServiceNow to Cortex XSOAR\), Outgoing \(from Cortex XSOAR to ServiceNow\), or Incoming and Outgoing \(from/to Cortex XSOAR and ServiceNow\). | False | | Use Display Value | Select this checkbox to retrieve comments and work notes without accessing the \`sys_field_journal\` table. | False | - | Instance Date Format | Select the date format of your ServiceNow instance. Mandatory when using the \`Use Display Value\` option. More details under the troubleshooting section in the documentation of the integration. | False | + | Instance Date Format | Select the date format of your ServiceNow instance. Mandatory when using the \`Use Display Value\` option. More details under the troubleshooting section in the documentation of the integration. The integration supports the ServiceNow default time format (full form) `HH:mm:ss` with support to `a` notation for AM/PM. | False | | Comment Entry Tag | Choose the tag to add to an entry to mirror it as a comment in ServiceNow. | False | | Work Note Entry Tag | Choose the tag to add to an entry to mirror it as a work note in ServiceNow. | False | | File Entry Tag To ServiceNow | Choose the tag to add to an entry to mirror it as a file in ServiceNow. | False | diff --git a/Packs/ServiceNow/Integrations/ServiceNowv2/ServiceNowv2.py b/Packs/ServiceNow/Integrations/ServiceNowv2/ServiceNowv2.py index eb40bb6e7d26..a3825d533302 100644 --- a/Packs/ServiceNow/Integrations/ServiceNowv2/ServiceNowv2.py +++ b/Packs/ServiceNow/Integrations/ServiceNowv2/ServiceNowv2.py @@ -529,6 +529,7 @@ def split_notes(raw_notes, note_type, time_info): # convert note creation time to UTC try: display_date_format = time_info.get('display_date_format') + created_on = (created_on.replace('AM', '').replace('PM', '')).strip() created_on_UTC = datetime.strptime(created_on, display_date_format) + time_info.get('timezone_offset') except ValueError as e: raise Exception(f'Failed to convert {created_on} to a datetime object. Error: {e}') @@ -2526,8 +2527,10 @@ def get_timezone_offset(ticket: dict, display_date_format: str): datetime.timedelta: The timezone offset between the SNOW instance and UTC. """ try: - local_time = ticket.get('sys_created_on', {}).get('display_value', '') - local_time = datetime.strptime(local_time, display_date_format) + local_time: str = ticket.get('sys_created_on', {}).get('display_value', '') + # With %H hour format, AM/PM is redundant info. + local_time = (local_time.replace('AM', '').replace('PM', '')).strip() + local_time_dt = datetime.strptime(local_time, display_date_format) except Exception as e: raise Exception(f'Failed to get the display value offset time. ERROR: {e}') try: @@ -2535,7 +2538,7 @@ def get_timezone_offset(ticket: dict, display_date_format: str): utc_time = datetime.strptime(utc_time, DATE_FORMAT) except ValueError as e: raise Exception(f'Failed to convert {utc_time} to datetime object. ERROR: {e}') - offset = utc_time - local_time + offset = utc_time - local_time_dt return offset diff --git a/Packs/ServiceNow/Integrations/ServiceNowv2/ServiceNowv2.yml b/Packs/ServiceNow/Integrations/ServiceNowv2/ServiceNowv2.yml index 828e8099529f..0783d79586b1 100644 --- a/Packs/ServiceNow/Integrations/ServiceNowv2/ServiceNowv2.yml +++ b/Packs/ServiceNow/Integrations/ServiceNowv2/ServiceNowv2.yml @@ -83,7 +83,7 @@ configuration: name: use_display_value type: 8 required: false -- additionalinfo: 'Select the date format of your ServiceNow instance. Mandatory when using the `Use Display Value` option. More details under the troubleshooting section in the documentation of the integration.' +- additionalinfo: 'Select the date format of your ServiceNow instance. Mandatory when using the `Use Display Value` option. More details under the troubleshooting section in the documentation of the integration. The integration supports the ServiceNow default time format (full form) `HH:mm:ss` with support to `a` notation for AM/PM.' defaultvalue: None display: Instance Date Format name: display_date_format diff --git a/Packs/ServiceNow/Integrations/ServiceNowv2/ServiceNowv2_test.py b/Packs/ServiceNow/Integrations/ServiceNowv2/ServiceNowv2_test.py index 5b54fa59bf2c..2bf76ca18bce 100644 --- a/Packs/ServiceNow/Integrations/ServiceNowv2/ServiceNowv2_test.py +++ b/Packs/ServiceNow/Integrations/ServiceNowv2/ServiceNowv2_test.py @@ -335,6 +335,10 @@ def test_get_timezone_offset(): offset = get_timezone_offset(full_response, display_date_format=DATE_FORMAT_OPTIONS.get('dd/MM/yyyy')) assert offset == timedelta(minutes=600) + full_response = {'sys_created_on': {'display_value': '06/12/2022 23:38:52 PM', 'value': '2022-12-07 09:38:52'}} + offset = get_timezone_offset(full_response, display_date_format=DATE_FORMAT_OPTIONS.get('dd/MM/yyyy')) + assert offset == timedelta(minutes=600) + full_response = {'sys_created_on': {'display_value': '07.12.2022 0:38:52', 'value': '2022-12-06 19:38:52'}} offset = get_timezone_offset(full_response, display_date_format=DATE_FORMAT_OPTIONS.get('dd.MM.yyyy')) assert offset == timedelta(minutes=-300) @@ -343,6 +347,14 @@ def test_get_timezone_offset(): offset = get_timezone_offset(full_response, display_date_format=DATE_FORMAT_OPTIONS.get('mmm-dd-yyyy')) assert offset == timedelta(minutes=-300) + full_response = {'sys_created_on': {'display_value': 'Dec-07-2022 00:38:52 AM', 'value': '2022-12-06 19:38:52'}} + offset = get_timezone_offset(full_response, display_date_format=DATE_FORMAT_OPTIONS.get('mmm-dd-yyyy')) + assert offset == timedelta(minutes=-300) + + full_response = {'sys_created_on': {'display_value': 'Dec-07-2022 00:38:52 AM ', 'value': '2022-12-06 19:38:52'}} + offset = get_timezone_offset(full_response, display_date_format=DATE_FORMAT_OPTIONS.get('mmm-dd-yyyy')) + assert offset == timedelta(minutes=-300) + def test_get_ticket_notes_command_success(mocker): """ diff --git a/Packs/ServiceNow/ReleaseNotes/2_5_64.md b/Packs/ServiceNow/ReleaseNotes/2_5_64.md new file mode 100644 index 000000000000..47080b010a9c --- /dev/null +++ b/Packs/ServiceNow/ReleaseNotes/2_5_64.md @@ -0,0 +1,6 @@ + +#### Integrations + +##### ServiceNow v2 + +- Added support for Servicenow annotation of AM/PM. diff --git a/Packs/ServiceNow/pack_metadata.json b/Packs/ServiceNow/pack_metadata.json index 1b3dfe1089ab..400c05d3b2bd 100644 --- a/Packs/ServiceNow/pack_metadata.json +++ b/Packs/ServiceNow/pack_metadata.json @@ -2,7 +2,7 @@ "name": "ServiceNow", "description": "Use The ServiceNow IT Service Management (ITSM) solution to modernize the way you manage and deliver services to your users.", "support": "xsoar", - "currentVersion": "2.5.63", + "currentVersion": "2.5.64", "author": "Cortex XSOAR", "url": "https://www.paloaltonetworks.com/cortex", "email": "", diff --git a/Packs/SpurContextAPI/CONTRIBUTORS.json b/Packs/SpurContextAPI/CONTRIBUTORS.json new file mode 100644 index 000000000000..ce9d08a30890 --- /dev/null +++ b/Packs/SpurContextAPI/CONTRIBUTORS.json @@ -0,0 +1,3 @@ +[ + "Fabio Dias" +] diff --git a/Packs/SpurContextAPI/Integrations/SpurContextAPI/SpurContextAPI.py b/Packs/SpurContextAPI/Integrations/SpurContextAPI/SpurContextAPI.py index 1dd3a0fff439..d5cd65030910 100644 --- a/Packs/SpurContextAPI/Integrations/SpurContextAPI/SpurContextAPI.py +++ b/Packs/SpurContextAPI/Integrations/SpurContextAPI/SpurContextAPI.py @@ -1,6 +1,5 @@ import demistomock as demisto # noqa: F401 -from CommonServerPython import * # noqa: F401 # pylint: disable=unused-wildcard-import -from CommonServerUserPython import * # noqa: F401 # pylint: disable=unused-wildcard-import +from CommonServerPython import * # noqa: F401 import ipaddress import urllib3 @@ -82,6 +81,7 @@ def test_module(client: Client) -> str: try: full_url = urljoin(client._base_url, 'status') demisto.debug(f'SpurContextAPI full_url: {full_url}') + client._http_request( method='GET', full_url=full_url, @@ -128,10 +128,9 @@ def main() -> None: """ api_key = demisto.params().get('credentials', {}).get('password') - base_url = "https://api.spur.us/" + base_url = demisto.params().get('base_url') verify_certificate = not demisto.params().get('insecure', False) proxy = demisto.params().get('proxy', False) - demisto.debug(f'Command being called is {demisto.command()}') try: diff --git a/Packs/SpurContextAPI/Integrations/SpurContextAPI/SpurContextAPI.yml b/Packs/SpurContextAPI/Integrations/SpurContextAPI/SpurContextAPI.yml index 24f9ed992add..a709bbbc5e00 100644 --- a/Packs/SpurContextAPI/Integrations/SpurContextAPI/SpurContextAPI.yml +++ b/Packs/SpurContextAPI/Integrations/SpurContextAPI/SpurContextAPI.yml @@ -3,12 +3,21 @@ commonfields: id: SpurContextAPI version: -1 configuration: -- display: "" - displaypassword: API Token +- display: "Server URL (e.g. https://api.spur.us/)" + name: base_url + type: 0 + required: false + defaultvalue: https://api.spur.us/ +- displaypassword: API Token + hiddenusername: true name: credentials - type: 9 required: true - hiddenusername: true + type: 9 +- defaultvalue: 'false' + display: Use system proxy settings + name: proxy + required: false + type: 8 description: 'Enrich indicators using the Spur Context API.' display: SpurContextAPI name: SpurContextAPI @@ -67,10 +76,10 @@ script: type: array description: The different type of client devices that we have observed on this IP address. runonce: false - script: '-' + script: '' type: python subtype: python3 - dockerimage: demisto/python3:3.10.13.89009 + dockerimage: demisto/python3:3.10.13.89873 fromversion: 6.10.0 tests: - No tests (auto formatted) diff --git a/Packs/SpurContextAPI/Integrations/SpurContextAPI/SpurContextAPI_description.md b/Packs/SpurContextAPI/Integrations/SpurContextAPI/SpurContextAPI_description.md index ea2d2155cca3..ffd8e4cc33e2 100644 --- a/Packs/SpurContextAPI/Integrations/SpurContextAPI/SpurContextAPI_description.md +++ b/Packs/SpurContextAPI/Integrations/SpurContextAPI/SpurContextAPI_description.md @@ -1 +1 @@ -This integration allows Cortex XSOAR users to enrich indicators, specifically IP addresses, using the Spur Context API. It provides detailed context around the reputation, behavior, and characteristics of IP addresses, helping security analysts to quickly assess threats and make informed decisions. +This integration allows Cortex XSOAR users to enrich indicators, specifically IP addresses, using the Spur Context API. It provides detailed context around the reputation, behavior, and characteristics of IP addresses, helping security analysts to quickly assess threats and make informed decisions. \ No newline at end of file diff --git a/Packs/SpurContextAPI/ReleaseNotes/1_0_1.md b/Packs/SpurContextAPI/ReleaseNotes/1_0_1.md new file mode 100644 index 000000000000..0deefc36695c --- /dev/null +++ b/Packs/SpurContextAPI/ReleaseNotes/1_0_1.md @@ -0,0 +1,8 @@ + +#### Integrations + +##### SpurContextAPI + +- Added the **Use system proxy settings** parameter. +- Added the **Base URL** parameter. +- Updated the Docker image to: *demisto/python3:3.10.13.89873*. diff --git a/Packs/SpurContextAPI/pack_metadata.json b/Packs/SpurContextAPI/pack_metadata.json index ff575d8c911e..b90e0143d13c 100644 --- a/Packs/SpurContextAPI/pack_metadata.json +++ b/Packs/SpurContextAPI/pack_metadata.json @@ -2,7 +2,7 @@ "name": "Spur Context API", "description": "Enrich IP addresses with data from the Spur Context API", "support": "partner", - "currentVersion": "1.0.0", + "currentVersion": "1.0.1", "author": "Spur Intelligence Corporation", "url": "https://spur.us/contact/", "email": "support@spur.us", @@ -24,4 +24,4 @@ "githubUser": [ "jjunqueira" ] -} \ No newline at end of file +} diff --git a/Packs/XSOARFileManagement/Integrations/XSOARFileManagement/README.md b/Packs/XSOARFileManagement/Integrations/XSOARFileManagement/README.md index baaa22de77a3..785655f0f7d8 100644 --- a/Packs/XSOARFileManagement/Integrations/XSOARFileManagement/README.md +++ b/Packs/XSOARFileManagement/Integrations/XSOARFileManagement/README.md @@ -121,3 +121,87 @@ Rename a file. Warning: use this only if necessary, it's HEAVY to run, this will #### Context Output There is no context output for this command. +### file-management-download-file + +*** +Download files from server. + +#### Base Command + +`file-management-download-file` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| fileURI | File URI ex:'/markdown/image/123_60cad1a9-6f90-42c5-8b1b-514d66d74fc0.jpg'. | Required | +| fileName | Name of the new downloaded file. | Required | +| incidentID | Incident ID to upload the file. If empty, the current incident ID is taken. | Optional | +| target | Where to upload the file - Available options are: - 'war room entry': the file will be uploaded as war room entry. - 'incident attachment': the file will be uploaded as incident attachment. - default are 'war room entry'. Possible values are: war room entry, incident attachment. Default is war room entry. | Optional | + +#### Context Output + +There is no context output for this command. + +#### Command Example + +``` +!file-management-download-file file_uri="/markdown/image/12142_60cad1a9-6f90-42c5-8b1b-514d66d74fc0.jpg" +!file-management-download-file file_uri="/markdown/image/12142_60cad1a9-6f90-42c5-8b1b-514d66d74fc0.jpg" fileName="my_image.jpg" +!file-management-download-file file_uri="/markdown/image/12142_60cad1a9-6f90-42c5-8b1b-514d66d74fc0.jpg" fileName="my_image.jpg" incidentID="1234" +``` + +#### Human Readable Output + +> File my_image.jpg uploaded successfully to incident 1234. Entry ID is 1@1234 + +### file-management-get-file-hash + +*** +Get file hash from URI. + +#### Base Command + +`file-management-get-file-hash` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| fileURI | File URI ex:'/markdown/image/123_60cad1a9-6f90-42c5-8b1b-514d66d74fc0.jpg'. | Required | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| File_Hash.Extension | String | Extension of the file. | +| File_Hash.MD5 | String | MD5 of the file. | +| File_Hash.Name | String | Name of the file. | +| File_Hash.SHA1 | String | SHA1 of the file. | +| File_Hash.SHA256 | String | SHA256 of the file. | +| File_Hash.SHA512 | String | SHA512 of the file. | +| File_Hash.Size | String | Size of the file. | + +#### Command Example + +```!file-management-get-file-hash fileURI="/markdown/image/12142_60cad1a9-6f90-42c5-8b1b-514d66d74fc0.jpg" ``` + +#### Context Example + +```json +{ + "File_Hash": { + "Extension": "jpg", + "MD5": "e2f28a722de24003257ded589ac10eee", + "Name": "12142_60cad1a9-6f90-42c5-8b1b-514d66d74fc0.jpg", + "SHA1": "0e5e761a2e6794a4d1c445667d4944db34f78d22", + "SHA256": "877383f34532683580b53d2f5a36e68155de58175524a99d4c25d0da96202e5c", + "SHA512": "5ba5455f0ff3e545f8212b4811d22c66451e1a96a0d886b4550bb287c310f52b4ac37559e90546ef2eae69c1a7942223fb0d2660b9fe273562a96376bc0fdd03", + "Size": "1569787" + } +} +``` + +#### Human Readable Output + +> Hash save under the key 'File_Hash'. diff --git a/Packs/XSOARFileManagement/Integrations/XSOARFileManagement/XSOARFileManagement.py b/Packs/XSOARFileManagement/Integrations/XSOARFileManagement/XSOARFileManagement.py index 90c65e40c5d0..9c5e14120362 100644 --- a/Packs/XSOARFileManagement/Integrations/XSOARFileManagement/XSOARFileManagement.py +++ b/Packs/XSOARFileManagement/Integrations/XSOARFileManagement/XSOARFileManagement.py @@ -7,6 +7,7 @@ from typing import Tuple import urllib3 import base64 +import hashlib # Disable insecure warnings urllib3.disable_warnings() @@ -141,7 +142,7 @@ def delete_attachment(self, incident_id: str, file_path: str, field_name: str = ) return response - def get_file(self, entry_id: str): + def get_entry_file(self, entry_id: str): """Get the content of the file Arguments: client: (Client) The client class. @@ -155,6 +156,17 @@ def get_file(self, entry_id: str): ) return response + def get_markdown_file(self, entry_id: str): + """Get the content of the file + Arguments: + client: (Client) The client class. + entry_id {str} -- entry ID of the file + Returns: + json -- return of the API + """ + response = requests.get(f'{self._base_url}/markdown/image/{entry_id}', headers=self._headers, verify=self._verify) + return response + def get_current_user(self): """Get current user Arguments: @@ -201,7 +213,6 @@ def get_incident_id(entry_id: str) -> str: def rename_file_command(client: Client, args: dict) -> CommandResults: """Check if a file exist on the disk Arguments: - incidentID {str} -- incident id to upload the file to entryID {str} -- entry ID of the file newFileName {str} -- new name of the file Returns: @@ -212,7 +223,7 @@ def rename_file_command(client: Client, args: dict) -> CommandResults: entry_id = args.get('entryID', '') file_name = args.get('newFileName', '') - res_path, res_name = get_file_path_name(entry_id) + res_path, res_name = get_entry_file_path_name(entry_id) # read file file_binary = open(res_path, 'rb') # create new file new name @@ -220,22 +231,7 @@ def rename_file_command(client: Client, args: dict) -> CommandResults: response = client.upload_file(incident_id, file_binary, file_name, False) file_binary.close() # create data for the key file - nfu = { - "Size": response["entries"][0]["fileMetadata"]["size"], - "SHA1": response["entries"][0]["fileMetadata"]["sha1"], - "SHA256": response["entries"][0]["fileMetadata"]["sha256"], - "SHA512": response["entries"][0]["fileMetadata"]["sha512"], - "Name": response["entries"][0]["file"], - "SSDeep": response["entries"][0]["fileMetadata"]["ssdeep"], - "EntryID": response["entries"][0]["id"], - "Info": response["entries"][0]["fileMetadata"]["type"], - "Type": response["entries"][0]["fileMetadata"]["info"], - "MD5": response["entries"][0]["fileMetadata"]["md5"], - "Extension": response["entries"][0]["file"] - } - res = nfu.get("Name", "").split(".") - if len(res) > 1: - nfu["Extension"] = res[-1] + nfu = struct_file_upload(response) # delete old file new_files = delete_file(client, entry_id) new_files.append(nfu) @@ -285,10 +281,13 @@ def delete_attachment_command(client: Client, args: dict) -> CommandResults: Note: This command delete file on the disk """ - incident_id = args.get('incidentID', demisto.incident()["id"]) + inc = demisto.incident() + incident_id = args.get('incidentID', inc.get("investigationId") if not inc.get("id") else inc.get("id")) file_path = args.get('filePath', "") field_name = args.get('fieldName', "attachment") + if not incident_id: + return_error("Please provide an incident id") if not file_path: return_error("Argument file_path is empty.") try: @@ -331,7 +330,7 @@ def delete_file_command(client: Client, args: dict) -> CommandResults: return CommandResults(readable_output=f"File {entry_id} deleted !", outputs=new_files, outputs_prefix="File") -def get_file_path_name(file_input: str) -> Tuple[str, str]: +def get_entry_file_path_name(file_input: str) -> Tuple[str, str]: """Get the path and the name of a file Arguments: file_input {str} -- can be an entryID or a path under the key incident.attachments.path @@ -370,7 +369,8 @@ def upload_file_command(client: Client, args: dict) -> CommandResults: You can give either the entryID, the filePath or the fileContent. fileName have to contain the extension if you want one """ - incident_id = args.get('incidentID', demisto.incident()["id"]) + inc = demisto.incident() + incident_id = args.get('incidentID', inc.get("investigationId") if not inc.get("id") else inc.get("id")) file_content = args.get('fileContent', '') file_content_b64 = args.get('fileContentB64', '') entry_id = args.get('entryID', '') @@ -378,9 +378,8 @@ def upload_file_command(client: Client, args: dict) -> CommandResults: file_name = args.get('fileName', '') target = args.get('target', 'war room entry') - # id can be empty if this command is triggered by a field changed if not incident_id: - return_error("Unable to get the incident id of the incident !") + return_error("Please provide an incident id") # check if some content is given and not too many if len(list(filter(None, [file_content, file_content_b64, entry_id, file_path]))) != 1: return_error("You have to give either the content of the file using the arg 'fileContent'" @@ -403,7 +402,7 @@ def upload_file_command(client: Client, args: dict) -> CommandResults: target == 'incident attachment') else: arg_path: str = list(filter(None, [entry_id, file_path]))[0] - res_path, res_name = get_file_path_name(arg_path) + res_path, res_name = get_entry_file_path_name(arg_path) # file name override by user file_name = file_name if file_name else res_name if not file_name: @@ -415,6 +414,7 @@ def upload_file_command(client: Client, args: dict) -> CommandResults: file_name, target == 'incident attachment') file_binary.close() + # create output readable = f'File {file_name} uploaded successfully to incident {incident_id}.' # in case the file uploaded as war room entry if target == 'war room entry': @@ -423,6 +423,107 @@ def upload_file_command(client: Client, args: dict) -> CommandResults: return CommandResults(readable_output=readable) +def struct_file_upload(response): + nfu = { + "Size": response["entries"][0]["fileMetadata"]["size"], + "SHA1": response["entries"][0]["fileMetadata"]["sha1"], + "SHA256": response["entries"][0]["fileMetadata"]["sha256"], + "SHA512": response["entries"][0]["fileMetadata"]["sha512"], + "Name": response["entries"][0]["file"], + "SSDeep": response["entries"][0]["fileMetadata"]["ssdeep"], + "EntryID": response["entries"][0]["id"], + "Info": response["entries"][0]["fileMetadata"]["type"], + "Type": response["entries"][0]["fileMetadata"]["info"], + "MD5": response["entries"][0]["fileMetadata"]["md5"], + "Extension": response["entries"][0]["file"] + } + res = nfu.get("Name", "").split(".") + if len(res) > 1: + nfu["Extension"] = res[-1] + return nfu + + +def download_file_command(client: Client, args: dict) -> CommandResults: + """Download a file and upload it + Arguments: + incidentID {str} -- incident id to upload the file to + fileName {str} -- name of the file in the dest incident + fileURI {str} -- URI of the file + target {bool} -- upload the file as an attachment or an war room entry + Returns: + CommandResults -- Readable output + """ + inc = demisto.incident() + incident_id = args.get('incidentID', inc.get("investigationId") if not inc.get("id") else inc.get("id")) + file_name = args.get("fileName", "") + file_uri = re.sub("\/?markdown\/image\/", "", args.get("fileURI", "")) + target = args.get('target', 'war room entry') + + if not incident_id: + return_error("Please provide an incident id") + if not file_uri: + return_error("Please provide file URI") + # download file + response = client.get_markdown_file(file_uri) + if response.status_code != 200: + return_error(f"HTTP error {response.status_code}") + # extract file_name from URL or reponse header + if not file_name: + headers = response.headers + if "Content-Disposition" in headers.keys(): + file_name = re.findall("filename=(.+)", headers["Content-Disposition"])[0] + else: + file_name = file_uri.split("/")[-1] + if not file_name: + return_error("Please provide file name") + response = client.upload_file(incident_id, response.content, file_name, target == 'incident attachment') + + # create output + readable = f'File {file_name} uploaded successfully to incident {incident_id}.' + # in case the file uploaded as war room entry + if target == 'war room entry': + readable += f' Entry ID is {response["entries"][0]["id"]}' + return CommandResults(readable_output=readable, + outputs=struct_file_upload(response), + outputs_prefix="File") + + +def get_file_hahs_command(client: Client, args: dict) -> CommandResults: + """Get the file hash + Arguments: + fileURI {str} -- URI of the file + Returns: + CommandResults -- Readable output + """ + file_uri = re.sub("\/?markdown\/image\/", "", args.get("fileURI", "")) + if not file_uri: + return_error("Please provide file URI") + # download file + response = client.get_markdown_file(file_uri) + if response.status_code != 200: + return_error(f"HTTP error {response.status_code}") + file_name = "" + if "Content-Disposition" in response.headers.keys(): + file_name = re.findall("filename=(.+)", response.headers["Content-Disposition"])[0] + + # structure to return + nfu = { + "Size": response.headers['Content-length'], + "SHA1": hashlib.sha1(response.content, usedforsecurity=False).hexdigest(), + "SHA256": hashlib.sha256(response.content, usedforsecurity=False).hexdigest(), + "SHA512": hashlib.sha512(response.content, usedforsecurity=False).hexdigest(), + "Name": file_name, + "MD5": hashlib.md5(response.content, usedforsecurity=False).hexdigest() + } + res = nfu.get("Name", "").split(".") + if len(res) > 1: + nfu["Extension"] = res[-1] + + return CommandResults(readable_output="Hash save under the key 'File_Hash'.", + outputs=nfu, + outputs_prefix="File_Hash") + + ''' MAIN FUNCTION ''' @@ -466,6 +567,10 @@ def main() -> None: return_results(check_file_command(client, args)) elif command == 'file-management-rename-file': return_results(rename_file_command(client, args)) + elif command == 'file-management-download-file': + return_results(download_file_command(client, args)) + elif command == 'file-management-get-file-hash': + return_results(get_file_hahs_command(client, args)) else: raise NotImplementedError(f'Command {command} is not implemented') diff --git a/Packs/XSOARFileManagement/Integrations/XSOARFileManagement/XSOARFileManagement.yml b/Packs/XSOARFileManagement/Integrations/XSOARFileManagement/XSOARFileManagement.yml index 33954d1ecac2..7f77b735b8cf 100644 --- a/Packs/XSOARFileManagement/Integrations/XSOARFileManagement/XSOARFileManagement.yml +++ b/Packs/XSOARFileManagement/Integrations/XSOARFileManagement/XSOARFileManagement.yml @@ -33,65 +33,122 @@ display: XSOAR File Management name: XSOAR File Management script: commands: - - arguments: - - description: Incident ID to upload the file. If empty, the current incident ID is taken. - name: incidentID - - description: Content of the file encoded in Base64 (if set let filePath, entrID and fileContent empty). - name: fileContentB64 - - description: Non binary content of the file (if set let filePath, entryID and fileContentB64 empty). - name: fileContent - - description: Entry ID of the file to read (if set let filePath, fileContent and fileContentB64 empty). - name: entryID - - description: 'Path of the file to read ex: incident.attachment.path (if set let entryID, fileContent and fileContentB64 empty).' - name: filePath - - description: Name of the file. Mandatory if used with filePath and fileContent otherwise the name of the file will not change. - name: fileName - - auto: PREDEFINED - defaultValue: war room entry - description: 'Where to upload the file - Available options are: - ''war room entry'': the file will be uploaded as war room entry. - ''incident attachment'': the file will be uploaded as incident attachment. - default are ''war room entry''.' - name: target + - name: file-management-upload-file-to-incident + arguments: + - name: incidentID + description: Incident ID to upload the file. If empty, the current incident + ID is taken. + - name: fileContentB64 + description: Content of the file encoded in Base64 (if set let filePath, entrID + and fileContent empty). + - name: fileContent + description: Non binary content of the file (if set let filePath, entryID and + fileContentB64 empty). + - name: entryID + description: Entry ID of the file to read (if set let filePath, fileContent + and fileContentB64 empty). + - name: filePath + description: 'Path of the file to read ex: incident.attachment.path (if set + let entryID, fileContent and fileContentB64 empty).' + - name: fileName + description: Name of the file. Mandatory if used with filePath and fileContent + otherwise the name of the file will not change. + - name: target + auto: PREDEFINED predefined: - war room entry - incident attachment - description: Copies a file from this incident to the specified incident. Usefull if you want to manipule file in the preprocessing. - name: file-management-upload-file-to-incident - - arguments: - - description: Entry ID of the file. - name: entryID + description: 'Where to upload the file - Available options are: - ''war room + entry'': the file will be uploaded as war room entry. - ''incident attachment'': + the file will be uploaded as incident attachment. - default are ''war room + entry''.' + defaultValue: war room entry + description: Copies a file from this incident to the specified incident. Usefull + if you want to manipule file in the preprocessing. + - name: file-management-delete-file + arguments: + - name: entryID required: true + description: Entry ID of the file. description: Delete the file from the incident and from the XSOAR server. execution: true - name: file-management-delete-file - - arguments: - - description: Entry ID of the file. - name: entryID + - name: file-management-check-file + arguments: + - name: entryID required: true - description: Check if entry ID exist. - name: file-management-check-file + description: Entry ID of the file. outputs: - - description: Dictionary with EntryID as key and boolean if the file exists as value. - contextPath: IsFileExists - - arguments: - - description: File path of the file. - name: filePath + - contextPath: IsFileExists + description: Dictionary with EntryID as key and boolean if the file exists as + value. + description: Check if entry ID exist. + - name: file-management-delete-attachment + arguments: + - name: filePath required: true - - description: ID of the incident to delete attachment. - name: incidentID - - description: 'Name of the field (type attachment) you want to remove the attachment by default it''s the incident attachment (incident.attachment) field.' - name: fieldName + description: File path of the file. + - name: incidentID + description: ID of the incident to delete attachment. + - name: fieldName + description: Name of the field (type attachment) you want to remove the attachment + by default it's the incident attachment (incident.attachment) field. description: Delete the attachment from the incident and from the XSOAR server. execution: true - name: file-management-delete-attachment - - arguments: - - description: Entry ID of the file to rename. - name: entryID + - name: file-management-rename-file + arguments: + - name: entryID required: true - - description: New name for the file. - name: newFileName + description: Entry ID of the file to rename. + - name: newFileName required: true - description: 'Rename a file. Warning: use this only if necessary, it''s HEAVY to run, this will delete and recreate the file with another name.' - name: file-management-rename-file - dockerimage: demisto/python3:3.10.13.89009 + description: New name for the file. + description: 'Rename a file. Warning: use this only if necessary, it''s HEAVY + to run, this will delete and recreate the file with another name.' + - name: file-management-download-file + arguments: + - name: fileName + description: Name of the new downloaded file. + - name: fileURI + required: true + description: 'File URI ex:''/markdown/image/123_60cad1a9-6f90-42c5-8b1b-514d66d74fc0.jpg''.' + - name: incidentID + description: Incident ID to upload the file. If empty, the current incident + ID is taken. + - name: target + description: 'Where to upload the file - Available options are: - ''war room + entry'': the file will be uploaded as war room entry. - ''incident attachment'': + the file will be uploaded as incident attachment. - default are ''war room + entry''.' + description: Download files from server. + - name: file-management-get-file-hash + arguments: + - name: fileURI + required: true + description: 'File URI ex:''/markdown/image/123_60cad1a9-6f90-42c5-8b1b-514d66d74fc0.jpg''.' + outputs: + - contextPath: File_Hash.Extension + description: Extension of the file. + type: string + - contextPath: File_Hash.MD5 + description: MD5 of the file. + type: string + - contextPath: File_Hash.SHA1 + description: SHA1of the file. + type: string + - contextPath: File_Hash.SHA256 + description: SHA256of the file. + type: string + - contextPath: File_Hash.SHA512 + description: SHA512of the file. + type: string + - contextPath: File_Hash.Name + description: Name of the file. + type: string + - contextPath: File_Hash.Size + description: Size of the file. + type: string + description: Get file hash from URI. + dockerimage: demisto/python3:3.10.14.90585 runonce: false script: '' subtype: python3 diff --git a/Packs/XSOARFileManagement/ReleaseNotes/1_1_2.md b/Packs/XSOARFileManagement/ReleaseNotes/1_1_2.md new file mode 100644 index 000000000000..d2948e2076b7 --- /dev/null +++ b/Packs/XSOARFileManagement/ReleaseNotes/1_1_2.md @@ -0,0 +1,8 @@ + +#### Integrations + +##### XSOAR File Management + +Added 2 commands: + - ***file-management-get-file-hash*** + - ***file-management-download-file*** diff --git a/Packs/XSOARFileManagement/pack_metadata.json b/Packs/XSOARFileManagement/pack_metadata.json index 37790eaa2532..261474a21e92 100644 --- a/Packs/XSOARFileManagement/pack_metadata.json +++ b/Packs/XSOARFileManagement/pack_metadata.json @@ -2,7 +2,7 @@ "name": "XSOAR File Management", "description": "This pack let user manipulate file inside XSOAR more easily than with the builtin functions.", "support": "community", - "currentVersion": "1.1.1", + "currentVersion": "1.1.2", "author": "Pierre", "url": "", "email": "", diff --git a/Tests/conf.json b/Tests/conf.json index b2ddb748db9b..d012fc7b0043 100644 --- a/Tests/conf.json +++ b/Tests/conf.json @@ -2860,7 +2860,7 @@ "integrations": [ "Cylance Protect v2", "carbonblack-v2", - "epo", + "McAfee ePO v2", "Active Directory Query v2", "VMware Carbon Black EDR v2" ], diff --git a/Tests/scripts/download_conf_repos.sh b/Tests/scripts/download_conf_repos.sh index 095c064caa27..9ddc13423fbd 100755 --- a/Tests/scripts/download_conf_repos.sh +++ b/Tests/scripts/download_conf_repos.sh @@ -117,9 +117,9 @@ CI_SERVER_HOST=${CI_SERVER_HOST:-gitlab.xdr.pan.local} # disable-secrets-detecti clone_repository_with_fallback_branch "${CI_SERVER_HOST}" "gitlab-ci-token" "${CI_JOB_TOKEN}" "${CI_PROJECT_NAMESPACE}/content-test-conf" "${SEARCHED_BRANCH_NAME}" 3 10 "master" -cp ./content-test-conf/secrets_build_scripts/google_secret_manager_handler.py ./Tests/scripts -cp ./content-test-conf/secrets_build_scripts/add_secrets_file_to_build.py ./Tests/scripts -cp ./content-test-conf/secrets_build_scripts/merge_and_delete_dev_secrets.py ./Tests/scripts +cp ./content-test-conf/SecretActions/google_secret_manager_handler.py ./Tests/scripts +cp ./content-test-conf/SecretActions/SecretsBuild/add_secrets_file_to_build.py ./Tests/scripts +cp ./content-test-conf/SecretActions/SecretsBuild/merge_and_delete_dev_secrets.py ./Tests/scripts cp -r ./content-test-conf/demisto.lic ${DEMISTO_LIC_PATH} cp -r ./content-test-conf/signDirectory ${DEMISTO_PACK_SIGNATURE_UTIL_PATH}