diff --git a/src/falconpy/_constant/__init__.py b/src/falconpy/_constant/__init__.py index c9d44ffde..94907529a 100644 --- a/src/falconpy/_constant/__init__.py +++ b/src/falconpy/_constant/__init__.py @@ -46,7 +46,7 @@ "GetDeviceDetails", "PostDeviceDetailsV2", "GetVulnerabilities", "GetIntelIndicatorEntities", "getChildrenV2", "cancel-scans", "GetDetectSummaries", "UpdateQuarantinedDetectsByIds", "GetQuarantineFiles", "PostEntitiesAlertsV1", "CreateSavedSearchesDeployV1", - "WorkflowExecutionsAction" + "WorkflowExecutionsAction", "signalChangesExternal" ] MOCK_OPERATIONS: List[str] = [ "GetImageAssessmentReport", "DeleteImageDetails", "ImageMatchesPolicy" diff --git a/src/falconpy/_endpoint/_filevantage.py b/src/falconpy/_endpoint/_filevantage.py index 7b69ffe91..6aa905d39 100644 --- a/src/falconpy/_endpoint/_filevantage.py +++ b/src/falconpy/_endpoint/_filevantage.py @@ -37,6 +37,66 @@ """ _filevantage_endpoints = [ + [ + "getActionsMixin0", + "GET", + "/filevantage/entities/actions/v1", + "Retrieves the processing results for 1 or more actions.", + "filevantage", + [ + { + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi", + "description": "One or more actions ids in the form of `ids=ID1&ids=ID2`", + "name": "ids", + "in": "query", + "required": True + } + ] + ], + [ + "startActions", + "POST", + "/filevantage/entities/actions/v1", + "Initiates the specified action on the provided change ids", + "filevantage", + [ + { + "description": "Create a new action.\n\n * `operation` must be one of the `suppress`, `unsuppress`, or " + " `purge`\n\n * `change_ids` represent the ids of the changes the operation will perform; limited to 100 ids " + "per action\n\n * `comment` optional comment to describe the reason for the action", + "name": "body", + "in": "body", + "required": True + } + ] + ], + [ + "getContents", + "GET", + "/filevantage/entities/change-content/v1", + "Retrieves the content captured for the provided change id", + "filevantage", + [ + { + "type": "string", + "description": "ID of the change in the form of id=ID1", + "name": "id", + "in": "query", + "required": True + }, + { + "type": "string", + "description": "Providing the value of `gzip` compresses the response, otherwise the content is " + "returned uncompressed.", + "name": "Accept-Encoding", + "in": "header" + } + ] + ], [ "getChanges", "GET", @@ -607,6 +667,59 @@ } ] ], + [ + "signalChangesExternal", + "POST", + "/filevantage/entities/workflow/v1", + "Initiates workflows for the provided change ids", + "filevantage", + [ + { + "description": "Change ids to initiate the workflows; limited to 100 per request.", + "name": "body", + "in": "body", + "required": True + } + ] + ], + [ + "queryActionsMixin0", + "GET", + "/filevantage/queries/actions/v1", + "Returns one or more action ids", + "filevantage", + [ + { + "minimum": 0, + "type": "integer", + "description": "The first action index to return in the response. If not provided it will default to " + "'0'. Use with the `limit` parameter to manage pagination of results.", + "name": "offset", + "in": "query" + }, + { + "type": "integer", + "description": "The maximum number of actions to return in the response (default: 100; max: 500). Use " + "with the `offset` parameter to manage pagination of results", + "name": "limit", + "in": "query" + }, + { + "type": "string", + "description": "The sort expression that should be used to sort the results (e.g. created_date|desc)", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "description": "Filter changes using a query in Falcon Query Language (FQL). \n\nCommon filter options " + " include:\n\n - `status`\n - `operation_type`\n\n The full list of allowed filter parameters can be reviewed " + "in our API documentation.", + "name": "filter", + "in": "query" + } + ] + ], [ "queryChanges", "GET", diff --git a/src/falconpy/_payload/__init__.py b/src/falconpy/_payload/__init__.py index a4659cfbd..b5940530c 100644 --- a/src/falconpy/_payload/__init__.py +++ b/src/falconpy/_payload/__init__.py @@ -72,7 +72,8 @@ filevantage_rule_group_payload, filevantage_rule_payload, filevantage_policy_payload, - filevantage_scheduled_exclusion_payload + filevantage_scheduled_exclusion_payload, + filevantage_start_payload ) from ._mssp import mssp_payload from ._firewall import ( @@ -127,5 +128,6 @@ "workflow_template_payload", "foundry_execute_search_payload", "foundry_dynamic_search_payload", "image_policy_payload", "image_exclusions_payload", "image_group_payload", "workflow_definition_payload", "workflow_human_input", "workflow_mock_payload", - "cspm_service_account_validate_payload", "api_plugin_command_payload", "mobile_enrollment_payload" + "cspm_service_account_validate_payload", "api_plugin_command_payload", "mobile_enrollment_payload", + "filevantage_start_payload" ] diff --git a/src/falconpy/_payload/_filevantage.py b/src/falconpy/_payload/_filevantage.py index b821a44a8..b361b23a2 100644 --- a/src/falconpy/_payload/_filevantage.py +++ b/src/falconpy/_payload/_filevantage.py @@ -79,6 +79,32 @@ def filevantage_policy_payload(passed_keywords: dict) -> dict: return returned +def filevantage_start_payload(passed_keywords: dict) -> dict: + """Craft a properly formatted FileVantage policy body payload. + + { + "change_ids": [ + "string" + ], + "comment": "string", + "operation": "string" + } + """ + returned = {} + keys = ["change_ids", "comment", "operation"] + for key in keys: + if passed_keywords.get(key, None): + if key == "change_ids": + changes = passed_keywords.get(key, None) + if isinstance(changes, str): + changes = changes.split(",") + returned[key] = changes + else: + returned[key] = passed_keywords.get(key, None) + + return returned + + def filevantage_scheduled_exclusion_payload(passed_keywords: dict) -> dict: """Craft a properly formatted FileVantage scheduled exclusion body payload. diff --git a/src/falconpy/filevantage.py b/src/falconpy/filevantage.py index 88222cba9..041627f4b 100644 --- a/src/falconpy/filevantage.py +++ b/src/falconpy/filevantage.py @@ -36,18 +36,22 @@ For more information, please refer to """ # pylint: disable=C0302 +import json from typing import Dict, Union from ._payload import ( filevantage_rule_group_payload, filevantage_rule_payload, filevantage_policy_payload, - filevantage_scheduled_exclusion_payload + filevantage_scheduled_exclusion_payload, + filevantage_start_payload, + generic_payload_list ) from ._util import process_service_request, force_default, handle_single_argument from ._service_class import ServiceClass from ._endpoint._filevantage import _filevantage_endpoints as Endpoints +# pylint: disable=R0904 # Aligning to the number of operations within this API class FileVantage(ServiceClass): """The only requirement to instantiate an instance of this class is one of the following. @@ -61,6 +65,102 @@ class FileVantage(ServiceClass): - a valid token provided by the authentication service class (oauth2.py) """ + @force_default(defaults=["parameters"], default_types=["dict"]) + def get_actions(self: object, *args, parameters: dict = None, **kwargs) -> Dict[str, Union[int, dict]]: + """Retrieve the processing result for one or more actions. + + Keyword arguments: + ids -- Action IDs to retrieve. String or list of strings. + parameters - full parameters payload, not required if ids is provided as a keyword. + + Arguments: When not specified, the first argument to this method is assumed to be 'ids'. + All others are ignored. + + Returns: dict object containing API response. + + HTTP Method: GET + + Swagger URL + https://assets.falcon.crowdstrike.com/support/api/swagger.html#/filevantage/getActionsMixin0 + """ + return process_service_request( + calling_object=self, + endpoints=Endpoints, + operation_id="getActionsMixin0", + keywords=kwargs, + params=handle_single_argument(args, parameters, "ids") + ) + + @force_default(defaults=["body"], default_types=["dict"]) + def start_actions(self: object, body: dict = None, **kwargs) -> Dict[str, Union[int, dict]]: + """Initiate the specified action on the provided change IDs. + + Keyword arguments: + body - full body payload in JSON format, not required if using other keywords. + { + "change_ids": [ + "string" + ], + "comment": "string", + "operation": "string" + } + change_ids -- Represents the IDs of the changes the operation will perform. + String or list of strings. Limited to 100 IDs per action. + comment -- OPtional comment to describe the reason for the action. String. + operation -- Operation to perform. String. Allowed values: suppress, unsuppress, or purge. + + This method only supports keywords for providing arguments. + + Returns: dict object containing API response. + + HTTP Method: POST + + Swagger URL + https://assets.falcon.crowdstrike.com/support/api/swagger.html#/filevantage/startActions + """ + if not body: + body = filevantage_start_payload(passed_keywords=kwargs) + + return process_service_request( + calling_object=self, + endpoints=Endpoints, + operation_id="startActions", + keywords=kwargs, + body=body + ) + + @force_default(defaults=["parameters"], default_types=["dict"]) + def get_contents(self: object, *args, parameters: dict = None, **kwargs) -> Dict[str, Union[int, dict]]: + """Retrieve the content captured for the provided change ID. + + Keyword arguments: + id -- Change IDs to retrieve. String. + compress -- Compress the response using gzip. Boolean. Defaults to False. + parameters - full parameters payload, not required if ids is provided as a keyword. + + Arguments: When not specified, the first argument to this method is assumed to be 'id'. + All others are ignored. + + Returns: dict object containing API response. + + HTTP Method: GET + + Swagger URL + https://assets.falcon.crowdstrike.com/support/api/swagger.html#/filevantage/getChanges + """ + header_payload = json.loads(json.dumps(self.headers)) + if kwargs.get("compress", None): + header_payload["Accept-Encoding"] = "gzip" + + return process_service_request( + calling_object=self, + endpoints=Endpoints, + operation_id="getContents", + keywords=kwargs, + params=handle_single_argument(args, parameters, "id"), + headers=header_payload + ) + @force_default(defaults=["parameters"], default_types=["dict"]) def get_changes(self: object, *args, parameters: dict = None, **kwargs) -> Dict[str, Union[int, dict]]: """Retrieve information on changes. @@ -977,6 +1077,77 @@ def update_rule_group(self: object, body: dict = None, **kwargs) -> Dict[str, Un body=body ) + @force_default(defaults=["body"], default_types=["dict"]) + def signal_changes(self: object, *args, body: dict = None, **kwargs) -> Dict[str, Union[int, dict]]: + """Initiate a workflow for the provided change IDs. + + Keyword arguments: + body - full body payload, not required if ids is provided as a keyword. + { + "ids": [ + "string" + ] + } + ids -- Action IDs to retrieve. String or list of strings. + + Arguments: When not specified, the first argument to this method is assumed to be 'ids'. + All others are ignored. + + Returns: dict object containing API response. + + HTTP Method: POST + + Swagger URL + https://assets.falcon.crowdstrike.com/support/api/swagger.html#/filevantage/signalChangesExternal + """ + parameters = handle_single_argument(args, kwargs, "ids") + + if not body: + body = generic_payload_list(submitted_keywords=kwargs, payload_value="ids") + # Try to gracefully catch IDs passed incorrectly as a query string parameter + if parameters: + if "ids" in parameters and "ids" not in body: + body["ids"] = parameters["ids"] + + return process_service_request( + calling_object=self, + endpoints=Endpoints, + operation_id="signalChangesExternal", + keywords=kwargs, + body=body + ) + + @force_default(defaults=["parameters"], default_types=["dict"]) + def query_actions(self: object, parameters: dict = None, **kwargs) -> Dict[str, Union[int, dict]]: + """Search for actions within your environment. Returns one or more action IDs. + + Keyword arguments: + filter -- The filter expression that should be used to limit the results. FQL syntax. String. + limit -- The maximum number of records to return. [Integer, 1-500, Default: 100] + offset -- The integer offset to start retrieving records from. Integer. + parameters - full parameters payload, not required if using other keywords. + sort -- The property to sort by. FQL syntax (e.g. status.desc or hostname.asc). String. + Available sort fields + action_timestamp ingestion_timestamp + + + This method only supports keywords for providing arguments. + + Returns: dict object containing API response. + + HTTP Method: GET + + Swagger URL + https://assets.falcon.crowdstrike.com/support/api/swagger.html#/filevantage/queryActionsMixin0 + """ + return process_service_request( + calling_object=self, + endpoints=Endpoints, + operation_id="queryActionsMixin0", + keywords=kwargs, + params=parameters + ) + @force_default(defaults=["parameters"], default_types=["dict"]) def query_changes(self: object, parameters: dict = None, **kwargs) -> Dict[str, Union[int, dict]]: """Search for changes within your environment. Returns one or more change IDs. @@ -1154,6 +1325,9 @@ def query_rule_groups(self: object, parameters: dict = None, **kwargs) -> Dict[s # This method name aligns to the operation ID in the API but # does not conform to snake_case / PEP8 and is defined here # for backwards compatibility / ease of use purposes + getActionsMixin0 = get_actions + startActions = start_actions + getContents = get_contents updatePolicyHostGroups = update_policy_host_groups updatePolicyPrecedence = update_policy_precedence updatePolicyRuleGroups = update_policy_rule_groups @@ -1175,6 +1349,8 @@ def query_rule_groups(self: object, parameters: dict = None, **kwargs) -> Dict[s deleteRuleGroups = delete_rule_groups updateRuleGroups = update_rule_group getChanges = get_changes + signalChangesExternal = signal_changes + queryActionsMixin0 = query_actions queryChanges = query_changes highVolumeQueryChanges = query_changes_scroll queryRuleGroups = query_rule_groups diff --git a/tests/test_filevantage.py b/tests/test_filevantage.py index 0c1779777..bfd6059df 100644 --- a/tests/test_filevantage.py +++ b/tests/test_filevantage.py @@ -72,7 +72,12 @@ def service_filevantage_remaining_tests(self): "update_rule_group" : falcon.update_rule_group(), "query_policies": falcon.query_policies(), "query_scheduled_exclusions": falcon.query_scheduled_exclusions(), - "query_rule_groups": falcon.query_rule_groups(type="WindowsFiles") + "query_rule_groups": falcon.query_rule_groups(type="WindowsFiles"), + "getActionsMixin0": falcon.get_actions(ids="123456"), + "startActions": falcon.start_actions(change_ids="123456", comment="whatever", operation="whatever"), + "getContents": falcon.get_contents(id="123456", compress=True), + "signalChangesExternal": falcon.signal_changes("123456"), + "queryActionsMixin0": falcon.query_actions() } for key in tests: if tests[key]["status_code"] not in AllowedResponses: